958fa885d51e39d588b42b984d25fcf8f5daf17e
[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 <cassert>
22 #include <algorithm>
23 #include <ostream>
24
25 #include <gtkmm.h>
26
27 #include <gtkmm2ext/gtk_ui.h>
28
29 #include <sigc++/signal.h>
30
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
33
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
41
42 #include "evoral/Parameter.hpp"
43 #include "evoral/MIDIParameters.hpp"
44 #include "evoral/Control.hpp"
45 #include "evoral/midi_util.h"
46
47 #include "automation_region_view.h"
48 #include "automation_time_axis.h"
49 #include "canvas-hit.h"
50 #include "canvas-note.h"
51 #include "canvas_patch_change.h"
52 #include "debug.h"
53 #include "editor.h"
54 #include "ghostregion.h"
55 #include "gui_thread.h"
56 #include "keyboard.h"
57 #include "midi_channel_dialog.h"
58 #include "midi_cut_buffer.h"
59 #include "midi_list_editor.h"
60 #include "midi_region_view.h"
61 #include "midi_streamview.h"
62 #include "midi_time_axis.h"
63 #include "midi_util.h"
64 #include "note_player.h"
65 #include "public_editor.h"
66 #include "rgb_macros.h"
67 #include "selection.h"
68 #include "simpleline.h"
69 #include "streamview.h"
70 #include "utils.h"
71 #include "mouse_cursors.h"
72 #include "patch_change_dialog.h"
73 #include "verbose_cursor.h"
74
75 #include "i18n.h"
76
77 using namespace ARDOUR;
78 using namespace PBD;
79 using namespace Editing;
80 using namespace ArdourCanvas;
81 using Gtkmm2ext::Keyboard;
82
83 #define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
84
85 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
86                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
87         : RegionView (parent, tv, r, spu, basic_color)
88         , _force_channel(-1)
89         , _last_channel_selection(0xFFFF)
90         , _current_range_min(0)
91         , _current_range_max(0)
92         , _model_name(string())
93         , _custom_device_mode(string())
94         , _active_notes(0)
95         , _note_group(new ArdourCanvas::Group(*group))
96         , _note_diff_command (0)
97         , _ghost_note(0)
98         , _drag_rect (0)
99         , _step_edit_cursor (0)
100         , _step_edit_cursor_width (1.0)
101         , _step_edit_cursor_position (0.0)
102         , _channel_selection_scoped_note (0)
103         , _temporary_note_group (0)
104         , _mouse_state(None)
105         , _pressed_button(0)
106         , _sort_needed (true)
107         , _optimization_iterator (_events.end())
108         , _list_editor (0)
109         , _no_sound_notes (false)
110         , _last_event_x (0)
111         , _last_event_y (0)
112         , _pre_enter_cursor (0)
113 {
114         _note_group->raise_to_top();
115         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
116
117         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
118         connect_to_diskstream ();
119 }
120
121 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
122                                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
123                                 TimeAxisViewItem::Visibility visibility)
124         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
125         , _force_channel(-1)
126         , _last_channel_selection(0xFFFF)
127         , _model_name(string())
128         , _custom_device_mode(string())
129         , _active_notes(0)
130         , _note_group(new ArdourCanvas::Group(*parent))
131         , _note_diff_command (0)
132         , _ghost_note(0)
133         , _drag_rect (0)
134         , _step_edit_cursor (0)
135         , _step_edit_cursor_width (1.0)
136         , _step_edit_cursor_position (0.0)
137         , _channel_selection_scoped_note (0)
138         , _temporary_note_group (0)
139         , _mouse_state(None)
140         , _pressed_button(0)
141         , _sort_needed (true)
142         , _optimization_iterator (_events.end())
143         , _list_editor (0)
144         , _no_sound_notes (false)
145         , _last_event_x (0)
146         , _last_event_y (0)
147 {
148         _note_group->raise_to_top();
149         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
150
151         connect_to_diskstream ();
152 }
153
154 void
155 MidiRegionView::parameter_changed (std::string const & p)
156 {
157         if (p == "diplay-first-midi-bank-as-zero") {
158                 if (_enable_display) {
159                         redisplay_model();
160                 }
161         }
162 }
163
164 MidiRegionView::MidiRegionView (const MidiRegionView& other)
165         : sigc::trackable(other)
166         , RegionView (other)
167         , _force_channel(-1)
168         , _last_channel_selection(0xFFFF)
169         , _model_name(string())
170         , _custom_device_mode(string())
171         , _active_notes(0)
172         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
173         , _note_diff_command (0)
174         , _ghost_note(0)
175         , _drag_rect (0)
176         , _step_edit_cursor (0)
177         , _step_edit_cursor_width (1.0)
178         , _step_edit_cursor_position (0.0)
179         , _channel_selection_scoped_note (0)
180         , _temporary_note_group (0)
181         , _mouse_state(None)
182         , _pressed_button(0)
183         , _sort_needed (true)
184         , _optimization_iterator (_events.end())
185         , _list_editor (0)
186         , _no_sound_notes (false)
187         , _last_event_x (0)
188         , _last_event_y (0)
189 {
190         Gdk::Color c;
191         int r,g,b,a;
192
193         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
194         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
195
196         init (c, false);
197 }
198
199 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
200         : RegionView (other, boost::shared_ptr<Region> (region))
201         , _force_channel(-1)
202         , _last_channel_selection(0xFFFF)
203         , _model_name(string())
204         , _custom_device_mode(string())
205         , _active_notes(0)
206         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
207         , _note_diff_command (0)
208         , _ghost_note(0)
209         , _drag_rect (0)
210         , _step_edit_cursor (0)
211         , _step_edit_cursor_width (1.0)
212         , _step_edit_cursor_position (0.0)
213         , _channel_selection_scoped_note (0)
214         , _temporary_note_group (0)
215         , _mouse_state(None)
216         , _pressed_button(0)
217         , _sort_needed (true)
218         , _optimization_iterator (_events.end())
219         , _list_editor (0)
220         , _no_sound_notes (false)
221         , _last_event_x (0)
222         , _last_event_y (0)
223 {
224         Gdk::Color c;
225         int r,g,b,a;
226
227         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
228         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
229
230         init (c, true);
231 }
232
233 void
234 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
235 {
236         PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
237
238         CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
239                                                          ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
240                                                          gui_context());
241
242         if (wfd) {
243                 midi_region()->midi_source(0)->load_model();
244         }
245
246         _model = midi_region()->midi_source(0)->model();
247         _enable_display = false;
248
249         RegionView::init (basic_color, false);
250
251         compute_colors (basic_color);
252
253         set_height (trackview.current_height());
254
255         region_muted ();
256         region_sync_changed ();
257         region_resized (ARDOUR::bounds_change);
258         region_locked ();
259
260         reset_width_dependent_items (_pixel_width);
261
262         set_colors ();
263
264         _enable_display = true;
265         if (_model) {
266                 if (wfd) {
267                         display_model (_model);
268                 }
269         }
270
271         group->raise_to_top();
272         group->signal_event().connect(
273                 sigc::mem_fun(this, &MidiRegionView::canvas_event), false);
274
275         midi_view()->signal_channel_mode_changed().connect(
276                 sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
277
278         midi_view()->signal_midi_patch_settings_changed().connect(
279                 sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
280
281         trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
282                                                ui_bind(&MidiRegionView::snap_changed, this),
283                                                gui_context());
284
285         Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
286         connect_to_diskstream ();
287 }
288
289 void
290 MidiRegionView::connect_to_diskstream ()
291 {
292         midi_view()->midi_track()->DataRecorded.connect(
293                 *this, invalidator(*this),
294                 ui_bind(&MidiRegionView::data_recorded, this, _1, _2),
295                 gui_context());
296 }
297
298 bool
299 MidiRegionView::canvas_event(GdkEvent* ev)
300 {
301         switch (ev->type) {
302         case GDK_ENTER_NOTIFY:
303         case GDK_LEAVE_NOTIFY:
304                 _last_event_x = ev->crossing.x;
305                 _last_event_y = ev->crossing.y;
306                 break;
307         case GDK_MOTION_NOTIFY:
308                 _last_event_x = ev->motion.x;
309                 _last_event_y = ev->motion.y;
310                 break;
311         default:
312                 break;
313         }
314
315         if (!trackview.editor().internal_editing()) {
316                 return false;
317         }
318
319         switch (ev->type) {
320         case GDK_SCROLL:
321                 return scroll (&ev->scroll);
322
323         case GDK_KEY_PRESS:
324                 return key_press (&ev->key);
325
326         case GDK_KEY_RELEASE:
327                 return key_release (&ev->key);
328
329         case GDK_BUTTON_PRESS:
330                 return button_press (&ev->button);
331
332         case GDK_2BUTTON_PRESS:
333                 return true;
334
335         case GDK_BUTTON_RELEASE:
336                 return button_release (&ev->button);
337
338         case GDK_ENTER_NOTIFY:
339                 return enter_notify (&ev->crossing);
340
341         case GDK_LEAVE_NOTIFY:
342                 return leave_notify (&ev->crossing);
343
344         case GDK_MOTION_NOTIFY:
345                 return motion (&ev->motion);
346
347         default:
348                 break;
349         }
350
351         return false;
352 }
353
354 void
355 MidiRegionView::remove_ghost_note ()
356 {
357         delete _ghost_note;
358         _ghost_note = 0;
359 }
360
361 bool
362 MidiRegionView::enter_notify (GdkEventCrossing* ev)
363 {
364         trackview.editor().MouseModeChanged.connect (
365                 _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
366                 );
367
368         if (trackview.editor().current_mouse_mode() == MouseRange) {
369                 create_ghost_note (ev->x, ev->y);
370         }
371
372         if (!trackview.editor().internal_editing()) {
373                 Keyboard::magic_widget_drop_focus();
374         } else {
375                 Keyboard::magic_widget_grab_focus();
376                 group->grab_focus();
377         }
378
379         return false;
380 }
381
382 bool
383 MidiRegionView::leave_notify (GdkEventCrossing* ev)
384 {
385         _mouse_mode_connection.disconnect ();
386
387         trackview.editor().verbose_cursor()->hide ();
388         remove_ghost_note ();
389
390         return false;
391 }
392
393 void
394 MidiRegionView::mouse_mode_changed ()
395 {
396         if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) {
397                 create_ghost_note (_last_event_x, _last_event_y);
398         } else {
399                 remove_ghost_note ();
400                 trackview.editor().verbose_cursor()->hide ();
401         }
402
403         if (!trackview.editor().internal_editing()) {
404                 Keyboard::magic_widget_drop_focus();
405         } else {
406                 Keyboard::magic_widget_grab_focus();
407                 group->grab_focus();
408         }
409 }
410
411 bool
412 MidiRegionView::button_press (GdkEventButton* ev)
413 {
414         if (ev->button != 1) {
415                 return false;
416         }
417
418         _last_x = ev->x;
419         _last_y = ev->y;
420
421         group->w2i (_last_x, _last_y);
422
423         if (_mouse_state != SelectTouchDragging) {
424
425                 _pressed_button = ev->button;
426                 _mouse_state = Pressed;
427
428                 return true;
429         }
430
431         _pressed_button = ev->button;
432
433         return true;
434 }
435
436 bool
437 MidiRegionView::button_release (GdkEventButton* ev)
438 {
439         double event_x, event_y;
440
441         if (ev->button != 1) {
442                 return false;
443         }
444
445         event_x = ev->x;
446         event_y = ev->y;
447
448         group->w2i(event_x, event_y);
449         group->ungrab(ev->time);
450
451         switch (_mouse_state) {
452         case Pressed: // Clicked
453
454                 switch (trackview.editor().current_mouse_mode()) {
455                 case MouseObject:
456                 case MouseTimeFX:
457                         {
458                                 clear_selection();
459
460                                 if (Keyboard::is_insert_note_event(ev)) {
461
462                                         double event_x, event_y;
463
464                                         event_x = ev->x;
465                                         event_y = ev->y;
466                                         group->w2i(event_x, event_y);
467
468                                         bool success;
469                                         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
470
471                                         if (!success) {
472                                                 beats = 1;
473                                         }
474
475                                         create_note_at (event_x, event_y, beats, true, true);
476                                 }
477
478                                 break;
479                         }
480                 case MouseRange:
481                         {
482                                 bool success;
483                                 Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
484
485                                 if (!success) {
486                                         beats = 1;
487                                 }
488
489                                 create_note_at (event_x, event_y, beats, true, true);
490
491                                 break;
492                         }
493                 default:
494                         break;
495                 }
496
497                 _mouse_state = None;
498                 break;
499
500         case SelectRectDragging: // Select drag done
501
502                 _mouse_state = None;
503                 delete _drag_rect;
504                 _drag_rect = 0;
505                 break;
506
507         case AddDragging: // Add drag done
508
509                 _mouse_state = None;
510
511                 if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange) {
512
513                         if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
514
515                                 const double x  = _drag_rect->property_x1();
516                                 const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1());
517
518                                 create_note_at (x, _drag_rect->property_y1(), region_frames_to_region_beats(length), true, false);
519                         }
520                 }
521
522                 delete _drag_rect;
523                 _drag_rect = 0;
524
525                 create_ghost_note (ev->x, ev->y);
526
527         default:
528                 break;
529         }
530
531         return false;
532 }
533
534 bool
535 MidiRegionView::motion (GdkEventMotion* ev)
536 {
537         double event_x, event_y;
538         framepos_t event_frame = 0;
539
540         event_x = ev->x;
541         event_y = ev->y;
542         group->w2i(event_x, event_y);
543
544         PublicEditor& editor = trackview.editor ();
545         
546         // convert event_x to global frame
547         framecnt_t grid_frames;
548         event_frame = snap_frame_to_grid_underneath (editor.pixel_to_frame (event_x), grid_frames);
549
550         if (!_ghost_note && editor.current_mouse_mode() != MouseRange
551             && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())
552             && _mouse_state != AddDragging) {
553
554                 create_ghost_note (ev->x, ev->y);
555         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange
556                    && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
557
558                 update_ghost_note (ev->x, ev->y);
559         } else if (_ghost_note && editor.current_mouse_mode() != MouseRange) {
560
561                 delete _ghost_note;
562                 _ghost_note = 0;
563
564                 editor.verbose_cursor()->hide ();
565         } else if (_ghost_note && editor.current_mouse_mode() == MouseRange) {
566                 update_ghost_note (ev->x, ev->y);
567         }
568
569         /* any motion immediately hides velocity text that may have been visible */
570
571         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
572                 (*i)->hide_velocity ();
573         }
574
575         switch (_mouse_state) {
576         case Pressed: // Maybe start a drag, if we've moved a bit
577
578                 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
579                         /* no appreciable movement since the button was pressed */
580                         return false;
581                 }
582
583                 if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject
584                     && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
585                         // Select drag start
586
587                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
588                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
589
590                         _last_x = event_x;
591                         _last_y = event_y;
592                         _drag_start_x = event_x;
593                         _drag_start_y = event_y;
594
595                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
596                         _drag_rect->property_x1() = event_x;
597                         _drag_rect->property_y1() = event_y;
598                         _drag_rect->property_x2() = event_x;
599                         _drag_rect->property_y2() = event_y;
600                         _drag_rect->property_outline_what() = 0xFF;
601                         _drag_rect->property_outline_color_rgba()
602                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
603                         _drag_rect->property_fill_color_rgba()
604                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
605
606                         _mouse_state = SelectRectDragging;
607                         return true;
608
609                 } else if (editor.internal_editing()) {
610                         // Add note drag start
611
612                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
613                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
614
615                         _last_x = event_x;
616                         _last_y = event_y;
617                         _drag_start_x = event_x;
618                         _drag_start_y = event_y;
619
620                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
621                         _drag_rect->property_x1() = editor.frame_to_pixel(event_frame);
622
623                         _drag_rect->property_y1() = midi_stream_view()->note_to_y(
624                                 midi_stream_view()->y_to_note(event_y));
625                         _drag_rect->property_x2() = editor.frame_to_pixel(event_frame);
626                         
627                         _drag_rect->property_y2() = _drag_rect->property_y1()
628                                 + floor(midi_stream_view()->note_height());
629                         _drag_rect->property_outline_what() = 0xFF;
630                         _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
631                         _drag_rect->property_fill_color_rgba()    = 0xFFFFFF66;
632
633                         _mouse_state = AddDragging;
634                         
635                         delete _ghost_note;
636                         _ghost_note = 0;
637
638                         editor.verbose_cursor()->hide ();
639
640                         return true;
641                 }
642
643                 return false;
644
645         case SelectRectDragging: // Select drag motion
646         case AddDragging: // Add note drag motion
647
648                 if (ev->is_hint) {
649                         int t_x;
650                         int t_y;
651                         GdkModifierType state;
652                         gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
653                         event_x = t_x;
654                         event_y = t_y;
655                 }
656
657                 if (_mouse_state == AddDragging) {
658                         event_x = editor.frame_to_pixel(event_frame);
659
660                         if (editor.snap_mode() == SnapNormal) {
661                                 /* event_frame will have been snapped to the start of the note we are under;
662                                    it's more intuitive if we use the end of that note here
663                                 */
664                                 event_x = editor.frame_to_pixel (event_frame + grid_frames);
665                         } else {
666                                 event_x = editor.frame_to_pixel (event_frame);
667                         }
668                         
669                 }
670
671                 if (_drag_rect) {
672
673                         if (event_x > _drag_start_x) {
674                                 _drag_rect->property_x2() = event_x;
675                         }
676                         else {
677                                 _drag_rect->property_x1() = event_x;
678                         }
679                 }
680
681                 if (_drag_rect && _mouse_state == SelectRectDragging) {
682
683                         if (event_y > _drag_start_y) {
684                                 _drag_rect->property_y2() = event_y;
685                         }
686                         else {
687                                 _drag_rect->property_y1() = event_y;
688                         }
689
690                         update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y, Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
691                 }
692
693                 _last_x = event_x;
694                 _last_y = event_y;
695
696         case SelectTouchDragging:
697                 return false;
698
699         default:
700                 break;
701         }
702
703         return false;
704 }
705
706
707 bool
708 MidiRegionView::scroll (GdkEventScroll* ev)
709 {
710         if (_selection.empty()) {
711                 return false;
712         }
713
714         trackview.editor().verbose_cursor()->hide ();
715
716         bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
717
718         if (ev->direction == GDK_SCROLL_UP) {
719                 change_velocities (true, fine, false);
720         } else if (ev->direction == GDK_SCROLL_DOWN) {
721                 change_velocities (false, fine, false);
722         }
723         return true;
724 }
725
726 bool
727 MidiRegionView::key_press (GdkEventKey* ev)
728 {
729         /* since GTK bindings are generally activated on press, and since
730            detectable auto-repeat is the name of the game and only sends
731            repeated presses, carry out key actions at key press, not release.
732         */
733
734         bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
735         
736         if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
737                 _mouse_state = SelectTouchDragging;
738                 return true;
739
740         } else if (ev->keyval == GDK_Escape && unmodified) {
741                 clear_selection();
742                 _mouse_state = None;
743
744         } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
745
746                 bool start = (ev->keyval == GDK_comma);
747                 bool end = (ev->keyval == GDK_period);
748                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
749                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
750
751                 change_note_lengths (fine, shorter, 0.0, start, end);
752
753                 return true;
754
755         } else if (ev->keyval == GDK_Delete && unmodified) {
756
757                 delete_selection();
758                 return true;
759
760         } else if (ev->keyval == GDK_Tab) {
761
762                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
763                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
764                 } else {
765                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
766                 }
767                 return true;
768
769         } else if (ev->keyval == GDK_ISO_Left_Tab) {
770
771                 /* Shift-TAB generates ISO Left Tab, for some reason */
772
773                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
774                         goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
775                 } else {
776                         goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
777                 }
778                 return true;
779
780
781
782         } else if (ev->keyval == GDK_Up) {
783
784                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
785                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
786
787                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
788                         change_velocities (true, fine, allow_smush);
789                 } else {
790                         transpose (true, fine, allow_smush);
791                 }
792                 return true;
793
794         } else if (ev->keyval == GDK_Down) {
795
796                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
797                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
798
799                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
800                         change_velocities (false, fine, allow_smush);
801                 } else {
802                         transpose (false, fine, allow_smush);
803                 }
804                 return true;
805
806         } else if (ev->keyval == GDK_Left && unmodified) {
807
808                 nudge_notes (false);
809                 return true;
810
811         } else if (ev->keyval == GDK_Right && unmodified) {
812
813                 nudge_notes (true);
814                 return true;
815
816         } else if (ev->keyval == GDK_c && unmodified) {
817                 channel_edit ();
818                 return true;
819         }
820
821         return false;
822 }
823
824 bool
825 MidiRegionView::key_release (GdkEventKey* ev)
826 {
827         if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
828                 _mouse_state = None;
829                 return true;
830         }
831         return false;
832 }
833
834 void
835 MidiRegionView::channel_edit ()
836 {
837         bool first = true;
838         uint8_t current_channel;
839
840         if (_selection.empty()) {
841                 return;
842         }
843         
844         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
845                 if (first) {
846                         current_channel = (*i)->note()->channel ();
847                         first = false;
848                 }
849         }
850
851         MidiChannelDialog channel_dialog (current_channel);
852         int ret = channel_dialog.run ();
853
854         switch (ret) {
855         case Gtk::RESPONSE_OK:
856                 break;
857         default:
858                 return;
859         }
860
861         uint8_t new_channel = channel_dialog.active_channel ();
862
863         start_note_diff_command (_("channel edit"));
864
865         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
866                 Selection::iterator next = i;
867                 ++next;
868                 change_note_channel (*i, new_channel);
869                 i = next;
870         }
871
872         apply_diff ();
873 }
874
875 void
876 MidiRegionView::show_list_editor ()
877 {
878         if (!_list_editor) {
879                 _list_editor = new MidiListEditor (trackview.session(), midi_region());
880         }
881         _list_editor->present ();
882 }
883
884 /** Add a note to the model, and the view, at a canvas (click) coordinate.
885  * \param x horizontal position in pixels
886  * \param y vertical position in pixels
887  * \param length duration of the note in beats, which will be snapped to the grid
888  * \param sh true to make the note 1 frame shorter than the snapped version of \a length.
889  * \param snap_x true to snap x to the grid, otherwise false.
890  */
891 void
892 MidiRegionView::create_note_at (double x, double y, double length, bool sh, bool snap_x)
893 {
894         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
895         MidiStreamView* const view = mtv->midi_view();
896
897         double note = view->y_to_note(y);
898
899         assert(note >= 0.0);
900         assert(note <= 127.0);
901
902         // Start of note in frames relative to region start
903         framepos_t start_frames = trackview.editor().pixel_to_frame (x);
904         if (snap_x) {
905                 framecnt_t grid_frames;
906                 start_frames = snap_frame_to_grid_underneath (start_frames, grid_frames);
907         }
908         assert(start_frames >= 0);
909
910         // Snap length
911         length = region_frames_to_region_beats(
912                 snap_frame_to_frame (start_frames + region_beats_to_region_frames(length)) - start_frames);
913
914         assert (length != 0);
915
916         if (sh) {
917                 length = region_frames_to_region_beats (region_beats_to_region_frames (length) - 1);
918         }
919
920         const boost::shared_ptr<NoteType> new_note (new NoteType (mtv->get_channel_for_add (),
921                                                                   region_frames_to_region_beats(start_frames + _region->start()), length,
922                                                                   (uint8_t)note, 0x40));
923
924         if (_model->contains (new_note)) {
925                 return;
926         }
927
928         view->update_note_range(new_note->note());
929
930         MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note");
931         cmd->add (new_note);
932         _model->apply_command(*trackview.session(), cmd);
933
934         play_midi_note (new_note);
935 }
936
937 void
938 MidiRegionView::clear_events()
939 {
940         clear_selection();
941
942         MidiGhostRegion* gr;
943         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
944                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
945                         gr->clear_events();
946                 }
947         }
948
949         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
950                 delete *i;
951         }
952
953         _events.clear();
954         _patch_changes.clear();
955         _sys_exes.clear();
956         _optimization_iterator = _events.end();
957 }
958
959 void
960 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
961 {
962         _model = model;
963
964         content_connection.disconnect ();
965         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
966
967         clear_events ();
968
969         if (_enable_display) {
970                 redisplay_model();
971         }
972 }
973
974 void
975 MidiRegionView::start_note_diff_command (string name)
976 {
977         if (!_note_diff_command) {
978                 _note_diff_command = _model->new_note_diff_command (name);
979         }
980 }
981
982 void
983 MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
984 {
985         if (_note_diff_command) {
986                 _note_diff_command->add (note);
987         }
988         if (selected) {
989                 _marked_for_selection.insert(note);
990         }
991         if (show_velocity) {
992                 _marked_for_velocity.insert(note);
993         }
994 }
995
996 void
997 MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev)
998 {
999         if (_note_diff_command && ev->note()) {
1000                 _note_diff_command->remove(ev->note());
1001         }
1002 }
1003
1004 void
1005 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1006                                       MidiModel::NoteDiffCommand::Property property,
1007                                       uint8_t val)
1008 {
1009         if (_note_diff_command) {
1010                 _note_diff_command->change (ev->note(), property, val);
1011         }
1012 }
1013
1014 void
1015 MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
1016                                       MidiModel::NoteDiffCommand::Property property,
1017                                       Evoral::MusicalTime val)
1018 {
1019         if (_note_diff_command) {
1020                 _note_diff_command->change (ev->note(), property, val);
1021         }
1022 }
1023
1024 void
1025 MidiRegionView::apply_diff (bool as_subcommand)
1026 {
1027         bool add_or_remove;
1028
1029         if (!_note_diff_command) {
1030                 return;
1031         }
1032
1033         if ((add_or_remove = _note_diff_command->adds_or_removes())) {
1034                 // Mark all selected notes for selection when model reloads
1035                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1036                         _marked_for_selection.insert((*i)->note());
1037                 }
1038         }
1039
1040         if (as_subcommand) {
1041                 _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
1042         } else {
1043                 _model->apply_command (*trackview.session(), _note_diff_command);
1044         }
1045
1046         _note_diff_command = 0;
1047         midi_view()->midi_track()->playlist_modified();
1048
1049         if (add_or_remove) {
1050                 _marked_for_selection.clear();
1051         }
1052
1053         _marked_for_velocity.clear();
1054 }
1055
1056 void
1057 MidiRegionView::abort_command()
1058 {
1059         delete _note_diff_command;
1060         _note_diff_command = 0;
1061         clear_selection();
1062 }
1063
1064 CanvasNoteEvent*
1065 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
1066 {
1067         if (_optimization_iterator != _events.end()) {
1068                 ++_optimization_iterator;
1069         }
1070
1071         if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
1072                 return *_optimization_iterator;
1073         }
1074
1075         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
1076                 if ((*_optimization_iterator)->note() == note) {
1077                         return *_optimization_iterator;
1078                 }
1079         }
1080
1081         return 0;
1082 }
1083
1084 void
1085 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask)
1086 {
1087         MidiModel::Notes notes;
1088         _model->get_notes (notes, op, val, chan_mask);
1089
1090         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1091                 CanvasNoteEvent* cne = find_canvas_note (*n);
1092                 if (cne) {
1093                         e.push_back (cne);
1094                 }
1095         }
1096 }
1097
1098 void
1099 MidiRegionView::redisplay_model()
1100 {
1101         // Don't redisplay the model if we're currently recording and displaying that
1102         if (_active_notes) {
1103                 return;
1104         }
1105
1106         if (!_model) {
1107                 return;
1108         }
1109
1110         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1111                 (*i)->invalidate ();
1112         }
1113
1114         MidiModel::ReadLock lock(_model->read_lock());
1115
1116         MidiModel::Notes& notes (_model->notes());
1117         _optimization_iterator = _events.begin();
1118
1119         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1120
1121                 boost::shared_ptr<NoteType> note (*n);
1122                 CanvasNoteEvent* cne;
1123                 bool visible;
1124
1125                 if (note_in_region_range (note, visible)) {
1126
1127                         if ((cne = find_canvas_note (note)) != 0) {
1128
1129                                 cne->validate ();
1130
1131                                 CanvasNote* cn;
1132                                 CanvasHit* ch;
1133
1134                                 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
1135                                         update_note (cn);
1136                                 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
1137                                         update_hit (ch);
1138                                 }
1139
1140                                 if (visible) {
1141                                         cne->show ();
1142                                 } else {
1143                                         cne->hide ();
1144                                 }
1145
1146                         } else {
1147
1148                                 add_note (note, visible);
1149                         }
1150
1151                 } else {
1152
1153                         if ((cne = find_canvas_note (note)) != 0) {
1154                                 cne->validate ();
1155                                 cne->hide ();
1156                         }
1157                 }
1158         }
1159
1160
1161         /* remove note items that are no longer valid */
1162
1163         for (Events::iterator i = _events.begin(); i != _events.end(); ) {
1164                 if (!(*i)->valid ()) {
1165                         delete *i;
1166                         i = _events.erase (i);
1167                 } else {
1168                         ++i;
1169                 }
1170         }
1171
1172         _patch_changes.clear();
1173         _sys_exes.clear();
1174
1175         display_sysexes();
1176         display_patch_changes ();
1177
1178         _marked_for_selection.clear ();
1179         _marked_for_velocity.clear ();
1180
1181         /* we may have caused _events to contain things out of order (e.g. if a note
1182            moved earlier or later). we don't generally need them in time order, but
1183            make a note that a sort is required for those cases that require it.
1184         */
1185
1186         _sort_needed = true;
1187 }
1188
1189 void
1190 MidiRegionView::display_patch_changes ()
1191 {
1192         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1193         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
1194
1195         for (uint8_t i = 0; i < 16; ++i) {
1196                 if (chn_mask & (1<<i)) {
1197                         display_patch_changes_on_channel (i);
1198                 }
1199                 /* TODO gray-out patch instad of not displaying it */
1200         }
1201 }
1202
1203 void
1204 MidiRegionView::display_patch_changes_on_channel (uint8_t channel)
1205 {
1206         for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
1207
1208                 if ((*i)->channel() != channel) {
1209                         continue;
1210                 }
1211
1212                 MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ());
1213
1214                 boost::shared_ptr<MIDI::Name::Patch> patch =
1215                         MIDI::Name::MidiPatchManager::instance().find_patch(
1216                                 _model_name, _custom_device_mode, channel, patch_key);
1217
1218                 if (patch != 0) {
1219                         add_canvas_patch_change (*i, patch->name());
1220                 } else {
1221                         char buf[16];
1222                         /* program and bank numbers are zero-based: convert to one-based: MIDI_BP_ZERO */
1223                         snprintf (buf, 16, "%d %d", (*i)->program() + MIDI_BP_ZERO , (*i)->bank() + MIDI_BP_ZERO);
1224                         add_canvas_patch_change (*i, buf);
1225                 }
1226         }
1227 }
1228
1229 void
1230 MidiRegionView::display_sysexes()
1231 {
1232         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1233                 Evoral::MusicalTime time = (*i)->time();
1234                 assert(time >= 0);
1235
1236                 ostringstream str;
1237                 str << hex;
1238                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1239                         str << int((*i)->buffer()[b]);
1240                         if (b != (*i)->size() -1) {
1241                                 str << " ";
1242                         }
1243                 }
1244                 string text = str.str();
1245
1246                 const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time));
1247
1248                 double height = midi_stream_view()->contents_height();
1249
1250                 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1251                         new CanvasSysEx(*this, *_note_group, text, height, x, 1.0));
1252
1253                 // Show unless patch change is beyond the region bounds
1254                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1255                         sysex->hide();
1256                 } else {
1257                         sysex->show();
1258                 }
1259
1260                 _sys_exes.push_back(sysex);
1261         }
1262 }
1263
1264
1265 MidiRegionView::~MidiRegionView ()
1266 {
1267         in_destructor = true;
1268
1269         trackview.editor().verbose_cursor()->hide ();
1270
1271         note_delete_connection.disconnect ();
1272
1273         delete _list_editor;
1274
1275         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1276
1277         if (_active_notes) {
1278                 end_write();
1279         }
1280
1281         _selection.clear();
1282         clear_events();
1283
1284         delete _note_group;
1285         delete _note_diff_command;
1286         delete _step_edit_cursor;
1287         delete _temporary_note_group;
1288 }
1289
1290 void
1291 MidiRegionView::region_resized (const PropertyChange& what_changed)
1292 {
1293         RegionView::region_resized(what_changed);
1294
1295         if (what_changed.contains (ARDOUR::Properties::position)) {
1296                 set_duration(_region->length(), 0);
1297                 if (_enable_display) {
1298                         redisplay_model();
1299                 }
1300         }
1301 }
1302
1303 void
1304 MidiRegionView::reset_width_dependent_items (double pixel_width)
1305 {
1306         RegionView::reset_width_dependent_items(pixel_width);
1307         assert(_pixel_width == pixel_width);
1308
1309         if (_enable_display) {
1310                 redisplay_model();
1311         }
1312
1313         move_step_edit_cursor (_step_edit_cursor_position);
1314         set_step_edit_cursor_width (_step_edit_cursor_width);
1315 }
1316
1317 void
1318 MidiRegionView::set_height (double height)
1319 {
1320         static const double FUDGE = 2.0;
1321         const double old_height = _height;
1322         RegionView::set_height(height);
1323         _height = height - FUDGE;
1324
1325         apply_note_range(midi_stream_view()->lowest_note(),
1326                          midi_stream_view()->highest_note(),
1327                          height != old_height + FUDGE);
1328
1329         if (name_pixbuf) {
1330                 name_pixbuf->raise_to_top();
1331         }
1332
1333         for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
1334                 (*x)->set_height (midi_stream_view()->contents_height());
1335         }
1336
1337         if (_step_edit_cursor) {
1338                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
1339         }
1340 }
1341
1342
1343 /** Apply the current note range from the stream view
1344  * by repositioning/hiding notes as necessary
1345  */
1346 void
1347 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1348 {
1349         if (!_enable_display) {
1350                 return;
1351         }
1352
1353         if (!force && _current_range_min == min && _current_range_max == max) {
1354                 return;
1355         }
1356
1357         _current_range_min = min;
1358         _current_range_max = max;
1359
1360         for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1361                 CanvasNoteEvent* event = *i;
1362                 boost::shared_ptr<NoteType> note (event->note());
1363
1364                 if (note->note() < _current_range_min ||
1365                     note->note() > _current_range_max) {
1366                         event->hide();
1367                 } else {
1368                         event->show();
1369                 }
1370
1371                 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1372
1373                         const double y1 = midi_stream_view()->note_to_y(note->note());
1374                         const double y2 = y1 + floor(midi_stream_view()->note_height());
1375
1376                         cnote->property_y1() = y1;
1377                         cnote->property_y2() = y2;
1378
1379                 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1380
1381                         const double diamond_size = update_hit (chit);
1382
1383                         chit->set_height (diamond_size);
1384                 }
1385         }
1386 }
1387
1388 GhostRegion*
1389 MidiRegionView::add_ghost (TimeAxisView& tv)
1390 {
1391         CanvasNote* note;
1392
1393         double unit_position = _region->position () / samples_per_unit;
1394         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1395         MidiGhostRegion* ghost;
1396
1397         if (mtv && mtv->midi_view()) {
1398                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1399                    to allow having midi notes on top of note lines and waveforms.
1400                 */
1401                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1402         } else {
1403                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1404         }
1405
1406         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1407                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1408                         ghost->add_note(note);
1409                 }
1410         }
1411
1412         ghost->set_height ();
1413         ghost->set_duration (_region->length() / samples_per_unit);
1414         ghosts.push_back (ghost);
1415
1416         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1417
1418         return ghost;
1419 }
1420
1421
1422 /** Begin tracking note state for successive calls to add_event
1423  */
1424 void
1425 MidiRegionView::begin_write()
1426 {
1427         assert(!_active_notes);
1428         _active_notes = new CanvasNote*[128];
1429         for (unsigned i=0; i < 128; ++i) {
1430                 _active_notes[i] = 0;
1431         }
1432 }
1433
1434
1435 /** Destroy note state for add_event
1436  */
1437 void
1438 MidiRegionView::end_write()
1439 {
1440         delete[] _active_notes;
1441         _active_notes = 0;
1442         _marked_for_selection.clear();
1443         _marked_for_velocity.clear();
1444 }
1445
1446
1447 /** Resolve an active MIDI note (while recording).
1448  */
1449 void
1450 MidiRegionView::resolve_note(uint8_t note, double end_time)
1451 {
1452         if (midi_view()->note_mode() != Sustained) {
1453                 return;
1454         }
1455
1456         if (_active_notes && _active_notes[note]) {
1457
1458                 /* XXX is end_time really region-centric? I think so, because
1459                    this is a new region that we're recording, so source zero is
1460                    the same as region zero
1461                 */
1462                 const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
1463
1464                 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1465                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1466                 _active_notes[note] = 0;
1467         }
1468 }
1469
1470
1471 /** Extend active notes to rightmost edge of region (if length is changed)
1472  */
1473 void
1474 MidiRegionView::extend_active_notes()
1475 {
1476         if (!_active_notes) {
1477                 return;
1478         }
1479
1480         for (unsigned i=0; i < 128; ++i) {
1481                 if (_active_notes[i]) {
1482                         _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1483                 }
1484         }
1485 }
1486
1487
1488 void
1489 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1490 {
1491         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1492                 return;
1493         }
1494
1495         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1496
1497         if (!route_ui || !route_ui->midi_track()) {
1498                 return;
1499         }
1500
1501         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1502         np->add (note);
1503         np->play ();
1504 }
1505
1506 void
1507 MidiRegionView::play_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
1508 {
1509         if (_no_sound_notes || !trackview.editor().sound_notes()) {
1510                 return;
1511         }
1512
1513         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1514
1515         if (!route_ui || !route_ui->midi_track()) {
1516                 return;
1517         }
1518
1519         NotePlayer* np = new NotePlayer (route_ui->midi_track());
1520
1521         for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
1522                 np->add (*n);
1523         }
1524
1525         np->play ();
1526 }
1527
1528
1529 bool
1530 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
1531 {
1532         const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
1533         bool outside = (note_start_frames  < 0) || (note_start_frames > _region->last_frame());
1534
1535         visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1536                 (note->note() <= midi_stream_view()->highest_note());
1537
1538         return !outside;
1539 }
1540
1541 /** Update a canvas note's size from its model note.
1542  *  @param ev Canvas note to update.
1543  *  @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
1544  */
1545 void
1546 MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions)
1547 {
1548         boost::shared_ptr<NoteType> note = ev->note();
1549
1550         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time()));
1551         const double y1 = midi_stream_view()->note_to_y(note->note());
1552
1553         ev->property_x1() = x;
1554         ev->property_y1() = y1;
1555
1556         /* trim note display to not overlap the end of its region */
1557
1558         if (note->length() > 0) {
1559                 const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
1560                 ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames);
1561         } else {
1562                 ev->property_x2() = trackview.editor().frame_to_pixel (_region->length());
1563         }
1564
1565         ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1566
1567         if (note->length() == 0) {
1568                 if (_active_notes) {
1569                         assert(note->note() < 128);
1570                         // If this note is already active there's a stuck note,
1571                         // finish the old note rectangle
1572                         if (_active_notes[note->note()]) {
1573                                 CanvasNote* const old_rect = _active_notes[note->note()];
1574                                 boost::shared_ptr<NoteType> old_note = old_rect->note();
1575                                 old_rect->property_x2() = x;
1576                                 old_rect->property_outline_what() = (guint32) 0xF;
1577                         }
1578                         _active_notes[note->note()] = ev;
1579                 }
1580                 /* outline all but right edge */
1581                 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1582         } else {
1583                 /* outline all edges */
1584                 ev->property_outline_what() = (guint32) 0xF;
1585         }
1586
1587         if (update_ghost_regions) {
1588                 for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
1589                         MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
1590                         if (gr) {
1591                                 gr->update_note (ev);
1592                         }
1593                 }
1594         }
1595 }
1596
1597 double
1598 MidiRegionView::update_hit (CanvasHit* ev)
1599 {
1600         boost::shared_ptr<NoteType> note = ev->note();
1601
1602         const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
1603         const double x = trackview.editor().frame_to_pixel(note_start_frames);
1604         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1605         const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1606
1607         ev->move_to (x, y);
1608
1609         return diamond_size;
1610 }
1611
1612 /** Add a MIDI note to the view (with length).
1613  *
1614  * If in sustained mode, notes with length 0 will be considered active
1615  * notes, and resolve_note should be called when the corresponding note off
1616  * event arrives, to properly display the note.
1617  */
1618 void
1619 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1620 {
1621         CanvasNoteEvent* event = 0;
1622
1623         assert(note->time() >= 0);
1624         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1625
1626         //ArdourCanvas::Group* const group = (ArdourCanvas::Group*) get_canvas_group();
1627
1628         if (midi_view()->note_mode() == Sustained) {
1629
1630                 CanvasNote* ev_rect = new CanvasNote(*this, *_note_group, note);
1631
1632                 update_note (ev_rect);
1633
1634                 event = ev_rect;
1635
1636                 MidiGhostRegion* gr;
1637
1638                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1639                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1640                                 gr->add_note(ev_rect);
1641                         }
1642                 }
1643
1644         } else if (midi_view()->note_mode() == Percussive) {
1645
1646                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1647
1648                 CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note);
1649
1650                 update_hit (ev_diamond);
1651
1652                 event = ev_diamond;
1653
1654         } else {
1655                 event = 0;
1656         }
1657
1658         if (event) {
1659                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1660                         note_selected(event, true);
1661                 }
1662
1663                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1664                         event->show_velocity();
1665                 }
1666
1667                 event->on_channel_selection_change(_last_channel_selection);
1668                 _events.push_back(event);
1669
1670                 if (visible) {
1671                         event->show();
1672                 } else {
1673                         event->hide ();
1674                 }
1675         }
1676
1677         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1678         MidiStreamView* const view = mtv->midi_view();
1679
1680         view->update_note_range (note->note());
1681 }
1682
1683 void
1684 MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1685                                Evoral::MusicalTime pos, Evoral::MusicalTime len)
1686 {
1687         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1688
1689         /* potentially extend region to hold new note */
1690
1691         framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
1692         framepos_t region_end = _region->last_frame();
1693
1694         if (end_frame > region_end) {
1695                 _region->set_length (end_frame - _region->position());
1696         }
1697
1698         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1699         MidiStreamView* const view = mtv->midi_view();
1700
1701         view->update_note_range(new_note->note());
1702
1703         _marked_for_selection.clear ();
1704         clear_selection ();
1705
1706         start_note_diff_command (_("step add"));
1707         note_diff_add_note (new_note, true, false);
1708         apply_diff();
1709
1710         // last_step_edit_note = new_note;
1711 }
1712
1713 void
1714 MidiRegionView::step_sustain (Evoral::MusicalTime beats)
1715 {
1716         change_note_lengths (false, false, beats, false, true);
1717 }
1718
1719 void
1720 MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext)
1721 {
1722         assert (patch->time() >= 0);
1723
1724         const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (patch->time()));
1725
1726         double const height = midi_stream_view()->contents_height();
1727
1728         boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>(
1729                 new CanvasPatchChange(*this, *_note_group,
1730                                       displaytext,
1731                                       height,
1732                                       x, 1.0,
1733                                       _model_name,
1734                                       _custom_device_mode,
1735                                       patch)
1736                           );
1737
1738         // Show unless patch change is beyond the region bounds
1739         if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) {
1740                 patch_change->hide();
1741         } else {
1742                 patch_change->show();
1743         }
1744
1745         _patch_changes.push_back (patch_change);
1746 }
1747
1748 void
1749 MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1750 {
1751         MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
1752         while (i != _model->patch_changes().end() && (*i)->channel() != channel) {
1753                 ++i;
1754         }
1755
1756         if (i != _model->patch_changes().end()) {
1757                 key.msb = (*i)->bank_msb ();
1758                 key.lsb = (*i)->bank_lsb ();
1759                 key.program_number = (*i)->program ();
1760         } else {
1761                 key.msb = key.lsb = key.program_number = 0;
1762         }
1763
1764         assert (key.is_sane());
1765 }
1766
1767
1768 void
1769 MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
1770 {
1771         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1772
1773         if (pc.patch()->program() != new_patch.program_number) {
1774                 c->change_program (pc.patch (), new_patch.program_number);
1775         }
1776
1777         int const new_bank = (new_patch.msb << 7) | new_patch.lsb;
1778         if (pc.patch()->bank() != new_bank) {
1779                 c->change_bank (pc.patch (), new_bank);
1780         }
1781
1782         _model->apply_command (*trackview.session(), c);
1783
1784         _patch_changes.clear ();
1785         display_patch_changes ();
1786 }
1787
1788 void
1789 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change)
1790 {
1791         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
1792
1793         if (old_change->time() != new_change.time()) {
1794                 c->change_time (old_change, new_change.time());
1795         }
1796
1797         if (old_change->channel() != new_change.channel()) {
1798                 c->change_channel (old_change, new_change.channel());
1799         }
1800
1801         if (old_change->program() != new_change.program()) {
1802                 c->change_program (old_change, new_change.program());
1803         }
1804
1805         if (old_change->bank() != new_change.bank()) {
1806                 c->change_bank (old_change, new_change.bank());
1807         }
1808
1809         _model->apply_command (*trackview.session(), c);
1810
1811         _patch_changes.clear ();
1812         display_patch_changes ();
1813 }
1814
1815 /** Add a patch change to the region.
1816  *  @param t Time in frames relative to region position
1817  *  @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
1818  *  MidiTimeAxisView::get_channel_for_add())
1819  */
1820 void
1821 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch)
1822 {
1823         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
1824
1825         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
1826         c->add (MidiModel::PatchChangePtr (
1827                         new Evoral::PatchChange<Evoral::MusicalTime> (
1828                                 absolute_frames_to_source_beats (_region->position() + t),
1829                                 mtv->get_channel_for_add(), patch.program(), patch.bank()
1830                                 )
1831                         )
1832                 );
1833
1834         _model->apply_command (*trackview.session(), c);
1835
1836         _patch_changes.clear ();
1837         display_patch_changes ();
1838 }
1839
1840 void
1841 MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t)
1842 {
1843         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
1844         c->change_time (pc.patch (), t);
1845         _model->apply_command (*trackview.session(), c);
1846
1847         _patch_changes.clear ();
1848         display_patch_changes ();
1849 }
1850
1851 void
1852 MidiRegionView::delete_patch_change (CanvasPatchChange* pc)
1853 {
1854         MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
1855         c->remove (pc->patch ());
1856         _model->apply_command (*trackview.session(), c);
1857
1858         _patch_changes.clear ();
1859         display_patch_changes ();
1860 }
1861
1862 void
1863 MidiRegionView::previous_patch (CanvasPatchChange& patch)
1864 {
1865         if (patch.patch()->program() < 127) {
1866                 MIDI::Name::PatchPrimaryKey key;
1867                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1868                 key.program_number++;
1869                 change_patch_change (patch, key);
1870         }
1871 }
1872
1873 void
1874 MidiRegionView::next_patch (CanvasPatchChange& patch)
1875 {
1876         if (patch.patch()->program() > 0) {
1877                 MIDI::Name::PatchPrimaryKey key;
1878                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1879                 key.program_number--;
1880                 change_patch_change (patch, key);
1881         }
1882 }
1883
1884 void
1885 MidiRegionView::previous_bank (CanvasPatchChange& patch)
1886 {
1887         if (patch.patch()->program() < 127) {
1888                 MIDI::Name::PatchPrimaryKey key;
1889                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1890                 if (key.lsb > 0) {
1891                         key.lsb--;
1892                         change_patch_change (patch, key);
1893                 } else {
1894                         if (key.msb > 0) {
1895                                 key.lsb = 127;
1896                                 key.msb--;
1897                                 change_patch_change (patch, key);
1898                         }
1899                 }
1900         }
1901 }
1902
1903 void
1904 MidiRegionView::next_bank (CanvasPatchChange& patch)
1905 {
1906         if (patch.patch()->program() > 0) {
1907                 MIDI::Name::PatchPrimaryKey key;
1908                 get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key);
1909                 if (key.lsb < 127) {
1910                         key.lsb++;
1911                         change_patch_change (patch, key);
1912                 } else {
1913                         if (key.msb < 127) {
1914                                 key.lsb = 0;
1915                                 key.msb++;
1916                                 change_patch_change (patch, key);
1917                         }
1918                 }
1919         }
1920 }
1921
1922 void
1923 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1924 {
1925         if (_selection.empty()) {
1926                 return;
1927         }
1928
1929         _selection.erase (cne);
1930 }
1931
1932 void
1933 MidiRegionView::delete_selection()
1934 {
1935         if (_selection.empty()) {
1936                 return;
1937         }
1938
1939         start_note_diff_command (_("delete selection"));
1940
1941         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1942                 if ((*i)->selected()) {
1943                         _note_diff_command->remove((*i)->note());
1944                 }
1945         }
1946
1947         _selection.clear();
1948
1949         apply_diff ();
1950 }
1951
1952 void
1953 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1954 {
1955         start_note_diff_command (_("delete note"));
1956         _note_diff_command->remove (n);
1957         apply_diff ();
1958
1959         trackview.editor().verbose_cursor()->hide ();
1960 }
1961
1962 void
1963 MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev)
1964 {
1965         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1966                 if ((*i) != ev) {
1967                         Selection::iterator tmp = i;
1968                         ++tmp;
1969
1970                         (*i)->set_selected (false);
1971                         (*i)->hide_velocity ();
1972                         _selection.erase (i);
1973                         
1974                         i = tmp;
1975                 } else {
1976                         ++i;
1977                 }
1978         }
1979
1980         /* this does not change the status of this regionview w.r.t the editor
1981            selection.
1982         */
1983 }
1984
1985 void
1986 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1987 {
1988         clear_selection_except (ev);
1989
1990         /* don't bother with checking to see if we should remove this
1991            regionview from the editor selection, since we're about to add
1992            another note, and thus put/keep this regionview in the editor
1993            selection anyway.
1994         */
1995
1996         if (!ev->selected()) {
1997                 add_to_selection (ev);
1998         }
1999 }
2000
2001 void
2002 MidiRegionView::select_all_notes ()
2003 {
2004         clear_selection ();
2005
2006         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2007                 add_to_selection (*i);
2008         }
2009 }
2010
2011 void
2012 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
2013 {
2014         uint8_t low_note = 127;
2015         uint8_t high_note = 0;
2016         MidiModel::Notes& notes (_model->notes());
2017         _optimization_iterator = _events.begin();
2018
2019         if (!add) {
2020                 clear_selection ();
2021         }
2022
2023         if (extend && _selection.empty()) {
2024                 extend = false;
2025         }
2026
2027         if (extend) {
2028
2029                 /* scan existing selection to get note range */
2030
2031                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2032                         if ((*i)->note()->note() < low_note) {
2033                                 low_note = (*i)->note()->note();
2034                         }
2035                         if ((*i)->note()->note() > high_note) {
2036                                 high_note = (*i)->note()->note();
2037                         }
2038                 }
2039
2040                 low_note = min (low_note, notenum);
2041                 high_note = max (high_note, notenum);
2042         }
2043
2044         _no_sound_notes = true;
2045
2046         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2047
2048                 boost::shared_ptr<NoteType> note (*n);
2049                 CanvasNoteEvent* cne;
2050                 bool select = false;
2051
2052                 if (((1 << note->channel()) & channel_mask) != 0) {
2053                         if (extend) {
2054                                 if ((note->note() >= low_note && note->note() <= high_note)) {
2055                                         select = true;
2056                                 }
2057                         } else if (note->note() == notenum) {
2058                                 select = true;
2059                         }
2060                 }
2061
2062                 if (select) {
2063                         if ((cne = find_canvas_note (note)) != 0) {
2064                                 // extend is false because we've taken care of it,
2065                                 // since it extends by time range, not pitch.
2066                                 note_selected (cne, add, false);
2067                         }
2068                 }
2069
2070                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
2071
2072         }
2073
2074         _no_sound_notes = false;
2075 }
2076
2077 void
2078 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
2079 {
2080         MidiModel::Notes& notes (_model->notes());
2081         _optimization_iterator = _events.begin();
2082
2083         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
2084
2085                 boost::shared_ptr<NoteType> note (*n);
2086                 CanvasNoteEvent* cne;
2087
2088                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
2089                         if ((cne = find_canvas_note (note)) != 0) {
2090                                 if (cne->selected()) {
2091                                         note_deselected (cne);
2092                                 } else {
2093                                         note_selected (cne, true, false);
2094                                 }
2095                         }
2096                 }
2097         }
2098 }
2099
2100 void
2101 MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
2102 {
2103         if (!add) {
2104                 clear_selection_except (ev);
2105                 if (!_selection.empty()) {
2106                         PublicEditor& editor (trackview.editor());
2107                         editor.get_selection().add (this);
2108                 }
2109         }
2110
2111         if (!extend) {
2112
2113                 if (!ev->selected()) {
2114                         add_to_selection (ev);
2115                 }
2116
2117         } else {
2118                 /* find end of latest note selected, select all between that and the start of "ev" */
2119
2120                 Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2121                 Evoral::MusicalTime latest = 0;
2122
2123                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2124                         if ((*i)->note()->end_time() > latest) {
2125                                 latest = (*i)->note()->end_time();
2126                         }
2127                         if ((*i)->note()->time() < earliest) {
2128                                 earliest = (*i)->note()->time();
2129                         }
2130                 }
2131
2132                 if (ev->note()->end_time() > latest) {
2133                         latest = ev->note()->end_time();
2134                 }
2135
2136                 if (ev->note()->time() < earliest) {
2137                         earliest = ev->note()->time();
2138                 }
2139
2140                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2141
2142                         /* find notes entirely within OR spanning the earliest..latest range */
2143
2144                         if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
2145                             ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
2146                                 add_to_selection (*i);
2147                         }
2148
2149                 }
2150         }
2151 }
2152
2153 void
2154 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
2155 {
2156         remove_from_selection (ev);
2157 }
2158
2159 void
2160 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend)
2161 {
2162         if (x1 > x2) {
2163                 swap (x1, x2);
2164         }
2165
2166         if (y1 > y2) {
2167                 swap (y1, y2);
2168         }
2169
2170         // TODO: Make this faster by storing the last updated selection rect, and only
2171         // adjusting things that are in the area that appears/disappeared.
2172         // We probably need a tree to be able to find events in O(log(n)) time.
2173
2174         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2175
2176                 /* check if any corner of the note is inside the rect
2177
2178                    Notes:
2179                    1) this is computing "touched by", not "contained by" the rect.
2180                    2) this does not require that events be sorted in time.
2181                 */
2182
2183                 const double ix1 = (*i)->x1();
2184                 const double ix2 = (*i)->x2();
2185                 const double iy1 = (*i)->y1();
2186                 const double iy2 = (*i)->y2();
2187
2188                 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2189                     (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
2190                     (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
2191                     (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
2192
2193                         // Inside rectangle
2194                         if (!(*i)->selected()) {
2195                                 add_to_selection (*i);
2196                         }
2197                 } else if ((*i)->selected() && !extend) {
2198                         // Not inside rectangle
2199                         remove_from_selection (*i);
2200                 }
2201         }
2202 }
2203
2204 void
2205 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
2206 {
2207         Selection::iterator i = _selection.find (ev);
2208
2209         if (i != _selection.end()) {
2210                 _selection.erase (i);
2211         }
2212
2213         ev->set_selected (false);
2214         ev->hide_velocity ();
2215
2216         if (_selection.empty()) {
2217                 PublicEditor& editor (trackview.editor());
2218                 editor.get_selection().remove (this);
2219         }
2220 }
2221
2222 void
2223 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
2224 {
2225         bool add_mrv_selection = false;
2226
2227         if (_selection.empty()) {
2228                 add_mrv_selection = true;
2229         }
2230
2231         if (_selection.insert (ev).second) {
2232                 ev->set_selected (true);
2233                 play_midi_note ((ev)->note());
2234         }
2235
2236         if (add_mrv_selection) {
2237                 PublicEditor& editor (trackview.editor());
2238                 editor.get_selection().add (this);
2239         }
2240 }
2241
2242 void
2243 MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
2244 {
2245         typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
2246         PossibleChord to_play;
2247         Evoral::MusicalTime earliest = Evoral::MaxMusicalTime;
2248
2249         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2250                 if ((*i)->note()->time() < earliest) {
2251                         earliest = (*i)->note()->time();
2252                 }
2253         }
2254
2255         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2256                 if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) {
2257                         to_play.push_back ((*i)->note());
2258                 }
2259                 (*i)->move_event(dx, dy);
2260         }
2261
2262         if (dy && !_selection.empty() && !_no_sound_notes && trackview.editor().sound_notes()) {
2263
2264                 if (to_play.size() > 1) {
2265
2266                         PossibleChord shifted;
2267
2268                         for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
2269                                 boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
2270                                 moved_note->set_note (moved_note->note() + cumulative_dy);
2271                                 shifted.push_back (moved_note);
2272                         }
2273
2274                         play_midi_chord (shifted);
2275
2276                 } else if (!to_play.empty()) {
2277
2278                         boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
2279                         moved_note->set_note (moved_note->note() + cumulative_dy);
2280                         play_midi_note (moved_note);
2281                 }
2282         }
2283 }
2284
2285 void
2286 MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote)
2287 {
2288         assert (!_selection.empty());
2289
2290         uint8_t lowest_note_in_selection  = 127;
2291         uint8_t highest_note_in_selection = 0;
2292         uint8_t highest_note_difference = 0;
2293
2294         // find highest and lowest notes first
2295
2296         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2297                 uint8_t pitch = (*i)->note()->note();
2298                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
2299                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
2300         }
2301
2302         /*
2303           cerr << "dnote: " << (int) dnote << endl;
2304           cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
2305           << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
2306           cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
2307           << int(highest_note_in_selection) << endl;
2308           cerr << "selection size: " << _selection.size() << endl;
2309           cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
2310         */
2311
2312         // Make sure the note pitch does not exceed the MIDI standard range
2313         if (highest_note_in_selection + dnote > 127) {
2314                 highest_note_difference = highest_note_in_selection - 127;
2315         }
2316
2317         start_note_diff_command (_("move notes"));
2318
2319         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
2320
2321                 Evoral::MusicalTime new_time = absolute_frames_to_source_beats (source_beats_to_absolute_frames ((*i)->note()->time()) + dt);
2322
2323                 if (new_time < 0) {
2324                         continue;
2325                 }
2326
2327                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
2328
2329                 uint8_t original_pitch = (*i)->note()->note();
2330                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
2331
2332                 // keep notes in standard midi range
2333                 clamp_to_0_127(new_pitch);
2334
2335                 // keep original pitch if note is dragged outside valid midi range
2336                 if ((original_pitch != 0 && new_pitch == 0)
2337                     || (original_pitch != 127 && new_pitch == 127)) {
2338                         new_pitch = original_pitch;
2339                 }
2340
2341                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
2342                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
2343
2344                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
2345         }
2346
2347         apply_diff();
2348
2349         // care about notes being moved beyond the upper/lower bounds on the canvas
2350         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
2351             highest_note_in_selection > midi_stream_view()->highest_note()) {
2352                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
2353         }
2354 }
2355
2356 framepos_t
2357 MidiRegionView::snap_pixel_to_frame(double x)
2358 {
2359         PublicEditor& editor (trackview.editor());
2360         return snap_frame_to_frame (editor.pixel_to_frame (x));
2361 }
2362
2363 double
2364 MidiRegionView::snap_to_pixel(double x)
2365 {
2366         return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2367 }
2368
2369 double
2370 MidiRegionView::get_position_pixels()
2371 {
2372         framepos_t region_frame = get_position();
2373         return trackview.editor().frame_to_pixel(region_frame);
2374 }
2375
2376 double
2377 MidiRegionView::get_end_position_pixels()
2378 {
2379         framepos_t frame = get_position() + get_duration ();
2380         return trackview.editor().frame_to_pixel(frame);
2381 }
2382
2383 framepos_t
2384 MidiRegionView::source_beats_to_absolute_frames(double beats) const
2385 {
2386         /* the time converter will return the frame corresponding to `beats'
2387            relative to the start of the source. The start of the source
2388            is an implied position given by region->position - region->start
2389         */
2390         const framepos_t source_start = _region->position() - _region->start();
2391         return  source_start +  _source_relative_time_converter.to (beats);
2392 }
2393
2394 double
2395 MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
2396 {
2397         /* the `frames' argument needs to be converted into a frame count
2398            relative to the start of the source before being passed in to the
2399            converter.
2400         */
2401         const framepos_t source_start = _region->position() - _region->start();
2402         return  _source_relative_time_converter.from (frames - source_start);
2403 }
2404
2405 framepos_t
2406 MidiRegionView::region_beats_to_region_frames(double beats) const
2407 {
2408         return _region_relative_time_converter.to(beats);
2409 }
2410
2411 double
2412 MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
2413 {
2414         return _region_relative_time_converter.from(frames);
2415 }
2416
2417 void
2418 MidiRegionView::begin_resizing (bool /*at_front*/)
2419 {
2420         _resize_data.clear();
2421
2422         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2423                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2424
2425                 // only insert CanvasNotes into the map
2426                 if (note) {
2427                         NoteResizeData *resize_data = new NoteResizeData();
2428                         resize_data->canvas_note = note;
2429
2430                         // create a new SimpleRect from the note which will be the resize preview
2431                         SimpleRect *resize_rect = new SimpleRect(
2432                                 *_note_group, note->x1(), note->y1(), note->x2(), note->y2());
2433
2434                         // calculate the colors: get the color settings
2435                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2436                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2437                                 128);
2438
2439                         // make the resize preview notes more transparent and bright
2440                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2441
2442                         // calculate color based on note velocity
2443                         resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2444                                 CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()),
2445                                 fill_color,
2446                                 0.85);
2447
2448                         resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2449                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2450
2451                         resize_data->resize_rect = resize_rect;
2452                         _resize_data.push_back(resize_data);
2453                 }
2454         }
2455 }
2456
2457 /** Update resizing notes while user drags.
2458  * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
2459  * @param at_front which end of the note (true == note on, false == note off)
2460  * @param delta_x change in mouse position since the start of the drag
2461  * @param relative true if relative resizing is taking place, false if absolute resizing.  This only makes
2462  * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
2463  * amount of the drag.  In non-relative mode, all selected notes are set to have the same start or end point
2464  * as the \a primary note.
2465  */
2466 void
2467 MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2468 {
2469         bool cursor_set = false;
2470
2471         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2472                 SimpleRect* resize_rect = (*i)->resize_rect;
2473                 CanvasNote* canvas_note = (*i)->canvas_note;
2474                 double current_x;
2475
2476                 if (at_front) {
2477                         if (relative) {
2478                                 current_x = canvas_note->x1() + delta_x;
2479                         } else {
2480                                 current_x = primary->x1() + delta_x;
2481                         }
2482                 } else {
2483                         if (relative) {
2484                                 current_x = canvas_note->x2() + delta_x;
2485                         } else {
2486                                 current_x = primary->x2() + delta_x;
2487                         }
2488                 }
2489
2490                 if (at_front) {
2491                         resize_rect->property_x1() = snap_to_pixel(current_x);
2492                         resize_rect->property_x2() = canvas_note->x2();
2493                 } else {
2494                         resize_rect->property_x2() = snap_to_pixel(current_x);
2495                         resize_rect->property_x1() = canvas_note->x1();
2496                 }
2497
2498                 if (!cursor_set) {
2499                         double beats;
2500
2501                         beats = snap_pixel_to_frame (current_x);
2502                         /* XXX not sure this is correct - snap_pixel_to_frame()
2503                            returns an absolute frame.
2504                         */
2505                         beats = region_frames_to_region_beats (beats);
2506
2507                         double len;
2508
2509                         if (at_front) {
2510                                 if (beats < canvas_note->note()->end_time()) {
2511                                         len = canvas_note->note()->time() - beats;
2512                                         len += canvas_note->note()->length();
2513                                 } else {
2514                                         len = 0;
2515                                 }
2516                         } else {
2517                                 if (beats >= canvas_note->note()->time()) {
2518                                         len = beats - canvas_note->note()->time();
2519                                 } else {
2520                                         len = 0;
2521                                 }
2522                         }
2523
2524                         char buf[16];
2525                         snprintf (buf, sizeof (buf), "%.3g beats", len);
2526                         show_verbose_cursor (buf, 0, 0);
2527
2528                         cursor_set = true;
2529                 }
2530
2531         }
2532 }
2533
2534
2535 /** Finish resizing notes when the user releases the mouse button.
2536  *  Parameters the same as for \a update_resizing().
2537  */
2538 void
2539 MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative)
2540 {
2541         start_note_diff_command (_("resize notes"));
2542
2543         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2544                 CanvasNote*  canvas_note = (*i)->canvas_note;
2545                 SimpleRect*  resize_rect = (*i)->resize_rect;
2546
2547                 /* Get the new x position for this resize, which is in pixels relative
2548                  * to the region position.
2549                  */
2550                 
2551                 double current_x;
2552
2553                 if (at_front) {
2554                         if (relative) {
2555                                 current_x = canvas_note->x1() + delta_x;
2556                         } else {
2557                                 current_x = primary->x1() + delta_x;
2558                         }
2559                 } else {
2560                         if (relative) {
2561                                 current_x = canvas_note->x2() + delta_x;
2562                         } else {
2563                                 current_x = primary->x2() + delta_x;
2564                         }
2565                 }
2566
2567                 /* Convert that to a frame within the region */
2568                 current_x = snap_pixel_to_frame (current_x) + _region->start ();
2569
2570                 /* and then to beats */
2571                 /* XXX not sure this is correct - snap_pixel_to_frame()
2572                    returns an absolute frame.
2573                 */
2574                 current_x = region_frames_to_region_beats (current_x);
2575
2576                 if (at_front && current_x < canvas_note->note()->end_time()) {
2577                         note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x);
2578
2579                         double len = canvas_note->note()->time() - current_x;
2580                         len += canvas_note->note()->length();
2581
2582                         if (len > 0) {
2583                                 /* XXX convert to beats */
2584                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2585                         }
2586                 }
2587
2588                 if (!at_front) {
2589                         double len = current_x - canvas_note->note()->time();
2590
2591                         if (len > 0) {
2592                                 /* XXX convert to beats */
2593                                 note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
2594                         }
2595                 }
2596
2597                 delete resize_rect;
2598                 delete (*i);
2599         }
2600
2601         _resize_data.clear();
2602         apply_diff();
2603 }
2604
2605 void
2606 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2607 {
2608         uint8_t new_velocity;
2609
2610         if (relative) {
2611                 new_velocity = event->note()->velocity() + velocity;
2612                 clamp_to_0_127(new_velocity);
2613         } else {
2614                 new_velocity = velocity;
2615         }
2616
2617         event->set_selected (event->selected()); // change color
2618
2619         note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
2620 }
2621
2622 void
2623 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2624 {
2625         uint8_t new_note;
2626
2627         if (relative) {
2628                 new_note = event->note()->note() + note;
2629         } else {
2630                 new_note = note;
2631         }
2632
2633         clamp_to_0_127 (new_note);
2634         note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
2635 }
2636
2637 void
2638 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2639 {
2640         bool change_start = false;
2641         bool change_length = false;
2642         Evoral::MusicalTime new_start = 0;
2643         Evoral::MusicalTime new_length = 0;
2644
2645         /* NOTE: the semantics of the two delta arguments are slightly subtle:
2646
2647            front_delta: if positive - move the start of the note later in time (shortening it)
2648            if negative - move the start of the note earlier in time (lengthening it)
2649
2650            end_delta:   if positive - move the end of the note later in time (lengthening it)
2651            if negative - move the end of the note earlier in time (shortening it)
2652         */
2653
2654         if (front_delta) {
2655                 if (front_delta < 0) {
2656
2657                         if (event->note()->time() < -front_delta) {
2658                                 new_start = 0;
2659                         } else {
2660                                 new_start = event->note()->time() + front_delta; // moves earlier
2661                         }
2662
2663                         /* start moved toward zero, so move the end point out to where it used to be.
2664                            Note that front_delta is negative, so this increases the length.
2665                         */
2666
2667                         new_length = event->note()->length() - front_delta;
2668                         change_start = true;
2669                         change_length = true;
2670
2671                 } else {
2672
2673                         Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2674
2675                         if (new_pos < event->note()->end_time()) {
2676                                 new_start = event->note()->time() + front_delta;
2677                                 /* start moved toward the end, so move the end point back to where it used to be */
2678                                 new_length = event->note()->length() - front_delta;
2679                                 change_start = true;
2680                                 change_length = true;
2681                         }
2682                 }
2683
2684         }
2685
2686         if (end_delta) {
2687                 bool can_change = true;
2688                 if (end_delta < 0) {
2689                         if (event->note()->length() < -end_delta) {
2690                                 can_change = false;
2691                         }
2692                 }
2693
2694                 if (can_change) {
2695                         new_length = event->note()->length() + end_delta;
2696                         change_length = true;
2697                 }
2698         }
2699
2700         if (change_start) {
2701                 note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
2702         }
2703
2704         if (change_length) {
2705                 note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
2706         }
2707 }
2708
2709 void
2710 MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative)
2711 {
2712         uint8_t new_channel;
2713
2714         if (relative) {
2715                 if (chn < 0.0) {
2716                         if (event->note()->channel() < -chn) {
2717                                 new_channel = 0;
2718                         } else {
2719                                 new_channel = event->note()->channel() + chn;
2720                         }
2721                 } else {
2722                         new_channel = event->note()->channel() + chn;
2723                 }
2724         } else {
2725                 new_channel = (uint8_t) chn;
2726         }
2727
2728         note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
2729 }
2730
2731 void
2732 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2733 {
2734         Evoral::MusicalTime new_time;
2735
2736         if (relative) {
2737                 if (delta < 0.0) {
2738                         if (event->note()->time() < -delta) {
2739                                 new_time = 0;
2740                         } else {
2741                                 new_time = event->note()->time() + delta;
2742                         }
2743                 } else {
2744                         new_time = event->note()->time() + delta;
2745                 }
2746         } else {
2747                 new_time = delta;
2748         }
2749
2750         note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
2751 }
2752
2753 void
2754 MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime t)
2755 {
2756         note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
2757 }
2758
2759 void
2760 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2761 {
2762         int8_t delta;
2763
2764         if (_selection.empty()) {
2765                 return;
2766         }
2767
2768         if (fine) {
2769                 delta = 1;
2770         } else {
2771                 delta = 10;
2772         }
2773
2774         if (!up) {
2775                 delta = -delta;
2776         }
2777
2778         if (!allow_smush) {
2779                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2780                         if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2781                                 return;
2782                         }
2783                 }
2784         }
2785
2786         start_note_diff_command (_("change velocities"));
2787
2788         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2789                 Selection::iterator next = i;
2790                 ++next;
2791                 change_note_velocity (*i, delta, true);
2792                 i = next;
2793         }
2794
2795         apply_diff();
2796
2797         if (!_selection.empty()) {
2798                 char buf[24];
2799                 snprintf (buf, sizeof (buf), "Vel %d",
2800                           (int) (*_selection.begin())->note()->velocity());
2801                 show_verbose_cursor (buf, 10, 10);
2802         }
2803 }
2804
2805
2806 void
2807 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2808 {
2809         if (_selection.empty()) {
2810                 return;
2811         }
2812
2813         int8_t delta;
2814
2815         if (fine) {
2816                 delta = 1;
2817         } else {
2818                 delta = 12;
2819         }
2820
2821         if (!up) {
2822                 delta = -delta;
2823         }
2824
2825         if (!allow_smush) {
2826                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2827                         if (!up) {
2828                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2829                                         return;
2830                                 }
2831                         } else {
2832                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
2833                                         return;
2834                                 }
2835                         }
2836                 }
2837         }
2838
2839         start_note_diff_command (_("transpose"));
2840
2841         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2842                 Selection::iterator next = i;
2843                 ++next;
2844                 change_note_note (*i, delta, true);
2845                 i = next;
2846         }
2847
2848         apply_diff ();
2849 }
2850
2851 void
2852 MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end)
2853 {
2854         if (delta == 0.0) {
2855                 if (fine) {
2856                         delta = 1.0/128.0;
2857                 } else {
2858                         /* grab the current grid distance */
2859                         bool success;
2860                         delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2861                         if (!success) {
2862                                 /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2863                                 cerr << "Grid type not available as beats - TO BE FIXED\n";
2864                                 return;
2865                         }
2866                 }
2867         }
2868
2869         if (shorter) {
2870                 delta = -delta;
2871         }
2872
2873         start_note_diff_command (_("change note lengths"));
2874
2875         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2876                 Selection::iterator next = i;
2877                 ++next;
2878
2879                 /* note the negation of the delta for start */
2880
2881                 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2882                 i = next;
2883         }
2884
2885         apply_diff ();
2886
2887 }
2888
2889 void
2890 MidiRegionView::nudge_notes (bool forward)
2891 {
2892         if (_selection.empty()) {
2893                 return;
2894         }
2895
2896         /* pick a note as the point along the timeline to get the nudge distance.
2897            its not necessarily the earliest note, so we may want to pull the notes out
2898            into a vector and sort before using the first one.
2899         */
2900
2901         framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
2902         framepos_t unused;
2903         framepos_t distance;
2904
2905         if (trackview.editor().snap_mode() == Editing::SnapOff) {
2906
2907                 /* grid is off - use nudge distance */
2908
2909                 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2910
2911         } else {
2912
2913                 /* use grid */
2914
2915                 framepos_t next_pos = ref_point;
2916
2917                 if (forward) {
2918                         if (max_framepos - 1 < next_pos) {
2919                                 next_pos += 1;
2920                         }
2921                 } else {
2922                         if (next_pos == 0) {
2923                                 return;
2924                         }
2925                         next_pos -= 1;
2926                 }
2927
2928                 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2929                 distance = ref_point - next_pos;
2930         }
2931
2932         if (distance == 0) {
2933                 return;
2934         }
2935
2936         Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance));
2937
2938         if (!forward) {
2939                 delta = -delta;
2940         }
2941
2942         start_note_diff_command (_("nudge"));
2943
2944         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2945                 Selection::iterator next = i;
2946                 ++next;
2947                 change_note_time (*i, delta, true);
2948                 i = next;
2949         }
2950
2951         apply_diff ();
2952 }
2953
2954 void
2955 MidiRegionView::change_channel(uint8_t channel)
2956 {
2957         start_note_diff_command(_("change channel"));
2958         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2959                 note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
2960         }
2961
2962         apply_diff();
2963 }
2964
2965
2966 void
2967 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2968 {
2969         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2970
2971         _pre_enter_cursor = editor->get_canvas_cursor ();
2972
2973         if (_mouse_state == SelectTouchDragging) {
2974                 note_selected (ev, true);
2975         }
2976
2977         show_verbose_cursor (ev->note ());
2978 }
2979
2980 void
2981 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
2982 {
2983         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
2984
2985         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2986                 (*i)->hide_velocity ();
2987         }
2988
2989         editor->verbose_cursor()->hide ();
2990
2991         if (_pre_enter_cursor) {
2992                 editor->set_canvas_cursor (_pre_enter_cursor);
2993                 _pre_enter_cursor = 0;
2994         }
2995 }
2996
2997 void
2998 MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev)
2999 {
3000         ostringstream s;
3001         /* XXX should get patch name if we can */
3002         s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1);
3003         show_verbose_cursor (s.str(), 10, 20);
3004 }
3005
3006 void
3007 MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *)
3008 {
3009         trackview.editor().verbose_cursor()->hide ();
3010 }
3011
3012 void
3013 MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
3014 {
3015         Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
3016
3017         if (x_fraction > 0.0 && x_fraction < 0.25) {
3018                 editor->set_canvas_cursor (editor->cursors()->left_side_trim);
3019         } else if (x_fraction >= 0.75 && x_fraction < 1.0) {
3020                 editor->set_canvas_cursor (editor->cursors()->right_side_trim);
3021         } else {
3022                 if (_pre_enter_cursor && can_set_cursor) {
3023                         editor->set_canvas_cursor (_pre_enter_cursor);
3024                 }
3025         }
3026 }
3027
3028 void
3029 MidiRegionView::set_frame_color()
3030 {
3031         uint32_t f;
3032
3033         TimeAxisViewItem::set_frame_color ();
3034
3035         if (!frame) {
3036                 return;
3037         }
3038
3039         if (_selected) {
3040                 f = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
3041         } else if (high_enough_for_name) {
3042                 f= ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
3043         } else {
3044                 f = fill_color;
3045         }
3046
3047         if (!rect_visible) {
3048                 f = UINT_RGBA_CHANGE_A (f, 0);
3049         }
3050
3051         frame->property_fill_color_rgba() = f;
3052 }
3053
3054 void
3055 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
3056 {
3057         switch (mode) {
3058         case AllChannels:
3059         case FilterChannels:
3060                 _force_channel = -1;
3061                 break;
3062         case ForceChannel:
3063                 _force_channel = mask;
3064                 mask = 0xFFFF; // Show all notes as active (below)
3065         };
3066
3067         // Update notes for selection
3068         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3069                 (*i)->on_channel_selection_change(mask);
3070         }
3071
3072         _last_channel_selection = mask;
3073
3074         _patch_changes.clear ();
3075         display_patch_changes ();
3076 }
3077
3078 void
3079 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
3080 {
3081         _model_name         = model;
3082         _custom_device_mode = custom_device_mode;
3083         redisplay_model();
3084 }
3085
3086 void
3087 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
3088 {
3089         if (_selection.empty()) {
3090                 return;
3091         }
3092
3093         PublicEditor& editor (trackview.editor());
3094
3095         switch (op) {
3096         case Delete:
3097                 /* XXX what to do ? */
3098                 break;
3099         case Cut:
3100         case Copy:
3101                 editor.get_cut_buffer().add (selection_as_cut_buffer());
3102                 break;
3103         default:
3104                 break;
3105         }
3106
3107         if (op != Copy) {
3108
3109                 start_note_diff_command();
3110
3111                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3112                         switch (op) {
3113                         case Copy:
3114                                 break;
3115                         case Delete:
3116                         case Cut:
3117                         case Clear:
3118                                 note_diff_remove_note (*i);
3119                                 break;
3120                         }
3121                 }
3122
3123                 apply_diff();
3124         }
3125 }
3126
3127 MidiCutBuffer*
3128 MidiRegionView::selection_as_cut_buffer () const
3129 {
3130         Notes notes;
3131
3132         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
3133                 NoteType* n = (*i)->note().get();
3134                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
3135         }
3136
3137         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
3138         cb->set (notes);
3139
3140         return cb;
3141 }
3142
3143 /** This method handles undo */
3144 void
3145 MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
3146 {
3147         if (mcb.empty()) {
3148                 return;
3149         }
3150
3151         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times));
3152
3153         trackview.session()->begin_reversible_command (_("paste"));
3154
3155         start_note_diff_command (_("paste"));
3156
3157         Evoral::MusicalTime beat_delta;
3158         Evoral::MusicalTime paste_pos_beats;
3159         Evoral::MusicalTime duration;
3160         Evoral::MusicalTime end_point = 0;
3161
3162         duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
3163         paste_pos_beats = region_frames_to_region_beats (pos - _region->position());
3164         beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
3165         paste_pos_beats = 0;
3166
3167         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n",
3168                                                        (*mcb.notes().begin())->time(),
3169                                                        (*mcb.notes().rbegin())->end_time(),
3170                                                        duration, pos, _region->position(),
3171                                                        paste_pos_beats, beat_delta));
3172
3173         clear_selection ();
3174
3175         for (int n = 0; n < (int) times; ++n) {
3176
3177                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
3178
3179                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
3180                         copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
3181
3182                         /* make all newly added notes selected */
3183
3184                         note_diff_add_note (copied_note, true);
3185                         end_point = copied_note->end_time();
3186                 }
3187
3188                 paste_pos_beats += duration;
3189         }
3190
3191         /* if we pasted past the current end of the region, extend the region */
3192
3193         framepos_t end_frame = source_beats_to_absolute_frames (end_point);
3194         framepos_t region_end = _region->position() + _region->length() - 1;
3195
3196         if (end_frame > region_end) {
3197
3198                 DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
3199
3200                 _region->clear_changes ();
3201                 _region->set_length (end_frame);
3202                 trackview.session()->add_command (new StatefulDiffCommand (_region));
3203         }
3204
3205         apply_diff (true);
3206
3207         trackview.session()->commit_reversible_command ();
3208 }
3209
3210 struct EventNoteTimeEarlyFirstComparator {
3211         bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
3212                 return a->note()->time() < b->note()->time();
3213         }
3214 };
3215
3216 void
3217 MidiRegionView::time_sort_events ()
3218 {
3219         if (!_sort_needed) {
3220                 return;
3221         }
3222
3223         EventNoteTimeEarlyFirstComparator cmp;
3224         _events.sort (cmp);
3225
3226         _sort_needed = false;
3227 }
3228
3229 void
3230 MidiRegionView::goto_next_note (bool add_to_selection)
3231 {
3232         bool use_next = false;
3233
3234         if (_events.back()->selected()) {
3235                 return;
3236         }
3237
3238         time_sort_events ();
3239
3240         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3241                 if ((*i)->selected()) {
3242                         use_next = true;
3243                         continue;
3244                 } else if (use_next) {
3245                         if (!add_to_selection) {
3246                                 unique_select (*i);
3247                         } else {
3248                                 note_selected (*i, true, false);
3249                         }
3250                         return;
3251                 }
3252         }
3253
3254         /* use the first one */
3255
3256         unique_select (_events.front());
3257
3258 }
3259
3260 void
3261 MidiRegionView::goto_previous_note (bool add_to_selection)
3262 {
3263         bool use_next = false;
3264
3265         if (_events.front()->selected()) {
3266                 return;
3267         }
3268
3269         time_sort_events ();
3270
3271         for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
3272                 if ((*i)->selected()) {
3273                         use_next = true;
3274                         continue;
3275                 } else if (use_next) {
3276                         if (!add_to_selection) {
3277                                 unique_select (*i);
3278                         } else {
3279                                 note_selected (*i, true, false);
3280                         }
3281                         return;
3282                 }
3283         }
3284
3285         /* use the last one */
3286
3287         unique_select (*(_events.rbegin()));
3288 }
3289
3290 void
3291 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
3292 {
3293         bool had_selected = false;
3294
3295         time_sort_events ();
3296
3297         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3298                 if ((*i)->selected()) {
3299                         selected.insert ((*i)->note());
3300                         had_selected = true;
3301                 }
3302         }
3303
3304         if (allow_all_if_none_selected && !had_selected) {
3305                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3306                         selected.insert ((*i)->note());
3307                 }
3308         }
3309 }
3310
3311 void
3312 MidiRegionView::update_ghost_note (double x, double y)
3313 {
3314         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3315
3316         _last_ghost_x = x;
3317         _last_ghost_y = y;
3318
3319         _note_group->w2i (x, y);
3320
3321         PublicEditor& editor = trackview.editor ();
3322         
3323         framepos_t const unsnapped_frame = editor.pixel_to_frame (x);
3324         framecnt_t grid_frames;
3325         framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
3326         
3327         /* use region_frames... because we are converting a delta within the region
3328         */
3329          
3330         double length = region_frames_to_region_beats (snap_frame_to_frame (f + grid_frames) - f);
3331
3332         /* note that this sets the time of the ghost note in beats relative to
3333            the start of the source; that is how all note times are stored.
3334         */
3335         _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ()));
3336         _ghost_note->note()->set_length (length);
3337         _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
3338         _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
3339
3340         /* the ghost note does not appear in ghost regions, so pass false in here */
3341         update_note (_ghost_note, false);
3342
3343         show_verbose_cursor (_ghost_note->note ());
3344 }
3345
3346 void
3347 MidiRegionView::create_ghost_note (double x, double y)
3348 {
3349         delete _ghost_note;
3350         _ghost_note = 0;
3351
3352         boost::shared_ptr<NoteType> g (new NoteType);
3353         _ghost_note = new NoEventCanvasNote (*this, *_note_group, g);
3354         _ghost_note->property_outline_color_rgba() = 0x000000aa;
3355         update_ghost_note (x, y);
3356         _ghost_note->show ();
3357
3358         _last_ghost_x = x;
3359         _last_ghost_y = y;
3360
3361         show_verbose_cursor (_ghost_note->note ());
3362 }
3363
3364 void
3365 MidiRegionView::snap_changed ()
3366 {
3367         if (!_ghost_note) {
3368                 return;
3369         }
3370
3371         create_ghost_note (_last_ghost_x, _last_ghost_y);
3372 }
3373
3374 void
3375 MidiRegionView::drop_down_keys ()
3376 {
3377         _mouse_state = None;
3378 }
3379
3380 void
3381 MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
3382 {
3383         double note = midi_stream_view()->y_to_note(y);
3384         Events e;
3385         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
3386
3387         uint16_t chn_mask = mtv->channel_selector().get_selected_channels();
3388
3389         if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
3390                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
3391         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
3392                 get_events (e, Evoral::Sequence<Evoral::MusicalTime>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
3393         } else {
3394                 return;
3395         }
3396
3397         bool add_mrv_selection = false;
3398
3399         if (_selection.empty()) {
3400                 add_mrv_selection = true;
3401         }
3402
3403         for (Events::iterator i = e.begin(); i != e.end(); ++i) {
3404                 if (_selection.insert (*i).second) {
3405                         (*i)->set_selected (true);
3406                 }
3407         }
3408
3409         if (add_mrv_selection) {
3410                 PublicEditor& editor (trackview.editor());
3411                 editor.get_selection().add (this);
3412         }
3413 }
3414
3415 void
3416 MidiRegionView::color_handler ()
3417 {
3418         RegionView::color_handler ();
3419
3420         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
3421                 (*i)->set_selected ((*i)->selected()); // will change color
3422         }
3423
3424         /* XXX probably more to do here */
3425 }
3426
3427 void
3428 MidiRegionView::enable_display (bool yn)
3429 {
3430         RegionView::enable_display (yn);
3431         if (yn) {
3432                 redisplay_model ();
3433         }
3434 }
3435
3436 void
3437 MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos)
3438 {
3439         if (_step_edit_cursor == 0) {
3440                 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
3441
3442                 _step_edit_cursor = new ArdourCanvas::SimpleRect (*group);
3443                 _step_edit_cursor->property_y1() = 0;
3444                 _step_edit_cursor->property_y2() = midi_stream_view()->contents_height();
3445                 _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90);
3446                 _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90);
3447         }
3448
3449         move_step_edit_cursor (pos);
3450         _step_edit_cursor->show ();
3451 }
3452
3453 void
3454 MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos)
3455 {
3456         _step_edit_cursor_position = pos;
3457
3458         if (_step_edit_cursor) {
3459                 double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos));
3460                 _step_edit_cursor->property_x1() = pixel;
3461                 set_step_edit_cursor_width (_step_edit_cursor_width);
3462         }
3463 }
3464
3465 void
3466 MidiRegionView::hide_step_edit_cursor ()
3467 {
3468         if (_step_edit_cursor) {
3469                 _step_edit_cursor->hide ();
3470         }
3471 }
3472
3473 void
3474 MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats)
3475 {
3476         _step_edit_cursor_width = beats;
3477
3478         if (_step_edit_cursor) {
3479                 _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats));
3480         }
3481 }
3482
3483 /** Called when a diskstream on our track has received some data.  Update the view, if applicable.
3484  *  @param buf Data that has been recorded.
3485  *  @param w Source that this data will end up in.
3486  */
3487 void
3488 MidiRegionView::data_recorded (boost::shared_ptr<MidiBuffer> buf, boost::weak_ptr<MidiSource> w)
3489 {
3490         if (!_active_notes) {
3491                 /* we aren't actively being recorded to */
3492                 return;
3493         }
3494
3495         boost::shared_ptr<MidiSource> src = w.lock ();
3496         if (!src || src != midi_region()->midi_source()) {
3497                 /* recorded data was not destined for our source */
3498                 return;
3499         }
3500
3501         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
3502         BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
3503
3504         framepos_t back = max_framepos;
3505
3506         for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
3507                 Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
3508                 assert (ev.buffer ());
3509
3510                 Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
3511
3512                 if (ev.type() == MIDI_CMD_NOTE_ON) {
3513
3514                         boost::shared_ptr<NoteType> note (
3515                                 new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity())
3516                                                           );
3517
3518                         add_note (note, true);
3519
3520                         /* fix up our note range */
3521                         if (ev.note() < _current_range_min) {
3522                                 midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
3523                         } else if (ev.note() > _current_range_max) {
3524                                 midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
3525                         }
3526
3527                 } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
3528                         resolve_note (ev.note (), time_beats);
3529                 }
3530
3531                 back = ev.time ();
3532         }
3533
3534         midi_stream_view()->check_record_layers (region(), back);
3535 }
3536
3537 void
3538 MidiRegionView::trim_front_starting ()
3539 {
3540         /* Reparent the note group to the region view's parent, so that it doesn't change
3541            when the region view is trimmed.
3542         */
3543         _temporary_note_group = new ArdourCanvas::Group (*group->property_parent ());
3544         _temporary_note_group->move (group->property_x(), group->property_y());
3545         _note_group->reparent (*_temporary_note_group);
3546 }
3547
3548 void
3549 MidiRegionView::trim_front_ending ()
3550 {
3551         _note_group->reparent (*group);
3552         delete _temporary_note_group;
3553         _temporary_note_group = 0;
3554
3555         if (_region->start() < 0) {
3556                 /* Trim drag made start time -ve; fix this */
3557                 midi_region()->fix_negative_start ();
3558         }
3559 }
3560
3561 void
3562 MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc)
3563 {
3564         PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY);
3565         if (d.run () != Gtk::RESPONSE_ACCEPT) {
3566                 return;
3567         }
3568
3569         change_patch_change (pc->patch(), d.patch ());
3570 }
3571
3572
3573 void
3574 MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
3575 {
3576         char buf[24];
3577         snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d",
3578                   Evoral::midi_note_name (n->note()).c_str(),
3579                   (int) n->note (),
3580                   (int) n->channel() + 1,
3581                   (int) n->velocity());
3582
3583         show_verbose_cursor (buf, 10, 20);
3584 }
3585
3586 void
3587 MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
3588 {
3589         double wx, wy;
3590
3591         trackview.editor().get_pointer_position (wx, wy);
3592
3593         wx += xoffset;
3594         wy += yoffset;
3595
3596         /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */
3597
3598         double x1, y1, x2, y2;
3599         trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2);
3600
3601         if ((wy + y2 - y1) > trackview.editor().canvas_height()) {
3602                 wy -= (y2 - y1) + 2 * yoffset;
3603         }
3604
3605         trackview.editor().verbose_cursor()->set (text, wx, wy);
3606         trackview.editor().verbose_cursor()->show ();
3607 }
3608
3609 /** @param p A session framepos.
3610  *  @param grid_frames Filled in with the number of frames that a grid interval is at p.
3611  *  @return p snapped to the grid subdivision underneath it.
3612  */
3613 framepos_t
3614 MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
3615 {
3616         PublicEditor& editor = trackview.editor ();
3617         
3618         bool success;
3619         Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p);
3620
3621         if (!success) {
3622                 grid_beats = 1;
3623         }
3624         
3625         grid_frames = region_beats_to_region_frames (grid_beats);
3626
3627         /* Hack so that we always snap to the note that we are over, instead of snapping
3628            to the next one if we're more than halfway through the one we're over.
3629         */
3630         if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
3631                 p -= grid_frames / 2;
3632         }
3633
3634         return snap_frame_to_frame (p);
3635 }