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