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