note display performance.
[ardour.git] / gtk2_ardour / midi_region_view.cc
1 /*
2     Copyright (C) 2001-2011 Paul Davis
3     Author: David Robillard
4
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.
9
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.
14
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.
18 */
19
20 #include <cmath>
21 #include <algorithm>
22 #include <ostream>
23
24 #include <gtkmm.h>
25
26 #include "gtkmm2ext/gtk_ui.h"
27
28 #include <sigc++/signal.h>
29
30 #include "midi++/midnam_patch.h"
31
32 #include "pbd/memento_command.h"
33 #include "pbd/stateful_diff_command.h"
34
35 #include "ardour/midi_model.h"
36 #include "ardour/midi_playlist.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/operations.h"
41 #include "ardour/session.h"
42
43 #include "evoral/Parameter.hpp"
44 #include "evoral/Event.hpp"
45 #include "evoral/Control.hpp"
46 #include "evoral/midi_util.h"
47
48 #include "canvas/debug.h"
49 #include "canvas/text.h"
50
51 #include "automation_region_view.h"
52 #include "automation_time_axis.h"
53 #include "control_point.h"
54 #include "debug.h"
55 #include "editor.h"
56 #include "editor_drag.h"
57 #include "ghostregion.h"
58 #include "gui_thread.h"
59 #include "item_counts.h"
60 #include "keyboard.h"
61 #include "midi_channel_dialog.h"
62 #include "midi_cut_buffer.h"
63 #include "midi_list_editor.h"
64 #include "midi_region_view.h"
65 #include "midi_streamview.h"
66 #include "midi_time_axis.h"
67 #include "midi_util.h"
68 #include "midi_velocity_dialog.h"
69 #include "mouse_cursors.h"
70 #include "note_player.h"
71 #include "paste_context.h"
72 #include "public_editor.h"
73 #include "route_time_axis.h"
74 #include "rgb_macros.h"
75 #include "selection.h"
76 #include "streamview.h"
77 #include "patch_change_dialog.h"
78 #include "verbose_cursor.h"
79 #include "note.h"
80 #include "hit.h"
81 #include "patch_change.h"
82 #include "sys_ex.h"
83 #include "ui_config.h"
84
85 #include "pbd/i18n.h"
86
87 using namespace ARDOUR;
88 using namespace PBD;
89 using namespace Editing;
90 using namespace std;
91 using Gtkmm2ext::Keyboard;
92
93 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
94
95 MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
96                                 RouteTimeAxisView&            tv,
97                                 boost::shared_ptr<MidiRegion> r,
98                                 double                        spu,
99                                 uint32_t                      basic_color)
100         : RegionView (parent, tv, r, spu, basic_color)
101         , _current_range_min(0)
102         , _current_range_max(0)
103         , _region_relative_time_converter(r->session().tempo_map(), r->position())
104         , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
105         , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
106         , _active_notes(0)
107         , _note_group (new ArdourCanvas::Container (group))
108         , _note_diff_command (0)
109         , _ghost_note(0)
110         , _step_edit_cursor (0)
111         , _step_edit_cursor_width (1.0)
112         , _step_edit_cursor_position (0.0)
113         , _channel_selection_scoped_note (0)
114         , _mouse_state(None)
115         , _pressed_button(0)
116         , _optimization_iterator (_events.end())
117         , _list_editor (0)
118         , _no_sound_notes (false)
119         , _last_display_zoom (0)
120         , _last_event_x (0)
121         , _last_event_y (0)
122         , _grabbed_keyboard (false)
123         , _entered (false)
124         , _entered_note (0)
125         , _mouse_changed_selection (false)
126 {
127         CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
128
129         _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
130         _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
131
132         _note_group->raise_to_top();
133         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
134
135         Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
136         connect_to_diskstream ();
137 }
138
139 MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
140                                 RouteTimeAxisView&            tv,
141                                 boost::shared_ptr<MidiRegion> r,
142                                 double                        spu,
143                                 uint32_t                      basic_color,
144                                 bool                          recording,
145                                 TimeAxisViewItem::Visibility  visibility)
146         : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
147         , _current_range_min(0)
148         , _current_range_max(0)
149         , _region_relative_time_converter(r->session().tempo_map(), r->position())
150         , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
151         , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
152         , _active_notes(0)
153         , _note_group (new ArdourCanvas::Container (group))
154         , _note_diff_command (0)
155         , _ghost_note(0)
156         , _step_edit_cursor (0)
157         , _step_edit_cursor_width (1.0)
158         , _step_edit_cursor_position (0.0)
159         , _channel_selection_scoped_note (0)
160         , _mouse_state(None)
161         , _pressed_button(0)
162         , _optimization_iterator (_events.end())
163         , _list_editor (0)
164         , _no_sound_notes (false)
165         , _last_display_zoom (0)
166         , _last_event_x (0)
167         , _last_event_y (0)
168         , _grabbed_keyboard (false)
169         , _entered (false)
170         , _entered_note (0)
171         , _mouse_changed_selection (false)
172 {
173         CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
174
175         _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
176         _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
177
178         _note_group->raise_to_top();
179
180         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
181
182         connect_to_diskstream ();
183 }
184
185 void
186 MidiRegionView::parameter_changed (std::string const & p)
187 {
188         if (p == "display-first-midi-bank-as-zero") {
189                 if (_enable_display) {
190                         redisplay_model();
191                 }
192         } else if (p == "color-regions-using-track-color") {
193                 set_colors ();
194         }
195 }
196
197 MidiRegionView::MidiRegionView (const MidiRegionView& other)
198         : sigc::trackable(other)
199         , RegionView (other)
200         , _current_range_min(0)
201         , _current_range_max(0)
202         , _region_relative_time_converter(other.region_relative_time_converter())
203         , _source_relative_time_converter(other.source_relative_time_converter())
204         , _region_relative_time_converter_double(other.region_relative_time_converter_double())
205         , _active_notes(0)
206         , _note_group (new ArdourCanvas::Container (get_canvas_group()))
207         , _note_diff_command (0)
208         , _ghost_note(0)
209         , _step_edit_cursor (0)
210         , _step_edit_cursor_width (1.0)
211         , _step_edit_cursor_position (0.0)
212         , _channel_selection_scoped_note (0)
213         , _mouse_state(None)
214         , _pressed_button(0)
215         , _optimization_iterator (_events.end())
216         , _list_editor (0)
217         , _no_sound_notes (false)
218         , _last_display_zoom (0)
219         , _last_event_x (0)
220         , _last_event_y (0)
221         , _grabbed_keyboard (false)
222         , _entered (false)
223         , _entered_note (0)
224         , _mouse_changed_selection (false)
225 {
226         init (false);
227 }
228
229 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
230         : RegionView (other, boost::shared_ptr<Region> (region))
231         , _current_range_min(0)
232         , _current_range_max(0)
233         , _region_relative_time_converter(other.region_relative_time_converter())
234         , _source_relative_time_converter(other.source_relative_time_converter())
235         , _region_relative_time_converter_double(other.region_relative_time_converter_double())
236         , _active_notes(0)
237         , _note_group (new ArdourCanvas::Container (get_canvas_group()))
238         , _note_diff_command (0)
239         , _ghost_note(0)
240         , _step_edit_cursor (0)
241         , _step_edit_cursor_width (1.0)
242         , _step_edit_cursor_position (0.0)
243         , _channel_selection_scoped_note (0)
244         , _mouse_state(None)
245         , _pressed_button(0)
246         , _optimization_iterator (_events.end())
247         , _list_editor (0)
248         , _no_sound_notes (false)
249         , _last_display_zoom (0)
250         , _last_event_x (0)
251         , _last_event_y (0)
252         , _grabbed_keyboard (false)
253         , _entered (false)
254         , _entered_note (0)
255         , _mouse_changed_selection (false)
256 {
257         init (true);
258 }
259
260 void
261 MidiRegionView::init (bool wfd)
262 {
263         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
264
265         if (wfd) {
266                 Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
267                 midi_region()->midi_source(0)->load_model(lm);
268         }
269
270         _model = midi_region()->midi_source(0)->model();
271         _enable_display = false;
272         fill_color_name = "midi frame base";
273
274         RegionView::init (false);
275
276         //set_height (trackview.current_height());
277
278         region_muted ();
279         region_sync_changed ();
280         region_resized (ARDOUR::bounds_change);
281         //region_locked ();
282
283         set_colors ();
284
285         _enable_display = true;
286         if (_model) {
287                 if (wfd) {
288                         display_model (_model);
289                 }
290         }
291
292         reset_width_dependent_items (_pixel_width);
293
294         group->raise_to_top();
295
296         midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
297                                                                        boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
298                                                                        gui_context ());
299
300         instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
301                                            boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
302
303         trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
304                                                boost::bind (&MidiRegionView::snap_changed, this),
305                                                gui_context());
306
307         trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
308                                                     boost::bind (&MidiRegionView::mouse_mode_changed, this),
309                                                     gui_context ());
310
311         Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
312         UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
313         connect_to_diskstream ();
314 }
315
316 InstrumentInfo&
317 MidiRegionView::instrument_info () const
318 {
319         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
320         return route_ui->route()->instrument_info();
321 }
322
323 const boost::shared_ptr<ARDOUR::MidiRegion>
324 MidiRegionView::midi_region() const
325 {
326         return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
327 }
328
329 void
330 MidiRegionView::connect_to_diskstream ()
331 {
332         midi_view()->midi_track()->DataRecorded.connect(
333                 *this, invalidator(*this),
334                 boost::bind (&MidiRegionView::data_recorded, this, _1),
335                 gui_context());
336 }
337
338 bool
339 MidiRegionView::canvas_group_event(GdkEvent* ev)
340 {
341         if (in_destructor || _recregion) {
342                 return false;
343         }
344
345         if (!trackview.editor().internal_editing()) {
346                 // not in internal edit mode, so just act like a normal region
347                 return RegionView::canvas_group_event (ev);
348         }
349
350         bool r;
351
352         switch (ev->type) {
353         case GDK_ENTER_NOTIFY:
354                 _last_event_x = ev->crossing.x;
355                 _last_event_y = ev->crossing.y;
356                 enter_notify(&ev->crossing);
357                 // set entered_regionview (among other things)
358                 return RegionView::canvas_group_event (ev);
359
360         case GDK_LEAVE_NOTIFY:
361                 _last_event_x = ev->crossing.x;
362                 _last_event_y = ev->crossing.y;
363                 leave_notify(&ev->crossing);
364                 // reset entered_regionview (among other things)
365                 return RegionView::canvas_group_event (ev);
366
367         case GDK_SCROLL:
368                 if (scroll (&ev->scroll)) {
369                         return true;
370                 }
371                 break;
372
373         case GDK_KEY_PRESS:
374                 return key_press (&ev->key);
375
376         case GDK_KEY_RELEASE:
377                 return key_release (&ev->key);
378
379         case GDK_BUTTON_PRESS:
380                 return button_press (&ev->button);
381
382         case GDK_BUTTON_RELEASE:
383                 r = button_release (&ev->button);
384                 return r;
385
386         case GDK_MOTION_NOTIFY:
387                 _last_event_x = ev->motion.x;
388                 _last_event_y = ev->motion.y;
389                 return motion (&ev->motion);
390
391         default:
392                 break;
393         }
394
395         return RegionView::canvas_group_event (ev);
396 }
397
398 bool
399 MidiRegionView::enter_notify (GdkEventCrossing* ev)
400 {
401         enter_internal (ev->state);
402
403         _entered = true;
404         return false;
405 }
406
407 bool
408 MidiRegionView::leave_notify (GdkEventCrossing*)
409 {
410         leave_internal();
411
412         _entered = false;
413         return false;
414 }
415
416 void
417 MidiRegionView::mouse_mode_changed ()
418 {
419         // Adjust frame colour (become more transparent for internal tools)
420         set_frame_color();
421
422         if (_entered) {
423                 if (!trackview.editor().internal_editing()) {
424                         /* Switched out of internal editing mode while entered.
425                         Only necessary for leave as a mouse_mode_change over a region
426                         automatically triggers an enter event. */
427                         leave_internal();
428                 }
429                 else if (trackview.editor().current_mouse_mode() == MouseContent) {
430                         // hide cursor and ghost note after changing to internal edit mode
431                         remove_ghost_note ();
432
433                         /* XXX This is problematic as the function is executed for every region
434                         and only for one region _entered_note can be true. Still it's
435                         necessary as to hide the verbose cursor when we're changing from
436                         draw mode to internal edit mode. These lines are the reason why
437                         in some situations no verbose cursor is shown when we enter internal
438                         edit mode over a note. */
439                         if (!_entered_note) {
440                                 hide_verbose_cursor ();
441                         }
442                 }
443         }
444 }
445
446 void
447 MidiRegionView::enter_internal (uint32_t state)
448 {
449         if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
450                 // Show ghost note under pencil
451                 create_ghost_note(_last_event_x, _last_event_y, state);
452         }
453
454         if (!_selection.empty()) {
455                 // Grab keyboard for moving selected notes with arrow keys
456                 Keyboard::magic_widget_grab_focus();
457                 _grabbed_keyboard = true;
458         }
459
460         // Lower frame handles below notes so they don't steal events
461         if (frame_handle_start) {
462                 frame_handle_start->lower_to_bottom();
463         }
464         if (frame_handle_end) {
465                 frame_handle_end->lower_to_bottom();
466         }
467 }
468
469 void
470 MidiRegionView::leave_internal()
471 {
472         hide_verbose_cursor ();
473         remove_ghost_note ();
474         _entered_note = 0;
475
476         if (_grabbed_keyboard) {
477                 Keyboard::magic_widget_drop_focus();
478                 _grabbed_keyboard = false;
479         }
480
481         // Raise frame handles above notes so they catch events
482         if (frame_handle_start) {
483                 frame_handle_start->raise_to_top();
484         }
485         if (frame_handle_end) {
486                 frame_handle_end->raise_to_top();
487         }
488 }
489
490 bool
491 MidiRegionView::button_press (GdkEventButton* ev)
492 {
493         if (ev->button != 1) {
494                 return false;
495         }
496
497         Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
498         MouseMode m = editor->current_mouse_mode();
499
500         if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
501                 _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
502         }
503
504         if (_mouse_state != SelectTouchDragging) {
505
506                 _pressed_button = ev->button;
507
508                 if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
509
510                         if (midi_view()->note_mode() == Percussive) {
511                                 editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
512                         } else {
513                                 editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
514                         }
515
516                         _mouse_state = AddDragging;
517                         remove_ghost_note ();
518                         hide_verbose_cursor ();
519                 } else {
520                         _mouse_state = Pressed;
521                 }
522
523                 return true;
524         }
525
526         _pressed_button = ev->button;
527         _mouse_changed_selection = false;
528
529         return true;
530 }
531
532 bool
533 MidiRegionView::button_release (GdkEventButton* ev)
534 {
535         double event_x, event_y;
536
537         if (ev->button != 1) {
538                 return false;
539         }
540
541         event_x = ev->x;
542         event_y = ev->y;
543
544         group->canvas_to_item (event_x, event_y);
545         group->ungrab ();
546
547         PublicEditor& editor = trackview.editor ();
548
549         _press_cursor_ctx.reset();
550
551         switch (_mouse_state) {
552         case Pressed: // Clicked
553
554                 switch (editor.current_mouse_mode()) {
555                 case MouseRange:
556                         /* no motion occurred - simple click */
557                         clear_editor_note_selection ();
558                         _mouse_changed_selection = true;
559                         break;
560
561                 case MouseContent:
562                 case MouseTimeFX:
563                         {
564                                 _mouse_changed_selection = true;
565                                 clear_editor_note_selection ();
566
567                                 break;
568                         }
569                 case MouseDraw:
570                         break;
571
572                 default:
573                         break;
574                 }
575
576                 _mouse_state = None;
577                 break;
578
579         case AddDragging:
580                 /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
581                    we don't want one when we were drag-selecting either. */
582         case SelectRectDragging:
583                 editor.drags()->end_grab ((GdkEvent *) ev);
584                 _mouse_state = None;
585                 break;
586
587
588         default:
589                 break;
590         }
591
592         if (_mouse_changed_selection) {
593                 trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
594                 trackview.editor().commit_reversible_selection_op ();
595         }
596
597         return false;
598 }
599
600 bool
601 MidiRegionView::motion (GdkEventMotion* ev)
602 {
603         PublicEditor& editor = trackview.editor ();
604
605         if (!_entered_note) {
606
607                 if (_mouse_state == AddDragging) {
608                         if (_ghost_note) {
609                                 remove_ghost_note ();
610                         }
611
612                 } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
613                     Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
614                     _mouse_state != AddDragging) {
615
616                         create_ghost_note (ev->x, ev->y, ev->state);
617
618                 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
619                            Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
620
621                         update_ghost_note (ev->x, ev->y, ev->state);
622
623                 } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
624
625                         remove_ghost_note ();
626                         hide_verbose_cursor ();
627
628                 } else if (editor.current_mouse_mode() == MouseDraw) {
629
630                         if (_ghost_note) {
631                                 update_ghost_note (ev->x, ev->y, ev->state);
632                         }
633                         else {
634                                 create_ghost_note (ev->x, ev->y, ev->state);
635                         }
636                 }
637         }
638
639         /* any motion immediately hides velocity text that may have been visible */
640
641         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
642                 (*i)->hide_velocity ();
643         }
644
645         switch (_mouse_state) {
646         case Pressed:
647
648                 if (_pressed_button == 1) {
649
650                         MouseMode m = editor.current_mouse_mode();
651
652                         if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
653                                 editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
654                                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
655                                         clear_editor_note_selection ();
656                                         _mouse_changed_selection = true;
657                                 }
658                                 _mouse_state = SelectRectDragging;
659                                 return true;
660                         } else if (m == MouseRange) {
661                                 editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
662                                 _mouse_state = SelectVerticalDragging;
663                                 return true;
664                         }
665                 }
666
667                 return false;
668
669         case SelectRectDragging:
670         case SelectVerticalDragging:
671         case AddDragging:
672                 editor.drags()->motion_handler ((GdkEvent *) ev, false);
673                 break;
674
675         case SelectTouchDragging:
676                 return false;
677
678         default:
679                 break;
680
681         }
682
683         /* we may be dragging some non-note object (eg. patch-change, sysex)
684          */
685
686         return editor.drags()->motion_handler ((GdkEvent *) ev, false);
687 }
688
689
690 bool
691 MidiRegionView::scroll (GdkEventScroll* ev)
692 {
693         if (_selection.empty()) {
694                 return false;
695         }
696
697         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
698             Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
699                 /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
700                  * through so that it still works for navigation.
701                 */
702                 return false;
703         }
704
705         hide_verbose_cursor ();
706
707         bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
708         Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
709         bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
710
711         if (ev->direction == GDK_SCROLL_UP) {
712                 change_velocities (true, fine, false, together);
713         } else if (ev->direction == GDK_SCROLL_DOWN) {
714                 change_velocities (false, fine, false, together);
715         } else {
716                 /* left, right: we don't use them */
717                 return false;
718         }
719
720         return true;
721 }
722
723 bool
724 MidiRegionView::key_press (GdkEventKey* ev)
725 {
726         /* since GTK bindings are generally activated on press, and since
727            detectable auto-repeat is the name of the game and only sends
728            repeated presses, carry out key actions at key press, not release.
729         */
730         bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
731
732         if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
733
734                 if (_mouse_state != AddDragging) {
735                         _mouse_state = SelectTouchDragging;
736                 }
737
738                 return true;
739
740         } else if (ev->keyval == GDK_Escape && unmodified) {
741                 clear_editor_note_selection ();
742                 _mouse_state = None;
743
744         } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
745
746                 bool start = (ev->keyval == GDK_comma);
747                 bool end = (ev->keyval == GDK_period);
748                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
749                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
750
751                 change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
752
753                 return true;
754
755         } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
756
757                 if (_selection.empty()) {
758                         return false;
759                 }
760
761                 delete_selection();
762                 return true;
763
764         } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
765
766                 trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
767
768                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
769                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
770                 } else {
771                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
772                 }
773
774                 trackview.editor().commit_reversible_selection_op();
775
776                 return true;
777
778         } else if (ev->keyval == GDK_Up) {
779
780                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
781                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
782                 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
783
784                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
785                         change_velocities (true, fine, allow_smush, together);
786                 } else {
787                         transpose (true, fine, allow_smush);
788                 }
789                 return true;
790
791         } else if (ev->keyval == GDK_Down) {
792
793                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
794                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
795                 bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
796
797                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
798                         change_velocities (false, fine, allow_smush, together);
799                 } else {
800                         transpose (false, fine, allow_smush);
801                 }
802                 return true;
803
804         } else if (ev->keyval == GDK_Left) {
805
806                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
807                 nudge_notes (false, fine);
808                 return true;
809
810         } else if (ev->keyval == GDK_Right) {
811
812                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
813                 nudge_notes (true, fine);
814                 return true;
815
816         } else if (ev->keyval == GDK_c && unmodified) {
817                 channel_edit ();
818                 return true;
819
820         } else if (ev->keyval == GDK_v && unmodified) {
821                 velocity_edit ();
822                 return true;
823         }
824
825         return false;
826 }
827
828 bool
829 MidiRegionView::key_release (GdkEventKey* ev)
830 {
831         if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
832                 _mouse_state = None;
833                 return true;
834         }
835         return false;
836 }
837
838 void
839 MidiRegionView::channel_edit ()
840 {
841         if (_selection.empty()) {
842                 return;
843         }
844
845         /* pick a note somewhat at random (since Selection is a set<>) to
846          * provide the "current" channel for the dialog.
847          */
848
849         uint8_t current_channel = (*_selection.begin())->note()->channel ();
850         MidiChannelDialog channel_dialog (current_channel);
851         int ret = channel_dialog.run ();
852
853         switch (ret) {
854         case Gtk::RESPONSE_OK:
855                 break;
856         default:
857                 return;
858         }
859
860         uint8_t new_channel = channel_dialog.active_channel ();
861
862         start_note_diff_command (_("channel edit"));
863
864         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
865                 Selection::iterator next = i;
866                 ++next;
867                 change_note_channel (*i, new_channel);
868                 i = next;
869         }
870
871         apply_diff ();
872 }
873
874 void
875 MidiRegionView::velocity_edit ()
876 {
877         if (_selection.empty()) {
878                 return;
879         }
880
881         /* pick a note somewhat at random (since Selection is a set<>) to
882          * provide the "current" velocity for the dialog.
883          */
884
885         uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
886         MidiVelocityDialog velocity_dialog (current_velocity);
887         int ret = velocity_dialog.run ();
888
889         switch (ret) {
890         case Gtk::RESPONSE_OK:
891                 break;
892         default:
893                 return;
894         }
895
896         uint8_t new_velocity = velocity_dialog.velocity ();
897
898         start_note_diff_command (_("velocity edit"));
899
900         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
901                 Selection::iterator next = i;
902                 ++next;
903                 change_note_velocity (*i, new_velocity, false);
904                 i = next;
905         }
906
907         apply_diff ();
908 }
909
910 void
911 MidiRegionView::show_list_editor ()
912 {
913         if (!_list_editor) {
914                 _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
915         }
916         _list_editor->present ();
917 }
918
919 /** Add a note to the model, and the view, at a canvas (click) coordinate.
920  * \param t time in frames relative to the position of the region
921  * \param y vertical position in pixels
922  * \param length duration of the note in beats
923  * \param snap_t true to snap t to the grid, otherwise false.
924  */
925 void
926 MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
927 {
928         if (length < 2 * DBL_EPSILON) {
929                 return;
930         }
931
932         MidiTimeAxisView* const mtv  = dynamic_cast<MidiTimeAxisView*>(&trackview);
933         MidiStreamView* const   view = mtv->midi_view();
934         boost::shared_ptr<MidiRegion> mr  = boost::dynamic_pointer_cast<MidiRegion> (_region);
935
936         if (!mr) {
937                 return;
938         }
939
940         // Start of note in frames relative to region start
941         const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
942         Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
943
944         const double  note     = view->y_to_note(y);
945         const uint8_t chan     = mtv->get_channel_for_add();
946         const uint8_t velocity = get_velocity_for_add(beat_time);
947
948         const boost::shared_ptr<NoteType> new_note(
949                 new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
950
951         if (_model->contains (new_note)) {
952                 return;
953         }
954
955         view->update_note_range(new_note->note());
956
957         start_note_diff_command(_("add note"));
958
959         clear_editor_note_selection ();
960         note_diff_add_note (new_note, true, false);
961
962         apply_diff();
963
964         play_midi_note (new_note);
965 }
966
967 void
968 MidiRegionView::clear_events ()
969 {
970         // clear selection without signaling
971         clear_selection_internal ();
972
973         MidiGhostRegion* gr;
974         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
975                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
976                         gr->clear_events();
977                 }
978         }
979
980
981         _note_group->clear (true);
982         _events.clear();
983         _patch_changes.clear();
984         _sys_exes.clear();
985         _optimization_iterator = _events.end();
986 }
987
988 void
989 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
990 {
991         _model = model;
992
993         content_connection.disconnect ();
994         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
995         /* Don't signal as nobody else needs to know until selection has been altered. */
996         clear_events();
997
998         if (_enable_display) {
999                 redisplay_model();
1000         }
1001 }
1002
1003 void
1004 MidiRegionView::start_note_diff_command (string name)
1005 {
1006         if (!_note_diff_command) {
1007                 trackview.editor().begin_reversible_command (name);
1008                 _note_diff_command = _model->new_note_diff_command (name);
1009         }
1010 }
1011
1012 void
1013 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
1014 {
1015         if (_note_diff_command) {
1016                 _note_diff_command->add (note);
1017         }
1018         if (selected) {
1019                 _marked_for_selection.insert(note);
1020         }
1021         if (show_velocity) {
1022                 _marked_for_velocity.insert(note);
1023         }
1024 }
1025
1026 void
1027 MidiRegionView::note_diff_remove_note (NoteBase* ev)
1028 {
1029         if (_note_diff_command && ev->note()) {
1030                 _note_diff_command->remove(ev->note());
1031         }
1032 }
1033
1034 void
1035 MidiRegionView::note_diff_add_change (NoteBase* ev,
1036                                       MidiModel::NoteDiffCommand::Property property,
1037                                       uint8_t val)
1038 {
1039         if (_note_diff_command) {
1040                 _note_diff_command->change (ev->note(), property, val);
1041         }
1042 }
1043
1044 void
1045 MidiRegionView::note_diff_add_change (NoteBase* ev,
1046                                       MidiModel::NoteDiffCommand::Property property,
1047                                       Evoral::Beats val)
1048 {
1049         if (_note_diff_command) {
1050                 _note_diff_command->change (ev->note(), property, val);
1051         }
1052 }
1053
1054 void
1055 MidiRegionView::apply_diff (bool as_subcommand)
1056 {
1057         bool add_or_remove;
1058         bool commit = false;
1059
1060         if (!_note_diff_command) {
1061                 return;
1062         }
1063
1064         if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1065                 // Mark all selected notes for selection when model reloads
1066                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1067                         _marked_for_selection.insert((*i)->note());
1068                 }
1069         }
1070
1071         midi_view()->midi_track()->midi_playlist()->region_edited(
1072                 _region, _note_diff_command);
1073
1074         if (as_subcommand) {
1075                 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1076         } else {
1077                 _model->apply_command (*trackview.session(), _note_diff_command);
1078                 commit = true;
1079         }
1080
1081         _note_diff_command = 0;
1082
1083         if (add_or_remove) {
1084                 _marked_for_selection.clear();
1085         }
1086
1087         _marked_for_velocity.clear();
1088         if (commit) {
1089                 trackview.editor().commit_reversible_command ();
1090         }
1091 }
1092
1093 void
1094 MidiRegionView::abort_command()
1095 {
1096         delete _note_diff_command;
1097         _note_diff_command = 0;
1098         clear_editor_note_selection();
1099 }
1100
1101 NoteBase*
1102 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1103 {
1104
1105         if (_optimization_iterator != _events.end()) {
1106                 ++_optimization_iterator;
1107         }
1108
1109         if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
1110                 return _optimization_iterator->second;
1111         }
1112
1113         _optimization_iterator = _events.find (note);
1114         if (_optimization_iterator != _events.end()) {
1115                 return _optimization_iterator->second;
1116         }
1117
1118         return 0;
1119 }
1120
1121 /** This version finds any canvas note matching the supplied note. */
1122 NoteBase*
1123 MidiRegionView::find_canvas_note (Evoral::event_id_t id)
1124 {
1125         Events::iterator it;
1126
1127         for (it = _events.begin(); it != _events.end(); ++it) {
1128                 if (it->first->id() == id) {
1129                         return it->second;
1130                 }
1131         }
1132
1133         return 0;
1134 }
1135
1136 boost::shared_ptr<PatchChange>
1137 MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
1138 {
1139         PatchChanges::const_iterator f = _patch_changes.find (p);
1140
1141         if (f != _patch_changes.end()) {
1142                 return f->second;
1143         }
1144
1145         return boost::shared_ptr<PatchChange>();
1146 }
1147
1148 void
1149 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
1150 {
1151         MidiModel::Notes notes;
1152         _model->get_notes (notes, op, val, chan_mask);
1153
1154         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1155                 NoteBase* cne = find_canvas_note (*n);
1156                 if (cne) {
1157                         e.insert (make_pair (*n, cne));
1158                 }
1159         }
1160 }
1161
1162 void
1163 MidiRegionView::redisplay_model()
1164 {
1165         if (_active_notes) {
1166                 // Currently recording
1167                 const framecnt_t zoom = trackview.editor().get_current_zoom();
1168                 if (zoom != _last_display_zoom) {
1169                         /* Update resolved canvas notes to reflect changes in zoom without
1170                            touching model.  Leave active notes (with length 0) alone since
1171                            they are being extended. */
1172                         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1173                                 if (i->second->note()->length() > 0) {
1174                                         update_note(i->second);
1175                                 }
1176                         }
1177                         _last_display_zoom = zoom;
1178                 }
1179                 return;
1180         }
1181
1182         if (!_model) {
1183                 return;
1184         }
1185
1186         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1187                 _optimization_iterator->second->invalidate();
1188         }
1189
1190         bool empty_when_starting = _events.empty();
1191         _optimization_iterator = _events.begin();
1192         MidiModel::Notes missing_notes;
1193         Note* sus = NULL;
1194         Hit*  hit = NULL;
1195
1196         MidiModel::ReadLock lock(_model->read_lock());
1197         MidiModel::Notes& notes (_model->notes());
1198
1199         NoteBase* cne;
1200         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1201
1202                 boost::shared_ptr<NoteType> note (*n);
1203                 bool visible;
1204
1205                 if (note_in_region_range (note, visible)) {
1206                         if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
1207                                 cne->validate ();
1208                                 bool update = false;
1209
1210                                 if (note_in_region_range (note, visible)) {
1211                                         if (visible) {
1212                                                 update = true;
1213                                                 cne->show ();
1214                                         } else {
1215                                                 cne->hide ();
1216                                         }
1217                                 } else {
1218                                         cne->hide ();
1219                                 }
1220                                 if ((sus = dynamic_cast<Note*>(cne))) {
1221
1222                                         if (update) {
1223                                                 update_sustained (sus);
1224                                         }
1225
1226                                         for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1227                                                 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1228                                                 if (gr && !gr->trackview.hidden()) {
1229                                                         gr->update_note (sus, !update);
1230                                                 }
1231                                         }
1232                                 } else if ((hit = dynamic_cast<Hit*>(cne))) {
1233
1234                                         if (update) {
1235                                                 update_hit (hit);
1236                                         }
1237
1238                                         for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1239                                                 MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1240                                                 if (gr && !gr->trackview.hidden()) {
1241                                                         gr->update_hit (hit, !update);
1242                                                 }
1243                                         }
1244                                 }
1245                         } else {
1246                                 missing_notes.insert (note);
1247                         }
1248                 }
1249         }
1250
1251         if (!empty_when_starting) {
1252                 MidiModel::Notes::iterator f;
1253                 for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1254
1255                         NoteBase* cne = i->second;
1256
1257                         /* remove note items that are no longer valid */
1258                         if (!cne->valid()) {
1259
1260                                 for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
1261                                         MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
1262                                         if (gr) {
1263                                                 gr->remove_note (cne);
1264                                         }
1265                                 }
1266
1267                                 delete cne;
1268                                 i = _events.erase (i);
1269
1270                         } else {
1271                                 ++i;
1272                         }
1273                 }
1274         }
1275
1276         for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
1277                 boost::shared_ptr<NoteType> note (*n);
1278                 NoteBase* cne;
1279                 bool visible;
1280
1281                 if (note_in_region_range (note, visible)) {
1282                         if (visible) {
1283                                 cne = add_note (note, true);
1284                         } else {
1285                                 cne = add_note (note, false);
1286                         }
1287                 } else {
1288                         cne = add_note (note, false);
1289                 }
1290
1291                 for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
1292                         if ((*it) == note->id()) {
1293                                 add_to_selection (cne);
1294                         }
1295                 }
1296         }
1297
1298         _sys_exes.clear();
1299
1300         display_sysexes();
1301         display_patch_changes ();
1302
1303         _marked_for_selection.clear ();
1304         _marked_for_velocity.clear ();
1305         _pending_note_selection.clear ();
1306
1307 }
1308
1309 void
1310 MidiRegionView::display_patch_changes ()
1311 {
1312         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1313         uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
1314
1315         for (uint8_t i = 0; i < 16; ++i) {
1316                 display_patch_changes_on_channel (i, chn_mask & (1 << i));
1317         }
1318 }
1319
1320 /** @param active_channel true to display patch changes fully, false to display
1321  * them `greyed-out' (as on an inactive channel)
1322  */
1323 void
1324 MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
1325 {
1326         for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1327                 boost::shared_ptr<PatchChange> p;
1328
1329                 if ((*i)->channel() != channel) {
1330                         continue;
1331                 }
1332
1333                 if ((p = find_canvas_patch_change (*i)) != 0) {
1334
1335                         const framecnt_t region_frames = source_beats_to_region_frames ((*i)->time());
1336
1337                         if (region_frames < 0 || region_frames >= _region->length()) {
1338                                 p->hide();
1339                         } else {
1340                                 const double x = trackview.editor().sample_to_pixel (region_frames);
1341                                 const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1342                                 p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
1343                                 p->flag()->set_text (patch_name);
1344
1345                                 p->show();
1346                         }
1347
1348                 } else {
1349                         const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
1350                         add_canvas_patch_change (*i, patch_name, active_channel);
1351                 }
1352         }
1353 }
1354
1355 void
1356 MidiRegionView::display_sysexes()
1357 {
1358         bool have_periodic_system_messages = false;
1359         bool display_periodic_messages = true;
1360
1361         if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
1362
1363                 for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1364                         if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1365                                 have_periodic_system_messages = true;
1366                                 break;
1367                         }
1368                 }
1369
1370                 if (have_periodic_system_messages) {
1371                         double zoom = trackview.editor().get_current_zoom (); // frames per pixel
1372
1373                         /* get an approximate value for the number of samples per video frame */
1374
1375                         double video_frame = trackview.session()->frame_rate() * (1.0/30);
1376
1377                         /* if we are zoomed out beyond than the cutoff (i.e. more
1378                          * frames per pixel than frames per 4 video frames), don't
1379                          * show periodic sysex messages.
1380                          */
1381
1382                         if (zoom > (video_frame*4)) {
1383                                 display_periodic_messages = false;
1384                         }
1385                 }
1386         } else {
1387                 display_periodic_messages = false;
1388         }
1389
1390         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1391                 Evoral::Beats time = (*i)->time();
1392
1393                 if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
1394                         if (!display_periodic_messages) {
1395                                 continue;
1396                         }
1397                 }
1398
1399                 ostringstream str;
1400                 str << hex;
1401                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1402                         str << int((*i)->buffer()[b]);
1403                         if (b != (*i)->size() -1) {
1404                                 str << " ";
1405                         }
1406                 }
1407                 string text = str.str();
1408
1409                 const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
1410
1411                 double height = midi_stream_view()->contents_height();
1412
1413                 // CAIROCANVAS: no longer passing *i (the sysex event) to the
1414                 // SysEx canvas object!!!
1415
1416                 boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
1417                         new SysEx (*this, _note_group, text, height, x, 1.0));
1418
1419                 // Show unless message is beyond the region bounds
1420                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1421                         sysex->hide();
1422                 } else {
1423                         sysex->show();
1424                 }
1425
1426                 _sys_exes.push_back(sysex);
1427         }
1428 }
1429
1430 MidiRegionView::~MidiRegionView ()
1431 {
1432         in_destructor = true;
1433
1434         hide_verbose_cursor ();
1435
1436         delete _list_editor;
1437
1438         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1439
1440         if (_active_notes) {
1441                 end_write();
1442         }
1443         _entered_note = 0;
1444         clear_events ();
1445
1446         delete _note_group;
1447         delete _note_diff_command;
1448         delete _step_edit_cursor;
1449 }
1450
1451 void
1452 MidiRegionView::region_resized (const PropertyChange& what_changed)
1453 {
1454         RegionView::region_resized(what_changed); // calls RegionView::set_duration()
1455
1456         if (what_changed.contains (ARDOUR::Properties::position)) {
1457                 _region_relative_time_converter.set_origin_b(_region->position());
1458                 _region_relative_time_converter_double.set_origin_b(_region->position());
1459                 /* reset_width dependent_items() redisplays model */
1460
1461         }
1462
1463         if (what_changed.contains (ARDOUR::Properties::start) ||
1464             what_changed.contains (ARDOUR::Properties::position)) {
1465                 _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
1466         }
1467         /* catch end and start trim so we can update the view*/
1468         if (!what_changed.contains (ARDOUR::Properties::start) &&
1469             what_changed.contains (ARDOUR::Properties::length)) {
1470                 enable_display (true);
1471         } else if (what_changed.contains (ARDOUR::Properties::start) &&
1472             what_changed.contains (ARDOUR::Properties::length)) {
1473                 enable_display (true);
1474         }
1475 }
1476
1477 void
1478 MidiRegionView::reset_width_dependent_items (double pixel_width)
1479 {
1480         RegionView::reset_width_dependent_items(pixel_width);
1481
1482         if (_enable_display) {
1483                 redisplay_model();
1484         }
1485
1486         bool hide_all = false;
1487         PatchChanges::iterator x = _patch_changes.begin();
1488         if (x != _patch_changes.end()) {
1489                 hide_all = x->second->flag()->width() >= _pixel_width;
1490         }
1491
1492         if (hide_all) {
1493                 for (; x != _patch_changes.end(); ++x) {
1494                         x->second->hide();
1495                 }
1496         }
1497
1498         move_step_edit_cursor (_step_edit_cursor_position);
1499         set_step_edit_cursor_width (_step_edit_cursor_width);
1500 }
1501
1502 void
1503 MidiRegionView::set_height (double height)
1504 {
1505         double old_height = _height;
1506         RegionView::set_height(height);
1507
1508         apply_note_range (midi_stream_view()->lowest_note(),
1509                           midi_stream_view()->highest_note(),
1510                           height != old_height);
1511
1512         if (name_text) {
1513                 name_text->raise_to_top();
1514         }
1515
1516         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1517                 (*x).second->set_height (midi_stream_view()->contents_height());
1518         }
1519
1520         if (_step_edit_cursor) {
1521                 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
1522         }
1523 }
1524
1525
1526 /** Apply the current note range from the stream view
1527  * by repositioning/hiding notes as necessary
1528  */
1529 void
1530 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1531 {
1532         if (!_enable_display) {
1533                 return;
1534         }
1535
1536         if (!force && _current_range_min == min && _current_range_max == max) {
1537                 return;
1538         }
1539
1540         _current_range_min = min;
1541         _current_range_max = max;
1542
1543         redisplay_model ();
1544 }
1545
1546 GhostRegion*
1547 MidiRegionView::add_ghost (TimeAxisView& tv)
1548 {
1549         double unit_position = _region->position () / samples_per_pixel;
1550         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1551         MidiGhostRegion* ghost;
1552
1553         if (mtv && mtv->midi_view()) {
1554                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1555                    to allow having midi notes on top of note lines and waveforms.
1556                 */
1557                 ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
1558         } else {
1559                 ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
1560         }
1561
1562         ghost->set_colors ();
1563         ghost->set_height ();
1564         ghost->set_duration (_region->length() / samples_per_pixel);
1565
1566         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1567                 ghost->add_note(i->second);
1568         }
1569
1570         ghosts.push_back (ghost);
1571         enable_display (true);
1572         return ghost;
1573 }
1574
1575
1576 /** Begin tracking note state for successive calls to add_event
1577  */
1578 void
1579 MidiRegionView::begin_write()
1580 {
1581         if (_active_notes) {
1582                 delete[] _active_notes;
1583         }
1584         _active_notes = new Note*[128];
1585         for (unsigned i = 0; i < 128; ++i) {
1586                 _active_notes[i] = 0;
1587         }
1588 }
1589
1590
1591 /** Destroy note state for add_event
1592  */
1593 void
1594 MidiRegionView::end_write()
1595 {
1596         delete[] _active_notes;
1597         _active_notes = 0;
1598         _marked_for_selection.clear();
1599         _marked_for_velocity.clear();
1600 }
1601
1602
1603 /** Resolve an active MIDI note (while recording).
1604  */
1605 void
1606 MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
1607 {
1608         if (midi_view()->note_mode() != Sustained) {
1609                 return;
1610         }
1611
1612         if (_active_notes && _active_notes[note]) {
1613                 /* Set note length so update_note() works.  Note this is a local note
1614                    for recording, not from a model, so we can safely mess with it. */
1615                 _active_notes[note]->note()->set_length(
1616                         end_time - _active_notes[note]->note()->time());
1617
1618                 /* End time is relative to the region being recorded. */
1619                 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1620
1621                 _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
1622                 _active_notes[note]->set_outline_all ();
1623                 _active_notes[note] = 0;
1624         }
1625 }
1626
1627
1628 /** Extend active notes to rightmost edge of region (if length is changed)
1629  */
1630 void
1631 MidiRegionView::extend_active_notes()
1632 {
1633         if (!_active_notes) {
1634                 return;
1635         }
1636
1637         for (unsigned i = 0; i < 128; ++i) {
1638                 if (_active_notes[i]) {
1639                         _active_notes[i]->set_x1(
1640                                 trackview.editor().sample_to_pixel(_region->length()));
1641                 }
1642         }
1643 }
1644
1645 void
1646 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1647 {
1648         if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1649                 return;
1650         }
1651
1652         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1653
1654         if (!route_ui || !route_ui->midi_track()) {
1655                 return;
1656         }
1657
1658         NotePlayer* np = new NotePlayer (route_ui->midi_track ());
1659         np->add (note);
1660         np->play ();
1661
1662         /* NotePlayer deletes itself */
1663 }
1664
1665 void
1666 MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
1667 {
1668         const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
1669         start_playing_midi_chord(notes);
1670 }
1671
1672 void
1673 MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1674 {
1675         if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
1676                 return;
1677         }
1678
1679         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1680
1681         if (!route_ui || !route_ui->midi_track()) {
1682                 return;
1683         }
1684
1685         NotePlayer* player = new NotePlayer (route_ui->midi_track());
1686
1687         for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1688                 player->add (*n);
1689         }
1690
1691         player->play ();
1692 }
1693
1694
1695 bool
1696 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1697 {
1698         const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
1699
1700         /* must compare double explicitly as Beats::operator< rounds to ppqn */
1701         const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
1702                               note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
1703
1704         visible = (note->note() >= _current_range_min) &&
1705                 (note->note() <= _current_range_max);
1706
1707         return !outside;
1708 }
1709
1710 void
1711 MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
1712 {
1713         Note* sus = NULL;
1714         Hit*  hit = NULL;
1715         if ((sus = dynamic_cast<Note*>(note))) {
1716                 update_sustained(sus, update_ghost_regions);
1717         } else if ((hit = dynamic_cast<Hit*>(note))) {
1718                 update_hit(hit, update_ghost_regions);
1719         }
1720 }
1721
1722 /** Update a canvas note's size from its model note.
1723  *  @param ev Canvas note to update.
1724  *  @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1725  */
1726 void
1727 MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
1728 {
1729         TempoMap& map (trackview.session()->tempo_map());
1730         const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
1731         boost::shared_ptr<NoteType> note = ev->note();
1732
1733         const double session_source_start = _region->quarter_note() - mr->start_beats();
1734         const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
1735
1736         const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
1737         double x1;
1738         const double y0 = 1 + floor(note_to_y(note->note()));
1739         double y1;
1740
1741         /* trim note display to not overlap the end of its region */
1742         if (note->length().to_double() > 0.0) {
1743                 double note_end_time = note->end_time().to_double();
1744
1745                 if (note_end_time > mr->start_beats() + mr->length_beats()) {
1746                         note_end_time = mr->start_beats() + mr->length_beats();
1747                 }
1748
1749                 const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
1750
1751                 x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
1752         } else {
1753                 x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
1754         }
1755
1756         y1 = y0 + std::max(1., floor(note_height()) - 1);
1757
1758         ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
1759
1760         if (!note->length()) {
1761                 if (_active_notes && note->note() < 128) {
1762                         Note* const old_rect = _active_notes[note->note()];
1763                         if (old_rect) {
1764                                 /* There is an active note on this key, so we have a stuck
1765                                    note.  Finish the old rectangle here. */
1766                                 old_rect->set_x1 (x1);
1767                                 old_rect->set_outline_all ();
1768                         }
1769                         _active_notes[note->note()] = ev;
1770                 }
1771                 /* outline all but right edge */
1772                 ev->set_outline_what (ArdourCanvas::Rectangle::What (
1773                                               ArdourCanvas::Rectangle::TOP|
1774                                               ArdourCanvas::Rectangle::LEFT|
1775                                               ArdourCanvas::Rectangle::BOTTOM));
1776         } else {
1777                 /* outline all edges */
1778                 ev->set_outline_all ();
1779         }
1780
1781         // Update color in case velocity has changed
1782         const uint32_t base_col = ev->base_color();
1783         ev->set_fill_color(base_col);
1784         ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1785
1786 }
1787
1788 void
1789 MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
1790 {
1791         boost::shared_ptr<NoteType> note = ev->note();
1792
1793         const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
1794         const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
1795
1796         const double x = trackview.editor().sample_to_pixel(note_start_frames);
1797         const double diamond_size = std::max(1., floor(note_height()) - 2.);
1798         const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
1799
1800         // see DnD note in MidiRegionView::apply_note_range() above
1801         if (y <= 0 || y >= _height) {
1802                 ev->hide();
1803         } else {
1804                 ev->show();
1805         }
1806
1807         ev->set_position (ArdourCanvas::Duple (x, y));
1808         ev->set_height (diamond_size);
1809
1810         // Update color in case velocity has changed
1811         const uint32_t base_col = ev->base_color();
1812         ev->set_fill_color(base_col);
1813         ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
1814
1815 }
1816
1817 /** Add a MIDI note to the view (with length).
1818  *
1819  * If in sustained mode, notes with length 0 will be considered active
1820  * notes, and resolve_note should be called when the corresponding note off
1821  * event arrives, to properly display the note.
1822  */
1823 NoteBase*
1824 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1825 {
1826         NoteBase* event = 0;
1827
1828         if (midi_view()->note_mode() == Sustained) {
1829
1830                 Note* ev_rect = new Note (*this, _note_group, note);
1831
1832                 update_sustained (ev_rect);
1833
1834                 event = ev_rect;
1835
1836         } else if (midi_view()->note_mode() == Percussive) {
1837
1838                 const double diamond_size = std::max(1., floor(note_height()) - 2.);
1839
1840                 Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
1841
1842                 update_hit (ev_diamond);
1843
1844                 event = ev_diamond;
1845
1846         } else {
1847                 event = 0;
1848         }
1849
1850         if (event) {
1851                 MidiGhostRegion* gr;
1852
1853                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1854                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1855                                 gr->add_note(event);
1856                         }
1857                 }
1858
1859                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1860                         note_selected(event, true);
1861                 }
1862
1863                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1864                         event->show_velocity();
1865                 }
1866
1867                 event->on_channel_selection_change (get_selected_channels());
1868                 _events.insert (make_pair (event->note(), event));
1869
1870                 if (visible) {
1871                         event->show();
1872                 } else {
1873                         event->hide ();
1874                 }
1875         }
1876
1877         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1878         MidiStreamView* const view = mtv->midi_view();
1879
1880         view->update_note_range (note->note());
1881         return event;
1882 }
1883
1884 void
1885 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1886                                Evoral::Beats pos, Evoral::Beats len)
1887 {
1888         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1889
1890         /* potentially extend region to hold new note */
1891
1892         framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1893         framepos_t region_end = _region->last_frame();
1894
1895         if (end_frame > region_end) {
1896                 /* XX sets length in beats from audio space. make musical */
1897                 _region->set_length (end_frame - _region->position(), 0);
1898         }
1899
1900         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1901         MidiStreamView* const view = mtv->midi_view();
1902
1903         view->update_note_range(new_note->note());
1904
1905         _marked_for_selection.clear ();
1906
1907         start_note_diff_command (_("step add"));
1908
1909         clear_editor_note_selection ();
1910         note_diff_add_note (new_note, true, false);
1911
1912         apply_diff();
1913
1914         // last_step_edit_note = new_note;
1915 }
1916
1917 void
1918 MidiRegionView::step_sustain (Evoral::Beats beats)
1919 {
1920         change_note_lengths (false, false, beats, false, true);
1921 }
1922
1923 /** Add a new patch change flag to the canvas.
1924  * @param patch the patch change to add
1925  * @param the text to display in the flag
1926  * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
1927  */
1928 void
1929 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
1930 {
1931         framecnt_t region_frames = source_beats_to_region_frames (patch->time());
1932         const double x = trackview.editor().sample_to_pixel (region_frames);
1933
1934         double const height = midi_stream_view()->contents_height();
1935
1936         // CAIROCANVAS: active_channel info removed from PatcChange constructor
1937         // so we need to do something more sophisticated to keep its color
1938         // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
1939         // up to date.
1940         boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
1941                 new PatchChange(*this, group,
1942                                 displaytext,
1943                                 height,
1944                                 x, 1.0,
1945                                 instrument_info(),
1946                                 patch,
1947                                 _patch_change_outline,
1948                                 _patch_change_fill)
1949                 );
1950
1951         if (patch_change->item().width() < _pixel_width) {
1952                 // Show unless patch change is beyond the region bounds
1953                 if (region_frames < 0 || region_frames >= _region->length()) {
1954                         patch_change->hide();
1955                 } else {
1956                         patch_change->show();
1957                 }
1958         } else {
1959                 patch_change->hide ();
1960         }
1961
1962         _patch_changes.insert (make_pair (patch, patch_change));
1963 }
1964
1965 void
1966 MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
1967 {
1968         /* remove the canvas item */
1969         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1970                 if (x->second->patch() == pc->patch()) {
1971                         _patch_changes.erase (x);
1972                         break;
1973                 }
1974         }
1975 }
1976
1977 MIDI::Name::PatchPrimaryKey
1978 MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
1979 {
1980         return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
1981 }
1982
1983 /// Return true iff @p pc applies to the given time on the given channel.
1984 static bool
1985 patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
1986 {
1987         return pc->time() <= time && pc->channel() == channel;
1988 }
1989
1990 void
1991 MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
1992 {
1993         // The earliest event not before time
1994         MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1995
1996         // Go backwards until we find the latest PC for this channel, or the start
1997         while (i != _model->patch_changes().begin() &&
1998                (i == _model->patch_changes().end() ||
1999                 !patch_applies(*i, time, channel))) {
2000                 --i;
2001         }
2002
2003         if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
2004                 key.set_bank((*i)->bank());
2005                 key.set_program((*i)->program ());
2006         } else {
2007                 key.set_bank(0);
2008                 key.set_program(0);
2009         }
2010 }
2011
2012 void
2013 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
2014 {
2015         string name = _("alter patch change");
2016         trackview.editor().begin_reversible_command (name);
2017
2018         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2019
2020         if (pc.patch()->program() != new_patch.program()) {
2021                 c->change_program (pc.patch (), new_patch.program());
2022         }
2023
2024         int const new_bank = new_patch.bank();
2025         if (pc.patch()->bank() != new_bank) {
2026                 c->change_bank (pc.patch (), new_bank);
2027         }
2028
2029         _model->apply_command (*trackview.session(), c);
2030         trackview.editor().commit_reversible_command ();
2031
2032         remove_canvas_patch_change (&pc);
2033         display_patch_changes ();
2034 }
2035
2036 void
2037 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
2038 {
2039         string name = _("alter patch change");
2040         trackview.editor().begin_reversible_command (name);
2041         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2042
2043         if (old_change->time() != new_change.time()) {
2044                 c->change_time (old_change, new_change.time());
2045         }
2046
2047         if (old_change->channel() != new_change.channel()) {
2048                 c->change_channel (old_change, new_change.channel());
2049         }
2050
2051         if (old_change->program() != new_change.program()) {
2052                 c->change_program (old_change, new_change.program());
2053         }
2054
2055         if (old_change->bank() != new_change.bank()) {
2056                 c->change_bank (old_change, new_change.bank());
2057         }
2058
2059         _model->apply_command (*trackview.session(), c);
2060         trackview.editor().commit_reversible_command ();
2061
2062         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
2063                 if (x->second->patch() == old_change) {
2064                         _patch_changes.erase (x);
2065                         break;
2066                 }
2067         }
2068
2069         display_patch_changes ();
2070 }
2071
2072 /** Add a patch change to the region.
2073  *  @param t Time in frames relative to region position
2074  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
2075  *  MidiTimeAxisView::get_channel_for_add())
2076  */
2077 void
2078 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
2079 {
2080         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
2081         string name = _("add patch change");
2082
2083         trackview.editor().begin_reversible_command (name);
2084         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
2085         c->add (MidiModel::PatchChangePtr (
2086                         new Evoral::PatchChange<Evoral::Beats> (
2087                                 absolute_frames_to_source_beats (_region->position() + t),
2088                                 mtv->get_channel_for_add(), patch.program(), patch.bank()
2089                                 )
2090                         )
2091                 );
2092
2093         _model->apply_command (*trackview.session(), c);
2094         trackview.editor().commit_reversible_command ();
2095
2096         display_patch_changes ();
2097 }
2098
2099 void
2100 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
2101 {
2102         trackview.editor().begin_reversible_command (_("move patch change"));
2103         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
2104         c->change_time (pc.patch (), t);
2105         _model->apply_command (*trackview.session(), c);
2106         trackview.editor().commit_reversible_command ();
2107
2108         display_patch_changes ();
2109 }
2110
2111 void
2112 MidiRegionView::delete_patch_change (PatchChange* pc)
2113 {
2114         trackview.editor().begin_reversible_command (_("delete patch change"));
2115
2116         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
2117         c->remove (pc->patch ());
2118         _model->apply_command (*trackview.session(), c);
2119         trackview.editor().commit_reversible_command ();
2120
2121         remove_canvas_patch_change (pc);
2122         display_patch_changes ();
2123 }
2124
2125 void
2126 MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
2127 {
2128         MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
2129         if (bank) {
2130                 key.set_bank(key.bank() + delta);
2131         } else {
2132                 key.set_program(key.program() + delta);
2133         }
2134         change_patch_change(patch, key);
2135 }
2136
2137 void
2138 MidiRegionView::note_deleted (NoteBase* cne)
2139 {
2140         if (_entered_note && cne == _entered_note) {
2141                 _entered_note = 0;
2142         }
2143
2144         if (_selection.empty()) {
2145                 return;
2146         }
2147
2148         _selection.erase (cne);
2149 }
2150
2151 void
2152 MidiRegionView::delete_selection()
2153 {
2154         if (_selection.empty()) {
2155                 return;
2156         }
2157
2158         if (trackview.editor().drags()->active()) {
2159                 return;
2160         }
2161
2162         start_note_diff_command (_("delete selection"));
2163
2164         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2165                 if ((*i)->selected()) {
2166                         _note_diff_command->remove((*i)->note());
2167                 }
2168         }
2169
2170         _selection.clear();
2171
2172         apply_diff ();
2173
2174         hide_verbose_cursor ();
2175 }
2176
2177 void
2178 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
2179 {
2180         start_note_diff_command (_("delete note"));
2181         _note_diff_command->remove (n);
2182         apply_diff ();
2183
2184         hide_verbose_cursor ();
2185 }
2186
2187 void
2188 MidiRegionView::clear_editor_note_selection ()
2189 {
2190         DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
2191         PublicEditor& editor(trackview.editor());
2192         editor.get_selection().clear_midi_notes();
2193 }
2194
2195 void
2196 MidiRegionView::clear_selection ()
2197 {
2198         clear_selection_internal();
2199         PublicEditor& editor(trackview.editor());
2200         editor.get_selection().remove(this);
2201 }
2202
2203 void
2204 MidiRegionView::clear_selection_internal ()
2205 {
2206         DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
2207
2208         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2209                 (*i)->set_selected(false);
2210                 (*i)->hide_velocity();
2211         }
2212         _selection.clear();
2213
2214         if (_entered) {
2215                 // Clearing selection entirely, ungrab keyboard
2216                 Keyboard::magic_widget_drop_focus();
2217                 _grabbed_keyboard = false;
2218         }
2219 }
2220
2221 void
2222 MidiRegionView::unique_select(NoteBase* ev)
2223 {
2224         clear_editor_note_selection();
2225         add_to_selection(ev);
2226 }
2227
2228 void
2229 MidiRegionView::select_all_notes ()
2230 {
2231         clear_editor_note_selection ();
2232
2233         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2234                 add_to_selection (i->second);
2235         }
2236 }
2237
2238 void
2239 MidiRegionView::select_range (framepos_t start, framepos_t end)
2240 {
2241         clear_editor_note_selection ();
2242
2243         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2244                 framepos_t t = source_beats_to_absolute_frames(i->first->time());
2245                 if (t >= start && t <= end) {
2246                         add_to_selection (i->second);
2247                 }
2248         }
2249 }
2250
2251 void
2252 MidiRegionView::invert_selection ()
2253 {
2254         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2255                 if (i->second->selected()) {
2256                         remove_from_selection(i->second);
2257                 } else {
2258                         add_to_selection (i->second);
2259                 }
2260         }
2261 }
2262
2263 /** Used for selection undo/redo.
2264     The requested notes most likely won't exist in the view until the next model redisplay.
2265 */
2266 void
2267 MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
2268 {
2269         NoteBase* cne;
2270         list<Evoral::event_id_t>::iterator n;
2271
2272         for (n = notes.begin(); n != notes.end(); ++n) {
2273                 if ((cne = find_canvas_note(*n)) != 0) {
2274                         add_to_selection (cne);
2275                 } else {
2276                         _pending_note_selection.insert(*n);
2277                 }
2278         }
2279 }
2280
2281 void
2282 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2283 {
2284         bool have_selection = !_selection.empty();
2285         uint8_t low_note = 127;
2286         uint8_t high_note = 0;
2287         MidiModel::Notes& notes (_model->notes());
2288         _optimization_iterator = _events.begin();
2289
2290         if (extend && !have_selection) {
2291                 extend = false;
2292         }
2293
2294         /* scan existing selection to get note range */
2295
2296         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2297                 if ((*i)->note()->note() < low_note) {
2298                         low_note = (*i)->note()->note();
2299                 }
2300                 if ((*i)->note()->note() > high_note) {
2301                         high_note = (*i)->note()->note();
2302                 }
2303         }
2304
2305         if (!add) {
2306                 clear_editor_note_selection ();
2307
2308                 if (!extend && (low_note == high_note) && (high_note == notenum)) {
2309                         /* only note previously selected is the one we are
2310                          * reselecting. treat this as cancelling the selection.
2311                          */
2312                         return;
2313                 }
2314         }
2315
2316         if (extend) {
2317                 low_note = min (low_note, notenum);
2318                 high_note = max (high_note, notenum);
2319         }
2320
2321         _no_sound_notes = true;
2322
2323         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2324
2325                 boost::shared_ptr<NoteType> note (*n);
2326                 NoteBase* cne;
2327                 bool select = false;
2328
2329                 if (((1 << note->channel()) & channel_mask) != 0) {
2330                         if (extend) {
2331                                 if ((note->note() >= low_note && note->note() <= high_note)) {
2332                                         select = true;
2333                                 }
2334                         } else if (note->note() == notenum) {
2335                                 select = true;
2336                         }
2337                 }
2338
2339                 if (select) {
2340                         if ((cne = find_canvas_note (note)) != 0) {
2341                                 // extend is false because we've taken care of it,
2342                                 // since it extends by time range, not pitch.
2343                                 note_selected (cne, add, false);
2344                         }
2345                 }
2346
2347                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2348
2349         }
2350
2351         _no_sound_notes = false;
2352 }
2353
2354 void
2355 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2356 {
2357         MidiModel::Notes& notes (_model->notes());
2358         _optimization_iterator = _events.begin();
2359
2360         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2361
2362                 boost::shared_ptr<NoteType> note (*n);
2363                 NoteBase* cne;
2364
2365                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2366                         if ((cne = find_canvas_note (note)) != 0) {
2367                                 if (cne->selected()) {
2368                                         note_deselected (cne);
2369                                 } else {
2370                                         note_selected (cne, true, false);
2371                                 }
2372                         }
2373                 }
2374         }
2375 }
2376
2377 void
2378 MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
2379 {
2380         if (!add) {
2381                 clear_editor_note_selection();
2382                 add_to_selection (ev);
2383         }
2384
2385         if (!extend) {
2386
2387                 if (!ev->selected()) {
2388                         add_to_selection (ev);
2389                 }
2390
2391         } else {
2392                 /* find end of latest note selected, select all between that and the start of "ev" */
2393
2394                 Evoral::Beats earliest = Evoral::MaxBeats;
2395                 Evoral::Beats latest   = Evoral::Beats();
2396
2397                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2398                         if ((*i)->note()->end_time() > latest) {
2399                                 latest = (*i)->note()->end_time();
2400                         }
2401                         if ((*i)->note()->time() < earliest) {
2402                                 earliest = (*i)->note()->time();
2403                         }
2404                 }
2405
2406                 if (ev->note()->end_time() > latest) {
2407                         latest = ev->note()->end_time();
2408                 }
2409
2410                 if (ev->note()->time() < earliest) {
2411                         earliest = ev->note()->time();
2412                 }
2413
2414                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2415
2416                         /* find notes entirely within OR spanning the earliest..latest range */
2417
2418                         if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
2419                             (i->first->time() <= earliest && i->first->end_time() >= latest)) {
2420                                 add_to_selection (i->second);
2421                         }
2422                 }
2423         }
2424 }
2425
2426 void
2427 MidiRegionView::note_deselected(NoteBase* ev)
2428 {
2429         remove_from_selection (ev);
2430 }
2431
2432 void
2433 MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
2434 {
2435         PublicEditor& editor = trackview.editor();
2436
2437         // Convert to local coordinates
2438         const framepos_t p  = _region->position();
2439         const double     y  = midi_view()->y_position();
2440         const double     x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
2441         const double     x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
2442         const double     y0 = max(0.0, gy0 - y);
2443         const double     y1 = max(0.0, gy1 - y);
2444
2445         // TODO: Make this faster by storing the last updated selection rect, and only
2446         // adjusting things that are in the area that appears/disappeared.
2447         // We probably need a tree to be able to find events in O(log(n)) time.
2448
2449         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2450                 if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
2451                         // Rectangles intersect
2452                         if (!i->second->selected()) {
2453                                 add_to_selection (i->second);
2454                         }
2455                 } else if (i->second->selected() && !extend) {
2456                         // Rectangles do not intersect
2457                         remove_from_selection (i->second);
2458                 }
2459         }
2460
2461         typedef RouteTimeAxisView::AutomationTracks ATracks;
2462         typedef std::list<Selectable*>              Selectables;
2463
2464         /* Add control points to selection. */
2465         const ATracks& atracks = midi_view()->automation_tracks();
2466         Selectables    selectables;
2467         editor.get_selection().clear_points();
2468         for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
2469                 a->second->get_selectables(start, end, gy0, gy1, selectables);
2470                 for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
2471                         ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
2472                         if (cp) {
2473                                 editor.get_selection().add(cp);
2474                         }
2475                 }
2476                 a->second->set_selected_points(editor.get_selection().points);
2477         }
2478 }
2479
2480 void
2481 MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
2482 {
2483         if (y1 > y2) {
2484                 swap (y1, y2);
2485         }
2486
2487         // TODO: Make this faster by storing the last updated selection rect, and only
2488         // adjusting things that are in the area that appears/disappeared.
2489         // We probably need a tree to be able to find events in O(log(n)) time.
2490
2491         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2492                 if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
2493                         // within y- (note-) range
2494                         if (!i->second->selected()) {
2495                                 add_to_selection (i->second);
2496                         }
2497                 } else if (i->second->selected() && !extend) {
2498                         remove_from_selection (i->second);
2499                 }
2500         }
2501 }
2502
2503 void
2504 MidiRegionView::remove_from_selection (NoteBase* ev)
2505 {
2506         Selection::iterator i = _selection.find (ev);
2507
2508         if (i != _selection.end()) {
2509                 _selection.erase (i);
2510                 if (_selection.empty() && _grabbed_keyboard) {
2511                         // Ungrab keyboard
2512                         Keyboard::magic_widget_drop_focus();
2513                         _grabbed_keyboard = false;
2514                 }
2515         }
2516
2517         ev->set_selected (false);
2518         ev->hide_velocity ();
2519
2520         if (_selection.empty()) {
2521                 PublicEditor& editor (trackview.editor());
2522                 editor.get_selection().remove (this);
2523         }
2524 }
2525
2526 void
2527 MidiRegionView::add_to_selection (NoteBase* ev)
2528 {
2529         const bool selection_was_empty = _selection.empty();
2530
2531         if (_selection.insert (ev).second) {
2532                 ev->set_selected (true);
2533                 start_playing_midi_note ((ev)->note());
2534                 if (selection_was_empty && _entered) {
2535                         // Grab keyboard for moving notes with arrow keys
2536                         Keyboard::magic_widget_grab_focus();
2537                         _grabbed_keyboard = true;
2538                 }
2539         }
2540
2541         if (selection_was_empty) {
2542                 PublicEditor& editor (trackview.editor());
2543                 editor.get_selection().add (this);
2544         }
2545 }
2546
2547 void
2548 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2549 {
2550         typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2551         PossibleChord to_play;
2552         Evoral::Beats earliest = Evoral::MaxBeats;
2553
2554         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2555                 if ((*i)->note()->time() < earliest) {
2556                         earliest = (*i)->note()->time();
2557                 }
2558         }
2559
2560         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2561                 if ((*i)->note()->time() == earliest) {
2562                         to_play.push_back ((*i)->note());
2563                 }
2564                 (*i)->move_event(dx, dy);
2565         }
2566
2567         if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
2568
2569                 if (to_play.size() > 1) {
2570
2571                         PossibleChord shifted;
2572
2573                         for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2574                                 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2575                                 moved_note->set_note (moved_note->note() + cumulative_dy);
2576                                 shifted.push_back (moved_note);
2577                         }
2578
2579                         start_playing_midi_chord (shifted);
2580
2581                 } else if (!to_play.empty()) {
2582
2583                         boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2584                         moved_note->set_note (moved_note->note() + cumulative_dy);
2585                         start_playing_midi_note (moved_note);
2586                 }
2587         }
2588 }
2589
2590 void
2591 MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote)
2592 {
2593         uint8_t lowest_note_in_selection  = 127;
2594         uint8_t highest_note_in_selection = 0;
2595         uint8_t highest_note_difference   = 0;
2596
2597         // find highest and lowest notes first
2598
2599         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2600                 uint8_t pitch = (*i)->note()->note();
2601                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2602                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2603         }
2604
2605         /*
2606           cerr << "dnote: " << (int) dnote << endl;
2607           cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2608           << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2609           cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2610           << int(highest_note_in_selection) << endl;
2611           cerr << "selection size: " << _selection.size() << endl;
2612           cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2613         */
2614
2615         // Make sure the note pitch does not exceed the MIDI standard range
2616         if (highest_note_in_selection + dnote > 127) {
2617                 highest_note_difference = highest_note_in_selection - 127;
2618         }
2619         TempoMap& map (trackview.session()->tempo_map());
2620
2621         start_note_diff_command (_("move notes"));
2622
2623         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2624
2625                 double const start_qn = _region->quarter_note() - midi_region()->start_beats();
2626                 framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
2627                 Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
2628                 if (new_time < 0) {
2629                         continue;
2630                 }
2631
2632                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2633
2634                 uint8_t original_pitch = (*i)->note()->note();
2635                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2636
2637                 // keep notes in standard midi range
2638                 clamp_to_0_127(new_pitch);
2639
2640                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2641                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2642
2643                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2644         }
2645
2646         apply_diff();
2647
2648         // care about notes being moved beyond the upper/lower bounds on the canvas
2649         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2650             highest_note_in_selection > midi_stream_view()->highest_note()) {
2651                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2652         }
2653 }
2654
2655 /** @param x Pixel relative to the region position.
2656  *  @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
2657  *  Used for inverting the snap logic with key modifiers and snap delta calculation.
2658  *  @return Snapped frame relative to the region position.
2659  */
2660 framepos_t
2661 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
2662 {
2663         PublicEditor& editor (trackview.editor());
2664         return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
2665 }
2666
2667 /** @param x Pixel relative to the region position.
2668  *  @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
2669  *  @return Snapped pixel relative to the region position.
2670  */
2671 double
2672 MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
2673 {
2674         return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
2675 }
2676
2677 double
2678 MidiRegionView::get_position_pixels()
2679 {
2680         framepos_t region_frame = get_position();
2681         return trackview.editor().sample_to_pixel(region_frame);
2682 }
2683
2684 double
2685 MidiRegionView::get_end_position_pixels()
2686 {
2687         framepos_t frame = get_position() + get_duration ();
2688         return trackview.editor().sample_to_pixel(frame);
2689 }
2690
2691 framepos_t
2692 MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
2693 {
2694         /* the time converter will return the frame corresponding to `beats'
2695            relative to the start of the source. The start of the source
2696            is an implied position given by region->position - region->start
2697         */
2698         const framepos_t source_start = _region->position() - _region->start();
2699         return  source_start +  _source_relative_time_converter.to (beats);
2700 }
2701
2702 Evoral::Beats
2703 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2704 {
2705         /* the `frames' argument needs to be converted into a frame count
2706            relative to the start of the source before being passed in to the
2707            converter.
2708         */
2709         const framepos_t source_start = _region->position() - _region->start();
2710         return  _source_relative_time_converter.from (frames - source_start);
2711 }
2712
2713 framepos_t
2714 MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
2715 {
2716         return _region_relative_time_converter.to(beats);
2717 }
2718
2719 Evoral::Beats
2720 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2721 {
2722         return _region_relative_time_converter.from(frames);
2723 }
2724
2725 double
2726 MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
2727 {
2728         return _region_relative_time_converter_double.from(frames);
2729 }
2730
2731 void
2732 MidiRegionView::begin_resizing (bool /*at_front*/)
2733 {
2734         _resize_data.clear();
2735
2736         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2737                 Note *note = dynamic_cast<Note*> (*i);
2738
2739                 // only insert CanvasNotes into the map
2740                 if (note) {
2741                         NoteResizeData *resize_data = new NoteResizeData();
2742                         resize_data->note = note;
2743
2744                         // create a new SimpleRect from the note which will be the resize preview
2745                         ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
2746                                                                                             ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
2747
2748                         // calculate the colors: get the color settings
2749                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2750                                 UIConfiguration::instance().color ("midi note selected"),
2751                                 128);
2752
2753                         // make the resize preview notes more transparent and bright
2754                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2755
2756                         // calculate color based on note velocity
2757                         resize_rect->set_fill_color (UINT_INTERPOLATE(
2758                                 NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
2759                                 fill_color,
2760                                 0.85));
2761
2762                         resize_rect->set_outline_color (NoteBase::calculate_outline (
2763                                                                 UIConfiguration::instance().color ("midi note selected")));
2764
2765                         resize_data->resize_rect = resize_rect;
2766                         _resize_data.push_back(resize_data);
2767                 }
2768         }
2769 }
2770
2771 /** Update resizing notes while user drags.
2772  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2773  * @param at_front which end of the note (true == note on, false == note off)
2774  * @param delta_x change in mouse position since the start of the drag
2775  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2776  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2777  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2778  * as the \a primary note.
2779  * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
2780  * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
2781  */
2782 void
2783 MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2784 {
2785         TempoMap& tmap (trackview.session()->tempo_map());
2786         bool cursor_set = false;
2787         bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2788
2789         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2790                 ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
2791                 Note* canvas_note = (*i)->note;
2792                 double current_x;
2793
2794                 if (at_front) {
2795                         if (relative) {
2796                                 current_x = canvas_note->x0() + delta_x + snap_delta;
2797                         } else {
2798                                 current_x = primary->x0() + delta_x + snap_delta;
2799                         }
2800                 } else {
2801                         if (relative) {
2802                                 current_x = canvas_note->x1() + delta_x + snap_delta;
2803                         } else {
2804                                 current_x = primary->x1() + delta_x + snap_delta;
2805                         }
2806                 }
2807
2808                 if (current_x < 0) {
2809                         // This works even with snapping because RegionView::snap_frame_to_frame()
2810                         // snaps forward if the snapped sample is before the beginning of the region
2811                         current_x = 0;
2812                 }
2813                 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2814                         current_x = trackview.editor().sample_to_pixel(_region->length());
2815                 }
2816
2817                 if (at_front) {
2818                         if (with_snap) {
2819                                 resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2820                         } else {
2821                                 resize_rect->set_x0 (current_x - snap_delta);
2822                         }
2823                         resize_rect->set_x1 (canvas_note->x1());
2824                 } else {
2825                         if (with_snap) {
2826                                 resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
2827                         } else {
2828                                 resize_rect->set_x1 (current_x - snap_delta);
2829                         }
2830                         resize_rect->set_x0 (canvas_note->x0());
2831                 }
2832
2833                 if (!cursor_set) {
2834                         /* Convert snap delta from pixels to beats. */
2835                         framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2836                         double snap_delta_beats = 0.0;
2837                         int sign = 1;
2838
2839                         /* negative beat offsets aren't allowed */
2840                         if (snap_delta_samps > 0) {
2841                                 snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2842                         } else if (snap_delta_samps < 0) {
2843                                 snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2844                                 sign = -1;
2845                         }
2846
2847                         double  snapped_x;
2848                         int32_t divisions = 0;
2849
2850                         if (with_snap) {
2851                                 snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
2852                                 divisions = trackview.editor().get_grid_music_divisions (0);
2853                         } else {
2854                                 snapped_x = trackview.editor ().pixel_to_sample (current_x);
2855                         }
2856                         const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
2857                                                                      - midi_region()->beat()) + midi_region()->start_beats();
2858
2859                         Evoral::Beats len         = Evoral::Beats();
2860
2861                         if (at_front) {
2862                                 if (beats < canvas_note->note()->end_time()) {
2863                                         len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
2864                                         len += canvas_note->note()->length();
2865                                 }
2866                         } else {
2867                                 if (beats >= canvas_note->note()->time()) {
2868                                         len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
2869                                 }
2870                         }
2871
2872                         len = std::max(Evoral::Beats(1 / 512.0), len);
2873
2874                         char buf[16];
2875                         snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
2876                         show_verbose_cursor (buf, 0, 0);
2877
2878                         cursor_set = true;
2879                 }
2880
2881         }
2882 }
2883
2884
2885 /** Finish resizing notes when the user releases the mouse button.
2886  *  Parameters the same as for \a update_resizing().
2887  */
2888 void
2889 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
2890 {
2891         _note_diff_command = _model->new_note_diff_command (_("resize notes"));
2892         TempoMap& tmap (trackview.session()->tempo_map());
2893
2894         /* XX why doesn't snap_pixel_to_sample() handle this properly? */
2895         bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
2896
2897         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2898                 Note*  canvas_note = (*i)->note;
2899                 ArdourCanvas::Rectangle*  resize_rect = (*i)->resize_rect;
2900
2901                 /* Get the new x position for this resize, which is in pixels relative
2902                  * to the region position.
2903                  */
2904
2905                 double current_x;
2906
2907                 if (at_front) {
2908                         if (relative) {
2909                                 current_x = canvas_note->x0() + delta_x + snap_delta;
2910                         } else {
2911                                 current_x = primary->x0() + delta_x + snap_delta;
2912                         }
2913                 } else {
2914                         if (relative) {
2915                                 current_x = canvas_note->x1() + delta_x + snap_delta;
2916                         } else {
2917                                 current_x = primary->x1() + delta_x + snap_delta;
2918                         }
2919                 }
2920
2921                 if (current_x < 0) {
2922                         current_x = 0;
2923                 }
2924                 if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
2925                         current_x = trackview.editor().sample_to_pixel(_region->length());
2926                 }
2927
2928                 /* Convert snap delta from pixels to beats with sign. */
2929                 framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
2930                 double snap_delta_beats = 0.0;
2931                 int sign = 1;
2932
2933                 if (snap_delta_samps > 0) {
2934                         snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
2935                 } else if (snap_delta_samps < 0) {
2936                         snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
2937                         sign = -1;
2938                 }
2939
2940                 uint32_t divisions = 0;
2941                 /* Convert the new x position to a frame within the source */
2942                 framepos_t current_fr;
2943                 if (with_snap) {
2944                         current_fr = snap_pixel_to_sample (current_x, ensure_snap);
2945                         divisions = trackview.editor().get_grid_music_divisions (0);
2946                 } else {
2947                         current_fr = trackview.editor().pixel_to_sample (current_x);
2948                 }
2949
2950                 /* and then to beats */
2951                 const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
2952                 const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
2953                 const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
2954
2955                 if (at_front && x_beats < canvas_note->note()->end_time()) {
2956                         note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
2957                         Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
2958                         len += canvas_note->note()->length();
2959
2960                         if (!!len) {
2961                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2962                         }
2963                 }
2964
2965                 if (!at_front) {
2966                         Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
2967                                                      x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
2968                         note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2969                 }
2970
2971                 delete resize_rect;
2972                 delete (*i);
2973         }
2974
2975         _resize_data.clear();
2976         apply_diff(true);
2977 }
2978
2979 void
2980 MidiRegionView::abort_resizing ()
2981 {
2982         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2983                 delete (*i)->resize_rect;
2984                 delete *i;
2985         }
2986
2987         _resize_data.clear ();
2988 }
2989
2990 void
2991 MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
2992 {
2993         uint8_t new_velocity;
2994
2995         if (relative) {
2996                 new_velocity = event->note()->velocity() + velocity;
2997                 clamp_to_0_127(new_velocity);
2998         } else {
2999                 new_velocity = velocity;
3000         }
3001
3002         event->set_selected (event->selected()); // change color
3003
3004         note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
3005 }
3006
3007 void
3008 MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
3009 {
3010         uint8_t new_note;
3011
3012         if (relative) {
3013                 new_note = event->note()->note() + note;
3014         } else {
3015                 new_note = note;
3016         }
3017
3018         clamp_to_0_127 (new_note);
3019         note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
3020 }
3021
3022 void
3023 MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
3024 {
3025         bool change_start = false;
3026         bool change_length = false;
3027         Evoral::Beats new_start;
3028         Evoral::Beats new_length;
3029
3030         /* NOTE: the semantics of the two delta arguments are slightly subtle:
3031
3032            front_delta: if positive - move the start of the note later in time (shortening it)
3033            if negative - move the start of the note earlier in time (lengthening it)
3034
3035            end_delta:   if positive - move the end of the note later in time (lengthening it)
3036            if negative - move the end of the note earlier in time (shortening it)
3037         */
3038
3039         if (!!front_delta) {
3040                 if (front_delta < 0) {
3041
3042                         if (event->note()->time() < -front_delta) {
3043                                 new_start = Evoral::Beats();
3044                         } else {
3045                                 new_start = event->note()->time() + front_delta; // moves earlier
3046                         }
3047
3048                         /* start moved toward zero, so move the end point out to where it used to be.
3049                            Note that front_delta is negative, so this increases the length.
3050                         */
3051
3052                         new_length = event->note()->length() - front_delta;
3053                         change_start = true;
3054                         change_length = true;
3055
3056                 } else {
3057
3058                         Evoral::Beats new_pos = event->note()->time() + front_delta;
3059
3060                         if (new_pos < event->note()->end_time()) {
3061                                 new_start = event->note()->time() + front_delta;
3062                                 /* start moved toward the end, so move the end point back to where it used to be */
3063                                 new_length = event->note()->length() - front_delta;
3064                                 change_start = true;
3065                                 change_length = true;
3066                         }
3067                 }
3068
3069         }
3070
3071         if (!!end_delta) {
3072                 bool can_change = true;
3073                 if (end_delta < 0) {
3074                         if (event->note()->length() < -end_delta) {
3075                                 can_change = false;
3076                         }
3077                 }
3078
3079                 if (can_change) {
3080                         new_length = event->note()->length() + end_delta;
3081                         change_length = true;
3082                 }
3083         }
3084
3085         if (change_start) {
3086                 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
3087         }
3088
3089         if (change_length) {
3090                 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
3091         }
3092 }
3093
3094 void
3095 MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
3096 {
3097         uint8_t new_channel;
3098
3099         if (relative) {
3100                 if (chn < 0.0) {
3101                         if (event->note()->channel() < -chn) {
3102                                 new_channel = 0;
3103                         } else {
3104                                 new_channel = event->note()->channel() + chn;
3105                         }
3106                 } else {
3107                         new_channel = event->note()->channel() + chn;
3108                 }
3109         } else {
3110                 new_channel = (uint8_t) chn;
3111         }
3112
3113         note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
3114 }
3115
3116 void
3117 MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
3118 {
3119         Evoral::Beats new_time;
3120
3121         if (relative) {
3122                 if (delta < 0.0) {
3123                         if (event->note()->time() < -delta) {
3124                                 new_time = Evoral::Beats();
3125                         } else {
3126                                 new_time = event->note()->time() + delta;
3127                         }
3128                 } else {
3129                         new_time = event->note()->time() + delta;
3130                 }
3131         } else {
3132                 new_time = delta;
3133         }
3134
3135         note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
3136 }
3137
3138 void
3139 MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
3140 {
3141         note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
3142 }
3143
3144 void
3145 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
3146 {
3147         int8_t delta;
3148         int8_t value = 0;
3149
3150         if (_selection.empty()) {
3151                 return;
3152         }
3153
3154         if (fine) {
3155                 delta = 1;
3156         } else {
3157                 delta = 10;
3158         }
3159
3160         if (!up) {
3161                 delta = -delta;
3162         }
3163
3164         if (!allow_smush) {
3165                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3166                         if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
3167                                 goto cursor_label;
3168                         }
3169                 }
3170         }
3171
3172         start_note_diff_command (_("change velocities"));
3173
3174         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
3175                 Selection::iterator next = i;
3176                 ++next;
3177
3178                 if (all_together) {
3179                         if (i == _selection.begin()) {
3180                                 change_note_velocity (*i, delta, true);
3181                                 value = (*i)->note()->velocity() + delta;
3182                         } else {
3183                                 change_note_velocity (*i, value, false);
3184                         }
3185
3186                 } else {
3187                         change_note_velocity (*i, delta, true);
3188                 }
3189
3190                 i = next;
3191         }
3192
3193         apply_diff();
3194
3195   cursor_label:
3196         if (!_selection.empty()) {
3197                 char buf[24];
3198                 snprintf (buf, sizeof (buf), "Vel %d",
3199                           (int) (*_selection.begin())->note()->velocity());
3200                 show_verbose_cursor (buf, 10, 10);
3201         }
3202 }
3203
3204
3205 void
3206 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
3207 {
3208         if (_selection.empty()) {
3209                 return;
3210         }
3211
3212         int8_t delta;
3213
3214         if (fine) {
3215                 delta = 1;
3216         } else {
3217                 delta = 12;
3218         }
3219
3220         if (!up) {
3221                 delta = -delta;
3222         }
3223
3224         if (!allow_smush) {
3225                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3226                         if (!up) {
3227                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
3228                                         return;
3229                                 }
3230                         } else {
3231                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
3232                                         return;
3233                                 }
3234                         }
3235                 }
3236         }
3237
3238         start_note_diff_command (_("transpose"));
3239
3240         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3241                 Selection::iterator next = i;
3242                 ++next;
3243                 change_note_note (*i, delta, true);
3244                 i = next;
3245         }
3246
3247         apply_diff ();
3248 }
3249
3250 void
3251 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
3252 {
3253         if (!delta) {
3254                 if (fine) {
3255                         delta = Evoral::Beats(1.0/128.0);
3256                 } else {
3257                         /* grab the current grid distance */
3258                         delta = get_grid_beats(_region->position());
3259                 }
3260         }
3261
3262         if (shorter) {
3263                 delta = -delta;
3264         }
3265
3266         start_note_diff_command (_("change note lengths"));
3267
3268         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3269                 Selection::iterator next = i;
3270                 ++next;
3271
3272                 /* note the negation of the delta for start */
3273
3274                 trim_note (*i,
3275                            (start ? -delta : Evoral::Beats()),
3276                            (end   ? delta  : Evoral::Beats()));
3277                 i = next;
3278         }
3279
3280         apply_diff ();
3281
3282 }
3283
3284 void
3285 MidiRegionView::nudge_notes (bool forward, bool fine)
3286 {
3287         if (_selection.empty()) {
3288                 return;
3289         }
3290
3291         /* pick a note as the point along the timeline to get the nudge distance.
3292            its not necessarily the earliest note, so we may want to pull the notes out
3293            into a vector and sort before using the first one.
3294         */
3295
3296         const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
3297         Evoral::Beats    delta;
3298
3299         if (!fine) {
3300
3301                 /* non-fine, move by 1 bar regardless of snap */
3302                 delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
3303
3304         } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
3305
3306                 /* grid is off - use nudge distance */
3307
3308                 framepos_t       unused;
3309                 const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
3310                 delta = region_frames_to_region_beats (fabs ((double)distance));
3311
3312         } else {
3313
3314                 /* use grid */
3315
3316                 framepos_t next_pos = ref_point;
3317
3318                 if (forward) {
3319                         if (max_framepos - 1 < next_pos) {
3320                                 next_pos += 1;
3321                         }
3322                 } else {
3323                         if (next_pos == 0) {
3324                                 return;
3325                         }
3326                         next_pos -= 1;
3327                 }
3328
3329                 trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
3330                 const framecnt_t distance = ref_point - next_pos;
3331                 delta = region_frames_to_region_beats (fabs ((double)distance));
3332         }
3333
3334         if (!delta) {
3335                 return;
3336         }
3337
3338         if (!forward) {
3339                 delta = -delta;
3340         }
3341
3342         start_note_diff_command (_("nudge"));
3343
3344         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
3345                 Selection::iterator next = i;
3346                 ++next;
3347                 change_note_time (*i, delta, true);
3348                 i = next;
3349         }
3350
3351         apply_diff ();
3352 }
3353
3354 void
3355 MidiRegionView::change_channel(uint8_t channel)
3356 {
3357         start_note_diff_command(_("change channel"));
3358         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3359                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
3360         }
3361
3362         apply_diff();
3363 }
3364
3365
3366 void
3367 MidiRegionView::note_entered(NoteBase* ev)
3368 {
3369         _entered_note = ev;
3370
3371         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3372
3373         if (_mouse_state == SelectTouchDragging) {
3374
3375                 note_selected (ev, true);
3376
3377         } else if (editor->current_mouse_mode() == MouseContent) {
3378
3379                 remove_ghost_note ();
3380                 show_verbose_cursor (ev->note ());
3381
3382         } else if (editor->current_mouse_mode() == MouseDraw) {
3383
3384                 remove_ghost_note ();
3385                 show_verbose_cursor (ev->note ());
3386         }
3387 }
3388
3389 void
3390 MidiRegionView::note_left (NoteBase*)
3391 {
3392         _entered_note = 0;
3393
3394         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3395                 (*i)->hide_velocity ();
3396         }
3397
3398         hide_verbose_cursor ();
3399 }
3400
3401 void
3402 MidiRegionView::patch_entered (PatchChange* p)
3403 {
3404         ostringstream s;
3405         s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
3406           << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
3407           << _("Channel ") << ((int) p->patch()->channel() + 1);
3408         show_verbose_cursor (s.str(), 10, 20);
3409         p->item().grab_focus();
3410 }
3411
3412 void
3413 MidiRegionView::patch_left (PatchChange *)
3414 {
3415         hide_verbose_cursor ();
3416         /* focus will transfer back via the enter-notify event sent to this
3417          * midi region view.
3418          */
3419 }
3420
3421 void
3422 MidiRegionView::sysex_entered (SysEx* p)
3423 {
3424         // ostringstream s;
3425         // CAIROCANVAS
3426         // need a way to extract text from p->_flag->_text
3427         // s << p->text();
3428         // show_verbose_cursor (s.str(), 10, 20);
3429         p->item().grab_focus();
3430 }
3431
3432 void
3433 MidiRegionView::sysex_left (SysEx *)
3434 {
3435         hide_verbose_cursor ();
3436         /* focus will transfer back via the enter-notify event sent to this
3437          * midi region view.
3438          */
3439 }
3440
3441 void
3442 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3443 {
3444         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3445         Editing::MouseMode mm = editor->current_mouse_mode();
3446         bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
3447
3448         Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
3449         if (can_set_cursor && ctx) {
3450                 if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
3451                         ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
3452                 } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
3453                         ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
3454                 } else {
3455                         ctx->cursor_ctx->change(editor->cursors()->grabber_note);
3456                 }
3457         }
3458 }
3459
3460 uint32_t
3461 MidiRegionView::get_fill_color() const
3462 {
3463         const std::string mod_name = (_dragging ? "dragging region" :
3464                                       trackview.editor().internal_editing() ? "editable region" :
3465                                       "midi frame base");
3466         if (_selected) {
3467                 return UIConfiguration::instance().color_mod ("selected region base", mod_name);
3468         } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
3469                    !UIConfiguration::instance().get_color_regions_using_track_color()) {
3470                 return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
3471         }
3472         return UIConfiguration::instance().color_mod (fill_color, mod_name);
3473 }
3474
3475 void
3476 MidiRegionView::midi_channel_mode_changed ()
3477 {
3478         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3479         uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
3480         ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
3481
3482         if (mode == ForceChannel) {
3483                 mask = 0xFFFF; // Show all notes as active (below)
3484         }
3485
3486         // Update notes for selection
3487         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3488                 i->second->on_channel_selection_change (mask);
3489         }
3490
3491         _patch_changes.clear ();
3492         display_patch_changes ();
3493 }
3494
3495 void
3496 MidiRegionView::instrument_settings_changed ()
3497 {
3498         redisplay_model();
3499 }
3500
3501 void
3502 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3503 {
3504         if (_selection.empty()) {
3505                 return;
3506         }
3507
3508         PublicEditor& editor (trackview.editor());
3509
3510         switch (op) {
3511         case Delete:
3512                 /* XXX what to do ? */
3513                 break;
3514         case Cut:
3515         case Copy:
3516                 editor.get_cut_buffer().add (selection_as_cut_buffer());
3517                 break;
3518         default:
3519                 break;
3520         }
3521
3522         if (op != Copy) {
3523
3524                 start_note_diff_command();
3525
3526                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3527                         switch (op) {
3528                         case Copy:
3529                                 break;
3530                         case Delete:
3531                         case Cut:
3532                         case Clear:
3533                                 note_diff_remove_note (*i);
3534                                 break;
3535                         }
3536                 }
3537
3538                 apply_diff();
3539         }
3540 }
3541
3542 MidiCutBuffer*
3543 MidiRegionView::selection_as_cut_buffer () const
3544 {
3545         Notes notes;
3546
3547         for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
3548                 NoteType* n = (*i)->note().get();
3549                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3550         }
3551
3552         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3553         cb->set (notes);
3554
3555         return cb;
3556 }
3557
3558 /** This method handles undo */
3559 bool
3560 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
3561 {
3562         bool commit = false;
3563         // Paste notes, if available
3564         MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
3565         if (m != selection.midi_notes.end()) {
3566                 ctx.counts.increase_n_notes();
3567                 if (!(*m)->empty()) {
3568                         commit = true;
3569                 }
3570                 paste_internal(pos, ctx.count, ctx.times, **m);
3571         }
3572
3573         // Paste control points to automation children, if available
3574         typedef RouteTimeAxisView::AutomationTracks ATracks;
3575         const ATracks& atracks = midi_view()->automation_tracks();
3576         for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
3577                 if (a->second->paste(pos, selection, ctx, sub_num)) {
3578                         if(!commit) {
3579                                 trackview.editor().begin_reversible_command (Operations::paste);
3580                         }
3581                         commit = true;
3582                 }
3583         }
3584
3585         if (commit) {
3586                 trackview.editor().commit_reversible_command ();
3587         }
3588         return true;
3589 }
3590
3591 /** This method handles undo */
3592 void
3593 MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
3594 {
3595         if (mcb.empty()) {
3596                 return;
3597         }
3598
3599         start_note_diff_command (_("paste"));
3600
3601         const Evoral::Beats snap_beats    = get_grid_beats(pos);
3602         const Evoral::Beats first_time    = (*mcb.notes().begin())->time();
3603         const Evoral::Beats last_time     = (*mcb.notes().rbegin())->end_time();
3604         const Evoral::Beats duration      = last_time - first_time;
3605         const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
3606         const Evoral::Beats paste_offset  = snap_duration * paste_count;
3607         const Evoral::Beats quarter_note     = absolute_frames_to_source_beats(pos) + paste_offset;
3608         Evoral::Beats       end_point     = Evoral::Beats();
3609
3610         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
3611                                                        first_time,
3612                                                        last_time,
3613                                                        duration, pos, _region->position(),
3614                                                        quarter_note));
3615
3616         clear_editor_note_selection ();
3617
3618         for (int n = 0; n < (int) times; ++n) {
3619
3620                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3621
3622                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3623                         copied_note->set_time (quarter_note + copied_note->time() - first_time);
3624                         copied_note->set_id (Evoral::next_event_id());
3625
3626                         /* make all newly added notes selected */
3627
3628                         note_diff_add_note (copied_note, true);
3629                         end_point = copied_note->end_time();
3630                 }
3631         }
3632
3633         /* if we pasted past the current end of the region, extend the region */
3634
3635         framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3636         framepos_t region_end = _region->position() + _region->length() - 1;
3637
3638         if (end_frame > region_end) {
3639
3640                 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3641
3642                 _region->clear_changes ();
3643                 /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
3644                 _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
3645                 trackview.session()->add_command (new StatefulDiffCommand (_region));
3646         }
3647
3648         apply_diff (true);
3649 }
3650
3651 struct EventNoteTimeEarlyFirstComparator {
3652         bool operator() (NoteBase* a, NoteBase* b) {
3653                 return a->note()->time() < b->note()->time();
3654         }
3655 };
3656
3657 void
3658 MidiRegionView::goto_next_note (bool add_to_selection)
3659 {
3660         bool use_next = false;
3661
3662         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3663         uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
3664         NoteBase* first_note = 0;
3665
3666         MidiModel::ReadLock lock(_model->read_lock());
3667         MidiModel::Notes& notes (_model->notes());
3668
3669         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
3670                 NoteBase* cne = 0;
3671                 if ((cne = find_canvas_note (*n))) {
3672
3673                         if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
3674                                 first_note = cne;
3675                         }
3676
3677                         if (cne->selected()) {
3678                                 use_next = true;
3679                                 continue;
3680                         } else if (use_next) {
3681                                 if (channel_mask & (1 << (*n)->channel())) {
3682                                         if (!add_to_selection) {
3683                                                 unique_select (cne);
3684                                         } else {
3685                                                 note_selected (cne, true, false);
3686                                         }
3687
3688                                         return;
3689                                 }
3690                         }
3691                 }
3692         }
3693
3694         /* use the first one */
3695
3696         if (!_events.empty() && first_note) {
3697                 unique_select (first_note);
3698         }
3699 }
3700
3701 void
3702 MidiRegionView::goto_previous_note (bool add_to_selection)
3703 {
3704         bool use_next = false;
3705
3706         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3707         uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
3708         NoteBase* last_note = 0;
3709
3710         MidiModel::ReadLock lock(_model->read_lock());
3711         MidiModel::Notes& notes (_model->notes());
3712
3713         for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
3714                 NoteBase* cne = 0;
3715                 if ((cne = find_canvas_note (*n))) {
3716
3717                         if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
3718                                 last_note = cne;
3719                         }
3720
3721                         if (cne->selected()) {
3722                                 use_next = true;
3723                                 continue;
3724
3725                         } else if (use_next) {
3726                                 if (channel_mask & (1 << (*n)->channel())) {
3727                                         if (!add_to_selection) {
3728                                                 unique_select (cne);
3729                                         } else {
3730                                                 note_selected (cne, true, false);
3731                                         }
3732
3733                                         return;
3734                                 }
3735                         }
3736                 }
3737         }
3738
3739         /* use the last one */
3740
3741         if (!_events.empty() && last_note) {
3742                 unique_select (last_note);
3743         }
3744 }
3745
3746 void
3747 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3748 {
3749         bool had_selected = false;
3750
3751         /* we previously time sorted events here, but Notes is a multiset sorted by time */
3752
3753         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3754                 if (i->second->selected()) {
3755                         selected.insert (i->first);
3756                         had_selected = true;
3757                 }
3758         }
3759
3760         if (allow_all_if_none_selected && !had_selected) {
3761                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3762                         selected.insert (i->first);
3763                 }
3764         }
3765 }
3766
3767 void
3768 MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
3769 {
3770         x = std::max(0.0, x);
3771
3772         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3773
3774         _last_ghost_x = x;
3775         _last_ghost_y = y;
3776
3777         _note_group->canvas_to_item (x, y);
3778
3779         PublicEditor& editor = trackview.editor ();
3780
3781         framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
3782
3783         const int32_t divisions = editor.get_grid_music_divisions (state);
3784         const bool shift_snap = midi_view()->note_mode() != Percussive;
3785         const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
3786
3787         /* prevent Percussive mode from displaying a ghost hit at region end */
3788         if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
3789                 _ghost_note->hide();
3790                 hide_verbose_cursor ();
3791                 return;
3792         }
3793
3794         /* ghost note may have been snapped before region */
3795         if (_ghost_note && snapped_beats.to_double() < 0.0) {
3796                 _ghost_note->hide();
3797                 return;
3798
3799         } else if (_ghost_note) {
3800                 _ghost_note->show();
3801         }
3802
3803         /* calculate time in beats relative to start of source */
3804         const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
3805
3806         _ghost_note->note()->set_time (snapped_beats);
3807         _ghost_note->note()->set_length (length);
3808         _ghost_note->note()->set_note (y_to_note (y));
3809         _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3810         _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
3811         /* the ghost note does not appear in ghost regions, so pass false in here */
3812         update_note (_ghost_note, false);
3813
3814         show_verbose_cursor (_ghost_note->note ());
3815 }
3816
3817 void
3818 MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
3819 {
3820         remove_ghost_note ();
3821
3822         boost::shared_ptr<NoteType> g (new NoteType);
3823         if (midi_view()->note_mode() == Sustained) {
3824                 _ghost_note = new Note (*this, _note_group, g);
3825         } else {
3826                 _ghost_note = new Hit (*this, _note_group, 10, g);
3827         }
3828         _ghost_note->set_ignore_events (true);
3829         _ghost_note->set_outline_color (0x000000aa);
3830         update_ghost_note (x, y, state);
3831         _ghost_note->show ();
3832
3833         show_verbose_cursor (_ghost_note->note ());
3834 }
3835
3836 void
3837 MidiRegionView::remove_ghost_note ()
3838 {
3839         delete _ghost_note;
3840         _ghost_note = 0;
3841 }
3842
3843 void
3844 MidiRegionView::hide_verbose_cursor ()
3845 {
3846         trackview.editor().verbose_cursor()->hide ();
3847         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3848         if (mtv) {
3849                 mtv->set_note_highlight (NO_MIDI_NOTE);
3850         }
3851 }
3852
3853 void
3854 MidiRegionView::snap_changed ()
3855 {
3856         if (!_ghost_note) {
3857                 return;
3858         }
3859
3860         create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
3861 }
3862
3863 void
3864 MidiRegionView::drop_down_keys ()
3865 {
3866         _mouse_state = None;
3867 }
3868
3869 void
3870 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3871 {
3872         /* XXX: This is dead code.  What was it for? */
3873
3874         double note = y_to_note(y);
3875         Events e;
3876         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3877
3878         uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
3879
3880         if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3881                 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3882         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3883                 get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3884         } else {
3885                 return;
3886         }
3887
3888         bool add_mrv_selection = false;
3889
3890         if (_selection.empty()) {
3891                 add_mrv_selection = true;
3892         }
3893
3894         for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3895                 if (_selection.insert (i->second).second) {
3896                         i->second->set_selected (true);
3897                 }
3898         }
3899
3900         if (add_mrv_selection) {
3901                 PublicEditor& editor (trackview.editor());
3902                 editor.get_selection().add (this);
3903         }
3904 }
3905
3906 void
3907 MidiRegionView::color_handler ()
3908 {
3909         RegionView::color_handler ();
3910
3911         _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
3912         _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
3913
3914         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3915                 i->second->set_selected (i->second->selected()); // will change color
3916         }
3917
3918         /* XXX probably more to do here */
3919 }
3920
3921 void
3922 MidiRegionView::enable_display (bool yn)
3923 {
3924         RegionView::enable_display (yn);
3925 }
3926
3927 void
3928 MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
3929 {
3930         if (_step_edit_cursor == 0) {
3931                 ArdourCanvas::Item* const group = get_canvas_group();
3932
3933                 _step_edit_cursor = new ArdourCanvas::Rectangle (group);
3934                 _step_edit_cursor->set_y0 (0);
3935                 _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
3936                 _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
3937                 _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
3938         }
3939
3940         move_step_edit_cursor (pos);
3941         _step_edit_cursor->show ();
3942 }
3943
3944 void
3945 MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
3946 {
3947         _step_edit_cursor_position = pos;
3948
3949         if (_step_edit_cursor) {
3950                 double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
3951                 _step_edit_cursor->set_x0 (pixel);
3952                 set_step_edit_cursor_width (_step_edit_cursor_width);
3953         }
3954 }
3955
3956 void
3957 MidiRegionView::hide_step_edit_cursor ()
3958 {
3959         if (_step_edit_cursor) {
3960                 _step_edit_cursor->hide ();
3961         }
3962 }
3963
3964 void
3965 MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
3966 {
3967         _step_edit_cursor_width = beats;
3968
3969         if (_step_edit_cursor) {
3970                 _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
3971                                                    region_beats_to_region_frames (_step_edit_cursor_position + beats)
3972                                                    - region_beats_to_region_frames (_step_edit_cursor_position)));
3973         }
3974 }
3975
3976 /** Called when a diskstream on our track has received some data.  Update the view, if applicable.
3977  *  @param w Source that the data will end up in.
3978  */
3979 void
3980 MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
3981 {
3982         if (!_active_notes) {
3983                 /* we aren't actively being recorded to */
3984                 return;
3985         }
3986
3987         boost::shared_ptr<MidiSource> src = w.lock ();
3988         if (!src || src != midi_region()->midi_source()) {
3989                 /* recorded data was not destined for our source */
3990                 return;
3991         }
3992
3993         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3994
3995         boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
3996
3997         framepos_t back = max_framepos;
3998
3999         for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
4000                 const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
4001
4002                 if (ev.is_channel_event()) {
4003                         if (get_channel_mode() == FilterChannels) {
4004                                 if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
4005                                         continue;
4006                                 }
4007                         }
4008                 }
4009
4010                 /* convert from session frames to source beats */
4011                 Evoral::Beats const time_beats = _source_relative_time_converter.from(
4012                         ev.time() - src->timeline_position() + _region->start());
4013
4014                 if (ev.type() == MIDI_CMD_NOTE_ON) {
4015                         boost::shared_ptr<NoteType> note (
4016                                 new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
4017
4018                         add_note (note, true);
4019
4020                         /* fix up our note range */
4021                         if (ev.note() < _current_range_min) {
4022                                 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
4023                         } else if (ev.note() > _current_range_max) {
4024                                 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
4025                         }
4026
4027                 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
4028                         resolve_note (ev.note (), time_beats);
4029                 }
4030
4031                 back = ev.time ();
4032         }
4033
4034         midi_stream_view()->check_record_layers (region(), back);
4035 }
4036
4037 void
4038 MidiRegionView::trim_front_starting ()
4039 {
4040         /* We used to eparent the note group to the region view's parent, so that it didn't change.
4041            now we update it.
4042         */
4043 }
4044
4045 void
4046 MidiRegionView::trim_front_ending ()
4047 {
4048         if (_region->start() < 0) {
4049                 /* Trim drag made start time -ve; fix this */
4050                 midi_region()->fix_negative_start ();
4051         }
4052 }
4053
4054 void
4055 MidiRegionView::edit_patch_change (PatchChange* pc)
4056 {
4057         PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
4058
4059         int response = d.run();
4060
4061         switch (response) {
4062         case Gtk::RESPONSE_ACCEPT:
4063                 break;
4064         case Gtk::RESPONSE_REJECT:
4065                 delete_patch_change (pc);
4066                 return;
4067         default:
4068                 return;
4069         }
4070
4071         change_patch_change (pc->patch(), d.patch ());
4072 }
4073
4074 void
4075 MidiRegionView::delete_sysex (SysEx* /*sysex*/)
4076 {
4077         // CAIROCANVAS
4078         // sysyex object doesn't have a pointer to a sysex event
4079         // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
4080         // c->remove (sysex->sysex());
4081         // _model->apply_command (*trackview.session(), c);
4082
4083         //_sys_exes.clear ();
4084         // display_sysexes();
4085 }
4086
4087 std::string
4088 MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
4089 {
4090         using namespace MIDI::Name;
4091         std::string name;
4092
4093         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4094         if (mtv) {
4095                 boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
4096                 if (device_names) {
4097                         MIDI::Name::PatchPrimaryKey patch_key;
4098                         get_patch_key_at(n->time(), n->channel(), patch_key);
4099                         name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
4100                                                        n->channel(),
4101                                                        patch_key.bank(),
4102                                                        patch_key.program(),
4103                                                        note_value);
4104                 }
4105         }
4106
4107         char buf[128];
4108         snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
4109                   (int) note_value,
4110                   name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
4111                   (int) n->channel() + 1,
4112                   (int) n->velocity());
4113
4114         return buf;
4115 }
4116
4117 void
4118 MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
4119                                                        uint8_t new_value) const
4120 {
4121         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
4122         if (mtv) {
4123                 mtv->set_note_highlight (new_value);
4124         }
4125
4126         show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
4127 }
4128
4129 void
4130 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
4131 {
4132         show_verbose_cursor_for_new_note_value(n, n->note());
4133 }
4134
4135 void
4136 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
4137 {
4138         trackview.editor().verbose_cursor()->set (text);
4139         trackview.editor().verbose_cursor()->show ();
4140         trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
4141 }
4142
4143 uint8_t
4144 MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
4145 {
4146         if (_model->notes().empty()) {
4147                 return 0x40;  // No notes, use default
4148         }
4149
4150         MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
4151         if (m == _model->notes().begin()) {
4152                 // Before the start, use the velocity of the first note
4153                 return (*m)->velocity();
4154         } else if (m == _model->notes().end()) {
4155                 // Past the end, use the velocity of the last note
4156                 --m;
4157                 return (*m)->velocity();
4158         }
4159
4160         // Interpolate velocity of surrounding notes
4161         MidiModel::Notes::const_iterator n = m;
4162         --n;
4163
4164         const double frac = ((time - (*n)->time()).to_double() /
4165                              ((*m)->time() - (*n)->time()).to_double());
4166
4167         return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
4168 }
4169
4170 /** @param p A session framepos.
4171  *  @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
4172  *  bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
4173  *  @return beat duration of p snapped to the grid subdivision underneath it.
4174  */
4175 Evoral::Beats
4176 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
4177 {
4178         TempoMap& map (trackview.session()->tempo_map());
4179         double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
4180
4181         if (divisions != 0 && shift_snap) {
4182                 const double qaf = map.quarter_note_at_frame (p + _region->position());
4183                 /* Hack so that we always snap to the note that we are over, instead of snapping
4184                    to the next one if we're more than halfway through the one we're over.
4185                 */
4186                 const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
4187                 const double rem = eqaf - qaf;
4188                 if (rem >= 0.0) {
4189                         eqaf -= grid_beats.to_double();
4190                 }
4191         }
4192         const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
4193
4194         return Evoral::Beats (eqaf - session_start_off);
4195 }
4196
4197 ChannelMode
4198 MidiRegionView::get_channel_mode () const
4199 {
4200         RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4201         return rtav->midi_track()->get_playback_channel_mode();
4202 }
4203
4204 uint16_t
4205 MidiRegionView::get_selected_channels () const
4206 {
4207         RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
4208         return rtav->midi_track()->get_playback_channel_mask();
4209 }
4210
4211
4212 Evoral::Beats
4213 MidiRegionView::get_grid_beats(framepos_t pos) const
4214 {
4215         PublicEditor& editor  = trackview.editor();
4216         bool          success = false;
4217         Evoral::Beats beats   = editor.get_grid_type_as_beats (success, pos);
4218         if (!success) {
4219                 beats = Evoral::Beats(1);
4220         }
4221         return beats;
4222 }
4223 uint8_t
4224 MidiRegionView::y_to_note (double y) const
4225 {
4226         int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
4227                 + _current_range_min;
4228
4229         if (n < 0) {
4230                 return 0;
4231         } else if (n > 127) {
4232                 return 127;
4233         }
4234
4235         /* min due to rounding and/or off-by-one errors */
4236         return min ((uint8_t) n, _current_range_max);
4237 }
4238
4239 double
4240 MidiRegionView::note_to_y(uint8_t note) const
4241 {
4242         return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
4243 }