change color of note velocity text (and don't set it or justification repeatedly...
[ardour.git] / gtk2_ardour / midi_region_view.cc
1 /*
2     Copyright (C) 2001-2007 Paul Davis
3     Author: Dave Robillard
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <cmath>
21 #include <cassert>
22 #include <algorithm>
23 #include <ostream>
24
25 #include <gtkmm.h>
26
27 #include <gtkmm2ext/gtk_ui.h>
28
29 #include <sigc++/signal.h>
30
31 #include "pbd/memento_command.h"
32 #include "pbd/stateful_diff_command.h"
33
34 #include "ardour/playlist.h"
35 #include "ardour/tempo.h"
36 #include "ardour/midi_region.h"
37 #include "ardour/midi_source.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_patch_manager.h"
40 #include "ardour/session.h"
41
42 #include "evoral/Parameter.hpp"
43 #include "evoral/Control.hpp"
44 #include "evoral/midi_util.h"
45
46 #include "automation_region_view.h"
47 #include "automation_time_axis.h"
48 #include "canvas-hit.h"
49 #include "canvas-note.h"
50 #include "canvas-program-change.h"
51 #include "ghostregion.h"
52 #include "gui_thread.h"
53 #include "keyboard.h"
54 #include "midi_cut_buffer.h"
55 #include "midi_list_editor.h"
56 #include "midi_region_view.h"
57 #include "midi_streamview.h"
58 #include "midi_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "midi_util.h"
61 #include "public_editor.h"
62 #include "selection.h"
63 #include "simpleline.h"
64 #include "streamview.h"
65 #include "utils.h"
66
67 #include "i18n.h"
68
69 using namespace ARDOUR;
70 using namespace PBD;
71 using namespace Editing;
72 using namespace ArdourCanvas;
73 using Gtkmm2ext::Keyboard;
74
75 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
76                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
77         : RegionView (parent, tv, r, spu, basic_color)
78         , _force_channel(-1)
79         , _last_channel_selection(0xFFFF)
80         , _current_range_min(0)
81         , _current_range_max(0)
82         , _model_name(string())
83         , _custom_device_mode(string())
84         , _active_notes(0)
85         , _note_group(new ArdourCanvas::Group(*parent))
86         , _delta_command(0)
87         , _diff_command(0)
88         , _ghost_note(0)
89         , _drag_rect (0)
90         , _mouse_state(None)
91         , _pressed_button(0)
92         , _sort_needed (true)
93         , _optimization_iterator (_events.end())
94         , _list_editor (0)
95         , no_sound_notes (false)
96 {
97         _note_group->raise_to_top();
98 }
99
100 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
101                 boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
102                 TimeAxisViewItem::Visibility visibility)
103         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
104         , _force_channel(-1)
105         , _last_channel_selection(0xFFFF)
106         , _model_name(string())
107         , _custom_device_mode(string())
108         , _active_notes(0)
109         , _note_group(new ArdourCanvas::Group(*parent))
110         , _delta_command(0)
111         , _diff_command(0)
112         , _ghost_note(0)
113         , _drag_rect (0)
114         , _mouse_state(None)
115         , _pressed_button(0)
116         , _sort_needed (true)
117         , _optimization_iterator (_events.end())
118         , _list_editor (0)
119         , no_sound_notes (false)
120 {
121         _note_group->raise_to_top();
122 }
123
124
125 MidiRegionView::MidiRegionView (const MidiRegionView& other)
126         : sigc::trackable(other)
127         , RegionView (other)
128         , _force_channel(-1)
129         , _last_channel_selection(0xFFFF)
130         , _model_name(string())
131         , _custom_device_mode(string())
132         , _active_notes(0)
133         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
134         , _delta_command(0)
135         , _diff_command(0)
136         , _ghost_note(0)
137         , _drag_rect (0)
138         , _mouse_state(None)
139         , _pressed_button(0)
140         , _sort_needed (true)
141         , _optimization_iterator (_events.end())
142         , _list_editor (0)
143         , no_sound_notes (false)
144 {
145         Gdk::Color c;
146         int r,g,b,a;
147
148         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
149         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
150
151         init (c, false);
152 }
153
154 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
155         : RegionView (other, boost::shared_ptr<Region> (region))
156         , _force_channel(-1)
157         , _last_channel_selection(0xFFFF)
158         , _model_name(string())
159         , _custom_device_mode(string())
160         , _active_notes(0)
161         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
162         , _delta_command(0)
163         , _diff_command(0)
164         , _ghost_note(0)
165         , _drag_rect (0)
166         , _mouse_state(None)
167         , _pressed_button(0)
168         , _sort_needed (true)
169         , _optimization_iterator (_events.end())
170         , _list_editor (0)
171         , no_sound_notes (false)
172 {
173         Gdk::Color c;
174         int r,g,b,a;
175
176         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
177         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
178
179         init (c, true);
180 }
181
182 void
183 MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
184 {
185         CanvasNoteEvent::CanvasNoteEventDeleted.connect (note_delete_connection, MISSING_INVALIDATOR, 
186                                                          ui_bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
187                                                          gui_context());
188
189         if (wfd) {
190                 midi_region()->midi_source(0)->load_model();
191         }
192
193         _model = midi_region()->midi_source(0)->model();
194         _enable_display = false;
195
196         RegionView::init (basic_color, false);
197
198         compute_colors (basic_color);
199
200         set_height (trackview.current_height());
201
202         region_muted ();
203         region_sync_changed ();
204         region_resized (ARDOUR::bounds_change);
205         region_locked ();
206
207         reset_width_dependent_items (_pixel_width);
208
209         set_colors ();
210
211         _enable_display = true;
212         if (_model) {
213                 if (wfd) {
214                         display_model (_model);
215                 }
216         }
217
218         group->raise_to_top();
219         group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false);
220
221         midi_view()->signal_channel_mode_changed().connect(
222                         sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
223
224         midi_view()->signal_midi_patch_settings_changed().connect(
225                         sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
226
227         trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ());
228 }
229
230 bool
231 MidiRegionView::canvas_event(GdkEvent* ev)
232 {
233         if (!trackview.editor().internal_editing()) {
234                 return false;
235         }
236
237         /* XXX: note that until version 2.30, the GnomeCanvas did not propagate scroll events
238            to its items, which means that ev->type == GDK_SCROLL will never be seen
239         */
240
241         switch (ev->type) {
242         case GDK_SCROLL:
243                 return scroll (&ev->scroll);
244
245         case GDK_KEY_PRESS:
246                 return key_press (&ev->key);
247
248         case GDK_KEY_RELEASE:
249                 return key_release (&ev->key);
250
251         case GDK_BUTTON_PRESS:
252                 return button_press (&ev->button);
253
254         case GDK_2BUTTON_PRESS:
255                 return true;
256
257         case GDK_BUTTON_RELEASE:
258                 return button_release (&ev->button);
259                 
260         case GDK_ENTER_NOTIFY:
261                 return enter_notify (&ev->crossing);
262
263         case GDK_LEAVE_NOTIFY:
264                 return leave_notify (&ev->crossing);
265
266         case GDK_MOTION_NOTIFY:
267                 return motion (&ev->motion);
268
269         default: 
270                 break;
271         }
272
273         return false;
274 }
275
276 bool
277 MidiRegionView::enter_notify (GdkEventCrossing* ev)
278 {
279         /* FIXME: do this on switch to note tool, too, if the pointer is already in */
280
281         Keyboard::magic_widget_grab_focus();
282         group->grab_focus();
283         
284         if (trackview.editor().current_mouse_mode() == MouseRange) {
285                 create_ghost_note (ev->x, ev->y);
286         }
287
288         return false;
289 }
290
291 bool
292 MidiRegionView::leave_notify (GdkEventCrossing* ev)
293 {
294         trackview.editor().hide_verbose_canvas_cursor ();
295         delete _ghost_note;
296         _ghost_note = 0;
297         return false;
298 }
299
300 bool
301 MidiRegionView::button_press (GdkEventButton* ev)
302 {
303         _last_x = ev->x;
304         _last_y = ev->y;
305         group->w2i (_last_x, _last_y);
306         
307         if (_mouse_state != SelectTouchDragging && ev->button == 1) {
308                 _pressed_button = ev->button;
309                 _mouse_state = Pressed;
310                 return true;
311         }
312         _pressed_button = ev->button;
313
314         return true;
315 }
316
317 bool
318 MidiRegionView::button_release (GdkEventButton* ev)
319 {
320         double event_x, event_y;
321         nframes64_t event_frame = 0;
322
323         event_x = ev->x;
324         event_y = ev->y;
325         group->w2i(event_x, event_y);
326         group->ungrab(ev->time);
327         event_frame = trackview.editor().pixel_to_frame(event_x);
328
329         if (ev->button == 3) {
330                 return false;
331         } else if (_pressed_button != 1) {
332                 return false;
333         }
334
335         switch (_mouse_state) {
336         case Pressed: // Clicked
337                 switch (trackview.editor().current_mouse_mode()) {
338                 case MouseObject:
339                 case MouseTimeFX:
340                         clear_selection();
341                         break;
342                 case MouseRange:
343                 {
344                         bool success;
345                         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x));
346                         if (!success) {
347                                 beats = 1;
348                         }
349
350                         create_note_at (event_x, event_y, beats);
351                         break;
352                 }
353                 default:
354                         break;
355                 }
356                 _mouse_state = None;
357                 break;
358         case SelectRectDragging: // Select drag done
359                 _mouse_state = None;
360                 delete _drag_rect;
361                 _drag_rect = 0;
362                 break;
363
364         case AddDragging: // Add drag done
365                 _mouse_state = None;
366                 if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) {
367                         const double x      = _drag_rect->property_x1();
368                         const double length = trackview.editor().pixel_to_frame 
369                                 (_drag_rect->property_x2() - _drag_rect->property_x1());
370
371                         create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length));
372                 }
373
374                 delete _drag_rect;
375                 _drag_rect = 0;
376
377                 create_ghost_note (ev->x, ev->y);
378
379         default:
380                 break;
381         }
382
383         return false;
384 }
385
386 bool
387 MidiRegionView::motion (GdkEventMotion* ev)
388 {
389         double event_x, event_y;
390         nframes64_t event_frame = 0;
391
392         event_x = ev->x;
393         event_y = ev->y;
394         group->w2i(event_x, event_y);
395
396         // convert event_x to global frame
397         event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
398         trackview.editor().snap_to(event_frame);
399         // convert event_frame back to local coordinates relative to position
400         event_frame -= _region->position();
401
402         if (_ghost_note) {
403                 update_ghost_note (ev->x, ev->y);
404         }
405
406         /* any motion immediately hides velocity text that may have been visible */
407                 
408         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
409                 (*i)->hide_velocity ();
410         }
411
412         switch (_mouse_state) {
413         case Pressed: // Maybe start a drag, if we've moved a bit
414
415                 if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) {
416                         /* no appreciable movement since the button was pressed */
417                         return false;
418                 }
419
420                 // Select drag start
421                 if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject) {
422                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
423                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
424                         _last_x = event_x;
425                         _last_y = event_y;
426                         _drag_start_x = event_x;
427                         _drag_start_y = event_y;
428
429                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
430                         _drag_rect->property_x1() = event_x;
431                         _drag_rect->property_y1() = event_y;
432                         _drag_rect->property_x2() = event_x;
433                         _drag_rect->property_y2() = event_y;
434                         _drag_rect->property_outline_what() = 0xFF;
435                         _drag_rect->property_outline_color_rgba()
436                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
437                         _drag_rect->property_fill_color_rgba()
438                                 = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
439
440                         _mouse_state = SelectRectDragging;
441                         return true;
442
443                         // Add note drag start
444                 } else if (trackview.editor().internal_editing()) {
445
446                         delete _ghost_note;
447                         _ghost_note = 0;
448                                 
449                         group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
450                                     Gdk::Cursor(Gdk::FLEUR), ev->time);
451                         _last_x = event_x;
452                         _last_y = event_y;
453                         _drag_start_x = event_x;
454                         _drag_start_y = event_y;
455
456                         _drag_rect = new ArdourCanvas::SimpleRect(*group);
457                         _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
458
459                         _drag_rect->property_y1() = midi_stream_view()->note_to_y(
460                                 midi_stream_view()->y_to_note(event_y));
461                         _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame);
462                         _drag_rect->property_y2() = _drag_rect->property_y1()
463                                 + floor(midi_stream_view()->note_height());
464                         _drag_rect->property_outline_what() = 0xFF;
465                         _drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
466                         _drag_rect->property_fill_color_rgba()    = 0xFFFFFF66;
467
468                         _mouse_state = AddDragging;
469                         return true;
470                 }
471
472                 return false;
473
474         case SelectRectDragging: // Select drag motion
475         case AddDragging: // Add note drag motion
476                 if (ev->is_hint) {
477                         int t_x;
478                         int t_y;
479                         GdkModifierType state;
480                         gdk_window_get_pointer(ev->window, &t_x, &t_y, &state);
481                         event_x = t_x;
482                         event_y = t_y;
483                 }
484
485                 if (_mouse_state == AddDragging)
486                         event_x = trackview.editor().frame_to_pixel(event_frame);
487
488                 if (_drag_rect) {
489                         if (event_x > _drag_start_x)
490                                 _drag_rect->property_x2() = event_x;
491                         else
492                                 _drag_rect->property_x1() = event_x;
493                 }
494
495                 if (_drag_rect && _mouse_state == SelectRectDragging) {
496                         if (event_y > _drag_start_y)
497                                 _drag_rect->property_y2() = event_y;
498                         else
499                                 _drag_rect->property_y1() = event_y;
500
501                         update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y);
502                 }
503
504                 _last_x = event_x;
505                 _last_y = event_y;
506
507         case SelectTouchDragging:
508                 return false;
509
510         default:
511                 break;
512         }
513
514         return false;
515 }
516
517
518 bool
519 MidiRegionView::scroll (GdkEventScroll* ev)
520 {
521         if (_selection.empty()) {
522                 return false;
523         }
524
525         trackview.editor().hide_verbose_canvas_cursor ();
526
527         bool fine = Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier);
528         
529         if (ev->direction == GDK_SCROLL_UP) {
530                 change_velocities (true, fine, false);
531         } else if (ev->direction == GDK_SCROLL_DOWN) {
532                 change_velocities (false, fine, false);
533         } 
534         return true;
535 }
536
537 bool
538 MidiRegionView::key_press (GdkEventKey* ev)
539
540         /* since GTK bindings are generally activated on press, and since
541            detectable auto-repeat is the name of the game and only sends
542            repeated presses, carry out key actions at key press, not release.
543         */
544         
545         if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){
546                 _mouse_state = SelectTouchDragging;
547                 return true;
548                 
549         } else if (ev->keyval == GDK_Escape) {
550                 clear_selection();
551                 _mouse_state = None;
552                 
553         } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
554                 
555                 bool start = (ev->keyval == GDK_comma);
556                 bool end = (ev->keyval == GDK_period);
557                 bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
558                 bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
559                 
560                 change_note_lengths (fine, shorter, start, end);
561                 
562                 return true;
563                 
564         } else if (ev->keyval == GDK_Delete) {
565                 
566                 delete_selection();
567                 return true;
568                 
569         } else if (ev->keyval == GDK_Tab) {
570                 
571                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
572                         goto_previous_note ();
573                 } else {
574                         goto_next_note ();
575                 }
576                 return true;
577                 
578         } else if (ev->keyval == GDK_Up) {
579                 
580                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
581                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
582                 
583                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
584                         change_velocities (true, fine, allow_smush);
585                 } else {
586                         transpose (true, fine, allow_smush);
587                 }
588                 return true;
589                 
590         } else if (ev->keyval == GDK_Down) {
591                 
592                 bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
593                 bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
594                 
595                 if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
596                         change_velocities (false, fine, allow_smush);
597                 } else {
598                         transpose (false, fine, allow_smush);
599                 }
600                 return true;
601                 
602         } else if (ev->keyval == GDK_Left) {
603                 
604                 nudge_notes (false);
605                 return true;
606                 
607         } else if (ev->keyval == GDK_Right) {
608                 
609                 nudge_notes (true);
610                 return true;
611                 
612         } else if (ev->keyval == GDK_Control_L) {
613                 return true;
614                 
615         } else if (ev->keyval == GDK_r) {
616                 /* if we're not step editing, this really doesn't matter */
617                 midi_view()->step_edit_rest ();
618                 return true;
619         }
620         
621         return false;
622 }
623
624 bool
625 MidiRegionView::key_release (GdkEventKey* ev)
626 {
627         if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) {
628                 _mouse_state = None;
629                 return true;
630         }
631         return false;
632 }
633
634 void
635 MidiRegionView::show_list_editor ()
636 {
637         if (!_list_editor) {
638                 _list_editor = new MidiListEditor (trackview.session(), midi_region());
639         }
640         _list_editor->present ();
641 }
642
643 /** Add a note to the model, and the view, at a canvas (click) coordinate.
644  * \param x horizontal position in pixels
645  * \param y vertical position in pixels
646  * \param length duration of the note in beats */
647 void
648 MidiRegionView::create_note_at(double x, double y, double length)
649 {
650         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
651         MidiStreamView* const view = mtv->midi_view();
652
653         double note = midi_stream_view()->y_to_note(y);
654
655         assert(note >= 0.0);
656         assert(note <= 127.0);
657
658         // Start of note in frames relative to region start
659         nframes64_t const start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
660         assert(start_frames >= 0);
661
662         // Snap length
663         length = frames_to_beats(
664                         snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
665
666         assert (length != 0);
667
668         const boost::shared_ptr<NoteType> new_note(new NoteType(0,
669                         frames_to_beats(start_frames + _region->start()), length,
670                         (uint8_t)note, 0x40));
671
672         if (_model->contains (new_note)) {
673                 return;
674         }
675
676         view->update_note_range(new_note->note());
677
678         MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
679         cmd->add(new_note);
680         _model->apply_command(*trackview.session(), cmd);
681
682         play_midi_note (new_note);
683 }
684
685 void
686 MidiRegionView::clear_events()
687 {
688         clear_selection();
689
690         MidiGhostRegion* gr;
691         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
692                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
693                         gr->clear_events();
694                 }
695         }
696
697         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
698                 delete *i;
699         }
700
701         _events.clear();
702         _pgm_changes.clear();
703         _sys_exes.clear();
704         _optimization_iterator = _events.end();
705 }
706
707
708 void
709 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
710 {
711         _model = model;
712         content_connection.disconnect ();
713         _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
714
715         clear_events ();
716
717         if (_enable_display) {
718                 redisplay_model();
719         }
720 }
721
722
723 void
724 MidiRegionView::start_delta_command(string name)
725 {
726         if (!_delta_command) {
727                 _delta_command = _model->new_delta_command(name);
728         }
729 }
730
731 void
732 MidiRegionView::start_diff_command(string name)
733 {
734         if (!_diff_command) {
735                 _diff_command = _model->new_diff_command(name);
736         }
737 }
738
739 void
740 MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
741 {
742         if (_delta_command) {
743                 _delta_command->add(note);
744         }
745         if (selected) {
746                 _marked_for_selection.insert(note);
747         }
748         if (show_velocity) {
749                 _marked_for_velocity.insert(note);
750         }
751 }
752
753 void
754 MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
755 {
756         if (_delta_command && ev->note()) {
757                 _delta_command->remove(ev->note());
758         }
759 }
760
761 void
762 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
763                                  MidiModel::DiffCommand::Property property,
764                                  uint8_t val)
765 {
766         if (_diff_command) {
767                 _diff_command->change (ev->note(), property, val);
768         }
769 }
770
771 void
772 MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
773                                  MidiModel::DiffCommand::Property property,
774                                  Evoral::MusicalTime val)
775 {
776         if (_diff_command) {
777                 _diff_command->change (ev->note(), property, val);
778         }
779 }
780
781 void
782 MidiRegionView::apply_delta()
783 {
784         if (!_delta_command) {
785                 return;
786         }
787
788         // Mark all selected notes for selection when model reloads
789         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
790                 _marked_for_selection.insert((*i)->note());
791         }
792
793         _model->apply_command(*trackview.session(), _delta_command);
794         _delta_command = 0;
795         midi_view()->midi_track()->playlist_modified();
796
797         _marked_for_selection.clear();
798         _marked_for_velocity.clear();
799 }
800
801 void
802 MidiRegionView::apply_diff ()
803 {
804         if (!_diff_command) {
805                 return;
806         }
807
808         _model->apply_command(*trackview.session(), _diff_command);
809         _diff_command = 0;
810         midi_view()->midi_track()->playlist_modified();
811
812         _marked_for_velocity.clear();
813 }
814
815 void
816 MidiRegionView::apply_delta_as_subcommand()
817 {
818         if (!_delta_command) {
819                 return;
820         }
821
822         // Mark all selected notes for selection when model reloads
823         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
824                 _marked_for_selection.insert((*i)->note());
825         }
826
827         _model->apply_command_as_subcommand(*trackview.session(), _delta_command);
828         _delta_command = 0;
829         midi_view()->midi_track()->playlist_modified();
830
831         _marked_for_selection.clear();
832         _marked_for_velocity.clear();
833 }
834
835 void
836 MidiRegionView::apply_diff_as_subcommand()
837 {
838         if (!_diff_command) {
839                 return;
840         }
841
842         // Mark all selected notes for selection when model reloads
843         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
844                 _marked_for_selection.insert((*i)->note());
845         }
846
847         _model->apply_command_as_subcommand(*trackview.session(), _diff_command);
848         _diff_command = 0;
849         midi_view()->midi_track()->playlist_modified();
850
851         _marked_for_selection.clear();
852         _marked_for_velocity.clear();
853 }
854
855 void
856 MidiRegionView::abort_command()
857 {
858         delete _delta_command;
859         _delta_command = 0;
860         delete _diff_command;
861         _diff_command = 0;
862         clear_selection();
863 }
864
865 CanvasNoteEvent*
866 MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
867 {
868         if (_optimization_iterator != _events.end()) {
869                 ++_optimization_iterator;
870         }
871
872         if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
873                 return *_optimization_iterator;
874         }
875
876         for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
877                 if ((*_optimization_iterator)->note() == note) {
878                         return *_optimization_iterator;
879                 }
880         }
881
882         return 0;
883 }
884
885 void
886 MidiRegionView::redisplay_model()
887 {
888         // Don't redisplay the model if we're currently recording and displaying that
889         if (_active_notes) {
890                 return;
891         }
892
893         if (!_model) {
894                 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
895                 return;
896         }
897
898         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
899                 (*i)->invalidate ();
900         }
901
902         MidiModel::ReadLock lock(_model->read_lock());
903
904         MidiModel::Notes& notes (_model->notes());
905         _optimization_iterator = _events.begin();
906
907         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
908
909                 boost::shared_ptr<NoteType> note (*n);
910                 CanvasNoteEvent* cne;
911                 bool visible;
912
913                 if (note_in_region_range (note, visible)) {
914
915                         if ((cne = find_canvas_note (note)) != 0) {
916
917                                 cne->validate ();
918
919                                 CanvasNote* cn;
920                                 CanvasHit* ch;
921
922                                 if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
923                                         update_note (cn);
924                                 } else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
925                                         update_hit (ch);
926                                 }
927
928                                 if (visible) {
929                                         cne->show ();
930                                 } else {
931                                         cne->hide ();
932                                 }
933
934                         } else {
935
936                                 add_note (note, visible);
937                         }
938
939                 } else {
940
941                         if ((cne = find_canvas_note (note)) != 0) {
942                                 cne->validate ();
943                                 cne->hide ();
944                         }
945                 }
946         }
947
948
949         /* remove note items that are no longer valid */
950
951         for (Events::iterator i = _events.begin(); i != _events.end(); ) {
952                 if (!(*i)->valid ()) {
953                         delete *i;
954                         i = _events.erase (i);
955                 } else {
956                         ++i;
957                 }
958         }
959
960         display_sysexes();
961         display_program_changes();
962
963         _marked_for_selection.clear ();
964         _marked_for_velocity.clear ();
965
966         /* we may have caused _events to contain things out of order (e.g. if a note
967            moved earlier or later). we don't generally need them in time order, but
968            make a note that a sort is required for those cases that require it.
969         */
970
971         _sort_needed = true;
972 }
973
974 void
975 MidiRegionView::display_program_changes()
976 {
977         boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
978         if (!control) {
979                 return;
980         }
981
982         Glib::Mutex::Lock lock (control->list()->lock());
983
984         uint8_t channel = control->parameter().channel();
985
986         for (AutomationList::const_iterator event = control->list()->begin();
987                         event != control->list()->end(); ++event) {
988                 double event_time     = (*event)->when;
989                 double program_number = floor((*event)->value + 0.5);
990
991                 // Get current value of bank select MSB at time of the program change
992                 Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
993                 boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
994                 uint8_t msb = 0;
995                 if (msb_control != 0) {
996                         msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
997                 }
998
999                 // Get current value of bank select LSB at time of the program change
1000                 Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1001                 boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1002                 uint8_t lsb = 0;
1003                 if (lsb_control != 0) {
1004                         lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
1005                 }
1006
1007                 MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
1008
1009                 boost::shared_ptr<MIDI::Name::Patch> patch =
1010                         MIDI::Name::MidiPatchManager::instance().find_patch(
1011                                         _model_name, _custom_device_mode, channel, patch_key);
1012
1013                 PCEvent program_change(event_time, uint8_t(program_number), channel);
1014
1015                 if (patch != 0) {
1016                         add_pgm_change(program_change, patch->name());
1017                 } else {
1018                         char buf[4];
1019                         snprintf(buf, 4, "%d", int(program_number));
1020                         add_pgm_change(program_change, buf);
1021                 }
1022         }
1023 }
1024
1025 void
1026 MidiRegionView::display_sysexes()
1027 {
1028         for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
1029                 Evoral::MusicalTime time = (*i)->time();
1030                 assert(time >= 0);
1031
1032                 ostringstream str;
1033                 str << hex;
1034                 for (uint32_t b = 0; b < (*i)->size(); ++b) {
1035                         str << int((*i)->buffer()[b]);
1036                         if (b != (*i)->size() -1) {
1037                                 str << " ";
1038                         }
1039                 }
1040                 string text = str.str();
1041
1042                 ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1043
1044                 const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
1045
1046                 double height = midi_stream_view()->contents_height();
1047
1048                 boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
1049                                 new CanvasSysEx(*this, *group, text, height, x, 1.0));
1050
1051                 // Show unless program change is beyond the region bounds
1052                 if (time - _region->start() >= _region->length() || time < _region->start()) {
1053                         sysex->hide();
1054                 } else {
1055                         sysex->show();
1056                 }
1057
1058                 _sys_exes.push_back(sysex);
1059         }
1060 }
1061
1062
1063 MidiRegionView::~MidiRegionView ()
1064 {
1065         in_destructor = true;
1066
1067         note_delete_connection.disconnect ();
1068
1069         delete _list_editor;
1070
1071         RegionViewGoingAway (this); /* EMIT_SIGNAL */
1072
1073         if (_active_notes) {
1074                 end_write();
1075         }
1076
1077         _selection.clear();
1078         clear_events();
1079         delete _note_group;
1080         delete _delta_command;
1081 }
1082
1083 void
1084 MidiRegionView::region_resized (const PropertyChange& what_changed)
1085 {
1086         RegionView::region_resized(what_changed);
1087
1088         if (what_changed.contains (ARDOUR::Properties::position)) {
1089                 set_duration(_region->length(), 0);
1090                 if (_enable_display) {
1091                         redisplay_model();
1092                 }
1093         }
1094 }
1095
1096 void
1097 MidiRegionView::reset_width_dependent_items (double pixel_width)
1098 {
1099         RegionView::reset_width_dependent_items(pixel_width);
1100         assert(_pixel_width == pixel_width);
1101
1102         if (_enable_display) {
1103                 redisplay_model();
1104         }
1105 }
1106
1107 void
1108 MidiRegionView::set_height (double height)
1109 {
1110         static const double FUDGE = 2.0;
1111         const double old_height = _height;
1112         RegionView::set_height(height);
1113         _height = height - FUDGE;
1114
1115         apply_note_range(midi_stream_view()->lowest_note(),
1116                          midi_stream_view()->highest_note(),
1117                          height != old_height + FUDGE);
1118
1119         if (name_pixbuf) {
1120                 name_pixbuf->raise_to_top();
1121         }
1122 }
1123
1124
1125 /** Apply the current note range from the stream view
1126  * by repositioning/hiding notes as necessary
1127  */
1128 void
1129 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
1130 {
1131         if (!_enable_display) {
1132                 return;
1133         }
1134
1135         if (!force && _current_range_min == min && _current_range_max == max) {
1136                 return;
1137         }
1138
1139         _current_range_min = min;
1140         _current_range_max = max;
1141
1142         for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
1143                 CanvasNoteEvent* event = *i;
1144                 boost::shared_ptr<NoteType> note (event->note());
1145
1146                 if (note->note() < _current_range_min ||
1147                     note->note() > _current_range_max) {
1148                         event->hide();
1149                 } else {
1150                         event->show();
1151                 }
1152
1153                 if (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
1154
1155                         const double y1 = midi_stream_view()->note_to_y(note->note());
1156                         const double y2 = y1 + floor(midi_stream_view()->note_height());
1157
1158                         cnote->property_y1() = y1;
1159                         cnote->property_y2() = y2;
1160
1161                 } else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
1162
1163                         double x = trackview.editor().frame_to_pixel(
1164                                 beats_to_frames(note->time()) - _region->start());
1165                         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1166                         double y = midi_stream_view()->note_to_y(event->note()->note())
1167                                 + ((diamond_size-2.0) / 4.0);
1168
1169                         chit->set_height (diamond_size);
1170                         chit->move (x - chit->x1(), y - chit->y1());
1171                         chit->show ();
1172                 }
1173         }
1174 }
1175
1176 GhostRegion*
1177 MidiRegionView::add_ghost (TimeAxisView& tv)
1178 {
1179         CanvasNote* note;
1180
1181         double unit_position = _region->position () / samples_per_unit;
1182         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
1183         MidiGhostRegion* ghost;
1184
1185         if (mtv && mtv->midi_view()) {
1186                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
1187                    to allow having midi notes on top of note lines and waveforms.
1188                  */
1189                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
1190         } else {
1191                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
1192         }
1193
1194         ghost->set_height ();
1195         ghost->set_duration (_region->length() / samples_per_unit);
1196         ghosts.push_back (ghost);
1197
1198         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1199                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
1200                         ghost->add_note(note);
1201                 }
1202         }
1203
1204         GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context());
1205
1206         return ghost;
1207 }
1208
1209
1210 /** Begin tracking note state for successive calls to add_event
1211  */
1212 void
1213 MidiRegionView::begin_write()
1214 {
1215         assert(!_active_notes);
1216         _active_notes = new CanvasNote*[128];
1217         for (unsigned i=0; i < 128; ++i) {
1218                 _active_notes[i] = 0;
1219         }
1220 }
1221
1222
1223 /** Destroy note state for add_event
1224  */
1225 void
1226 MidiRegionView::end_write()
1227 {
1228         delete[] _active_notes;
1229         _active_notes = 0;
1230         _marked_for_selection.clear();
1231         _marked_for_velocity.clear();
1232 }
1233
1234
1235 /** Resolve an active MIDI note (while recording).
1236  */
1237 void
1238 MidiRegionView::resolve_note(uint8_t note, double end_time)
1239 {
1240         if (midi_view()->note_mode() != Sustained) {
1241                 return;
1242         }
1243
1244         if (_active_notes && _active_notes[note]) {
1245                 const nframes64_t end_time_frames = beats_to_frames(end_time);
1246                 _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
1247                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
1248                 _active_notes[note] = 0;
1249         }
1250 }
1251
1252
1253 /** Extend active notes to rightmost edge of region (if length is changed)
1254  */
1255 void
1256 MidiRegionView::extend_active_notes()
1257 {
1258         if (!_active_notes) {
1259                 return;
1260         }
1261
1262         for (unsigned i=0; i < 128; ++i) {
1263                 if (_active_notes[i]) {
1264                         _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1265                 }
1266         }
1267 }
1268
1269 void
1270 MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
1271 {
1272         if (no_sound_notes || !trackview.editor().sound_notes()) {
1273                 return;
1274         }
1275
1276         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1277         assert(route_ui);
1278
1279         route_ui->midi_track()->write_immediate_event(
1280                         note->on_event().size(), note->on_event().buffer());
1281
1282         const double note_length_beats = (note->off_event().time() - note->on_event().time());
1283         nframes_t note_length_ms = beats_to_frames(note_length_beats)
1284                         * (1000 / (double)route_ui->session()->nominal_frame_rate());
1285         Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(this, &MidiRegionView::play_midi_note_off), note),
1286                         note_length_ms, G_PRIORITY_DEFAULT);
1287 }
1288
1289 bool
1290 MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
1291 {
1292         RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
1293         assert(route_ui);
1294
1295         route_ui->midi_track()->write_immediate_event(
1296                         note->off_event().size(), note->off_event().buffer());
1297
1298         return false;
1299 }
1300
1301 bool
1302 MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
1303 {
1304         const nframes64_t note_start_frames = beats_to_frames(note->time());
1305
1306         bool outside = (note_start_frames - _region->start() >= _region->length()) ||
1307                 (note_start_frames < _region->start());
1308
1309         visible = (note->note() >= midi_stream_view()->lowest_note()) &&
1310                 (note->note() <= midi_stream_view()->highest_note());
1311
1312         return !outside;
1313 }
1314
1315 void
1316 MidiRegionView::update_note (CanvasNote* ev)
1317 {
1318         boost::shared_ptr<NoteType> note = ev->note();
1319
1320
1321         const nframes64_t note_start_frames = beats_to_frames(note->time());
1322         const nframes64_t note_end_frames   = beats_to_frames(note->end_time());
1323
1324         const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1325         const double y1 = midi_stream_view()->note_to_y(note->note());
1326         const double note_endpixel =
1327                 trackview.editor().frame_to_pixel(note_end_frames - _region->start());
1328
1329         ev->property_x1() = x;
1330         ev->property_y1() = y1;
1331         if (note->length() > 0) {
1332                 ev->property_x2() = note_endpixel;
1333         } else {
1334                 ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
1335         }
1336         ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
1337
1338         if (note->length() == 0) {
1339                 if (_active_notes) {
1340                         assert(note->note() < 128);
1341                         // If this note is already active there's a stuck note,
1342                         // finish the old note rectangle
1343                         if (_active_notes[note->note()]) {
1344                                 CanvasNote* const old_rect = _active_notes[note->note()];
1345                                 boost::shared_ptr<NoteType> old_note = old_rect->note();
1346                                 old_rect->property_x2() = x;
1347                                 old_rect->property_outline_what() = (guint32) 0xF;
1348                         }
1349                         _active_notes[note->note()] = ev;
1350                 }
1351                 /* outline all but right edge */
1352                 ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
1353         } else {
1354                 /* outline all edges */
1355                 ev->property_outline_what() = (guint32) 0xF;
1356         }
1357 }
1358
1359 void
1360 MidiRegionView::update_hit (CanvasHit* ev)
1361 {
1362         boost::shared_ptr<NoteType> note = ev->note();
1363
1364         const nframes64_t note_start_frames = beats_to_frames(note->time());
1365         const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
1366         const double diamond_size = midi_stream_view()->note_height() / 2.0;
1367         const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
1368
1369         ev->move_to (x, y);
1370 }
1371
1372 /** Add a MIDI note to the view (with length).
1373  *
1374  * If in sustained mode, notes with length 0 will be considered active
1375  * notes, and resolve_note should be called when the corresponding note off
1376  * event arrives, to properly display the note.
1377  */
1378 void
1379 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
1380 {
1381         CanvasNoteEvent* event = 0;
1382
1383         assert(note->time() >= 0);
1384         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
1385
1386         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1387
1388         if (midi_view()->note_mode() == Sustained) {
1389
1390                 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
1391
1392                 update_note (ev_rect);
1393
1394                 event = ev_rect;
1395
1396                 MidiGhostRegion* gr;
1397
1398                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
1399                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
1400                                 gr->add_note(ev_rect);
1401                         }
1402                 }
1403
1404         } else if (midi_view()->note_mode() == Percussive) {
1405
1406                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
1407
1408                 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
1409
1410                 update_hit (ev_diamond);
1411
1412                 event = ev_diamond;
1413
1414         } else {
1415                 event = 0;
1416         }
1417
1418         if (event) {
1419                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
1420                         note_selected(event, true);
1421                 }
1422
1423                 if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
1424                         event->show_velocity();
1425                 }
1426                 event->on_channel_selection_change(_last_channel_selection);
1427                 _events.push_back(event);
1428
1429                 if (visible) {
1430                         event->show();
1431                 } else {
1432                         event->hide ();
1433                 }
1434         }
1435 }
1436
1437 void
1438 MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
1439                           Evoral::MusicalTime pos, Evoral::MusicalTime len)
1440 {
1441         boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
1442
1443         start_delta_command (_("step add"));
1444         delta_add_note (new_note, true, false);
1445         apply_delta();
1446
1447         /* potentially extend region to hold new note */
1448
1449         nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
1450         nframes64_t region_end = _region->position() + _region->length() - 1;
1451
1452         if (end_frame > region_end) {
1453                 _region->set_length (end_frame, this);
1454         } else {
1455                 redisplay_model ();
1456         }
1457 }
1458
1459 void
1460 MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
1461 {
1462         assert(program.time >= 0);
1463
1464         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
1465         const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
1466
1467         double height = midi_stream_view()->contents_height();
1468
1469         boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
1470                         new CanvasProgramChange(*this, *group,
1471                                         displaytext,
1472                                         height,
1473                                         x, 1.0,
1474                                         _model_name,
1475                                         _custom_device_mode,
1476                                         program.time, program.channel, program.value));
1477
1478         // Show unless program change is beyond the region bounds
1479         if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
1480                 pgm_change->hide();
1481         } else {
1482                 pgm_change->show();
1483         }
1484
1485         _pgm_changes.push_back(pgm_change);
1486 }
1487
1488 void
1489 MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
1490 {
1491         cerr << "getting patch key at " << time << " for channel " << channel << endl;
1492         Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
1493         boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1494         float msb = -1.0;
1495         if (msb_control != 0) {
1496                 msb = int(msb_control->get_float(true, time));
1497                 cerr << "got msb " << msb;
1498         }
1499
1500         Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
1501         boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1502         float lsb = -1.0;
1503         if (lsb_control != 0) {
1504                 lsb = lsb_control->get_float(true, time);
1505                 cerr << " got lsb " << lsb;
1506         }
1507
1508         Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
1509         boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1510         float program_number = -1.0;
1511         if (program_control != 0) {
1512                 program_number = program_control->get_float(true, time);
1513                 cerr << " got program " << program_number << endl;
1514         }
1515
1516         key.msb = (int) floor(msb + 0.5);
1517         key.lsb = (int) floor(lsb + 0.5);
1518         key.program_number = (int) floor(program_number + 0.5);
1519         assert(key.is_sane());
1520 }
1521
1522
1523 void
1524 MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
1525 {
1526         // TODO: Get the real event here and alter them at the original times
1527         Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
1528         boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
1529         if (msb_control != 0) {
1530                 msb_control->set_float(float(new_patch.msb), true, old_program.time);
1531         }
1532
1533         // TODO: Get the real event here and alter them at the original times
1534         Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
1535         boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
1536         if (lsb_control != 0) {
1537                 lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
1538         }
1539
1540         Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
1541         boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
1542
1543         assert(program_control != 0);
1544         program_control->set_float(float(new_patch.program_number), true, old_program.time);
1545
1546         redisplay_model();
1547 }
1548
1549 void
1550 MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
1551 {
1552         PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1553         alter_program_change(program_change_event, new_patch);
1554 }
1555
1556 void
1557 MidiRegionView::previous_program(CanvasProgramChange& program)
1558 {
1559         MIDI::Name::PatchPrimaryKey key;
1560         get_patch_key_at(program.event_time(), program.channel(), key);
1561
1562         boost::shared_ptr<MIDI::Name::Patch> patch =
1563                 MIDI::Name::MidiPatchManager::instance().previous_patch(
1564                                 _model_name,
1565                                 _custom_device_mode,
1566                                 program.channel(),
1567                                 key);
1568
1569         PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1570         if (patch) {
1571                 alter_program_change(program_change_event, patch->patch_primary_key());
1572         }
1573 }
1574
1575 void
1576 MidiRegionView::next_program(CanvasProgramChange& program)
1577 {
1578         MIDI::Name::PatchPrimaryKey key;
1579         get_patch_key_at(program.event_time(), program.channel(), key);
1580
1581         boost::shared_ptr<MIDI::Name::Patch> patch =
1582                 MIDI::Name::MidiPatchManager::instance().next_patch(
1583                                 _model_name,
1584                                 _custom_device_mode,
1585                                 program.channel(),
1586                                 key);
1587
1588         PCEvent program_change_event(program.event_time(), program.program(), program.channel());
1589         if (patch) {
1590                 alter_program_change(program_change_event, patch->patch_primary_key());
1591         }
1592 }
1593
1594 void
1595 MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne)
1596 {
1597         if (_selection.empty()) {
1598                 return;
1599         }
1600  
1601         if (_selection.erase (cne) > 0) {
1602                 cerr << "Erased a CNE from selection\n";
1603         }
1604 }
1605
1606 void
1607 MidiRegionView::delete_selection()
1608 {
1609         if (_selection.empty()) {
1610                 return;
1611         }
1612
1613         start_delta_command (_("delete selection"));
1614
1615         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1616                 if ((*i)->selected()) {
1617                         _delta_command->remove((*i)->note());
1618                 }
1619         }
1620
1621         _selection.clear();
1622
1623         apply_delta ();
1624 }
1625
1626 void
1627 MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
1628 {
1629         start_delta_command (_("delete note"));
1630         _delta_command->remove (n);
1631         apply_delta ();
1632
1633         trackview.editor().hide_verbose_canvas_cursor ();
1634 }
1635
1636 void
1637 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
1638 {
1639         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1640                 if ((*i)->selected() && (*i) != ev) {
1641                         (*i)->selected(false);
1642                         (*i)->hide_velocity();
1643                 }
1644         }
1645
1646         _selection.clear();
1647 }
1648
1649 void
1650 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
1651 {
1652         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
1653                 if ((*i) != ev) {
1654
1655                         Selection::iterator tmp = i;
1656                         ++tmp;
1657
1658                         (*i)->selected (false);
1659                         _selection.erase (i);
1660
1661                         i = tmp;
1662
1663                 } else {
1664                         ++i;
1665                 }
1666         }
1667
1668         /* don't bother with removing this regionview from the editor selection,
1669            since we're about to add another note, and thus put/keep this
1670            regionview in the editor selection.
1671         */
1672
1673         if (!ev->selected()) {
1674                 add_to_selection (ev);
1675         }
1676 }
1677
1678 void
1679 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
1680 {
1681         uint8_t low_note = 127;
1682         uint8_t high_note = 0;
1683         MidiModel::Notes& notes (_model->notes());
1684         _optimization_iterator = _events.begin();
1685
1686         if (!add) {
1687                 clear_selection ();
1688         }
1689
1690         if (extend && _selection.empty()) {
1691                 extend = false;
1692         }
1693
1694         if (extend) {
1695
1696                 /* scan existing selection to get note range */
1697
1698                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1699                         if ((*i)->note()->note() < low_note) {
1700                                 low_note = (*i)->note()->note();
1701                         }
1702                         if ((*i)->note()->note() > high_note) {
1703                                 high_note = (*i)->note()->note();
1704                         }
1705                 }
1706
1707                 low_note = min (low_note, notenum);
1708                 high_note = max (high_note, notenum);
1709         }
1710
1711         no_sound_notes = true;
1712
1713         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1714
1715                 boost::shared_ptr<NoteType> note (*n);
1716                 CanvasNoteEvent* cne;
1717                 bool select = false;
1718
1719                 if (((0x0001 << note->channel()) & channel_mask) != 0) {
1720                         if (extend) {
1721                                 if ((note->note() >= low_note && note->note() <= high_note)) {
1722                                         select = true;
1723                                 }
1724                         } else if (note->note() == notenum) {
1725                                 select = true;
1726                         }
1727                 }
1728
1729                 if (select) {
1730                         if ((cne = find_canvas_note (note)) != 0) {
1731                                 // extend is false because we've taken care of it, 
1732                                 // since it extends by time range, not pitch.
1733                                 note_selected (cne, add, false);
1734                         }
1735                 }
1736                 
1737                 add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
1738
1739         }
1740
1741         no_sound_notes = false;
1742 }
1743
1744 void
1745 MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
1746 {
1747         MidiModel::Notes& notes (_model->notes());
1748         _optimization_iterator = _events.begin();
1749
1750         for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
1751
1752                 boost::shared_ptr<NoteType> note (*n);
1753                 CanvasNoteEvent* cne;
1754
1755                 if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
1756                         if ((cne = find_canvas_note (note)) != 0) {
1757                                 if (cne->selected()) {
1758                                         note_deselected (cne);
1759                                 } else {
1760                                         note_selected (cne, true, false);
1761                                 }
1762                         }
1763                 }
1764         }
1765 }
1766
1767 void
1768 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
1769 {
1770         if (!add) {
1771                 clear_selection_except(ev);
1772         }
1773
1774         if (!extend) {
1775
1776                 if (!ev->selected()) {
1777                         add_to_selection (ev);
1778                 }
1779
1780         } else {
1781                 /* find end of latest note selected, select all between that and the start of "ev" */
1782
1783                 Evoral::MusicalTime earliest = DBL_MAX;
1784                 Evoral::MusicalTime latest = 0;
1785
1786                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1787                         if ((*i)->note()->end_time() > latest) {
1788                                 latest = (*i)->note()->end_time();
1789                         }
1790                         if ((*i)->note()->time() < earliest) {
1791                                 earliest = (*i)->note()->time();
1792                         }
1793                 }
1794
1795                 if (ev->note()->end_time() > latest) {
1796                         latest = ev->note()->end_time();
1797                 }
1798
1799                 if (ev->note()->time() < earliest) {
1800                         earliest = ev->note()->time();
1801                 }
1802
1803                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1804
1805                         /* find notes entirely within OR spanning the earliest..latest range */
1806
1807                         if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
1808                             ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
1809                                 add_to_selection (*i);
1810                         }
1811
1812 #if 0
1813                         /* if events were guaranteed to be time sorted, we could do this.
1814                            but as of sept 10th 2009, they no longer are.
1815                         */
1816
1817                         if ((*i)->note()->time() > latest) {
1818                                 break;
1819                         }
1820 #endif
1821                 }
1822         }
1823 }
1824
1825 void
1826 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
1827 {
1828         remove_from_selection (ev);
1829 }
1830
1831 void
1832 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1833 {
1834         if (x1 > x2) {
1835                 swap (x1, x2);
1836         }
1837
1838         if (y1 > y2) {
1839                 swap (y1, y2);
1840         }
1841
1842         // TODO: Make this faster by storing the last updated selection rect, and only
1843         // adjusting things that are in the area that appears/disappeared.
1844         // We probably need a tree to be able to find events in O(log(n)) time.
1845
1846         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1847
1848                 /* check if any corner of the note is inside the rect
1849
1850                    Notes:
1851                      1) this is computing "touched by", not "contained by" the rect.
1852                      2) this does not require that events be sorted in time.
1853                  */
1854
1855                 const double ix1 = (*i)->x1();
1856                 const double ix2 = (*i)->x2();
1857                 const double iy1 = (*i)->y1();
1858                 const double iy2 = (*i)->y2();
1859
1860                 if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1861                     (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
1862                     (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
1863                     (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
1864
1865                         // Inside rectangle
1866                         if (!(*i)->selected()) {
1867                                 add_to_selection (*i);
1868                         }
1869                 } else if ((*i)->selected()) {
1870                         // Not inside rectangle
1871                         remove_from_selection (*i);
1872                 }
1873         }
1874 }
1875
1876 void
1877 MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
1878 {
1879         Selection::iterator i = _selection.find (ev);
1880
1881         if (i != _selection.end()) {
1882                 _selection.erase (i);
1883         }
1884
1885         ev->selected (false);
1886         ev->hide_velocity ();
1887
1888         if (_selection.empty()) {
1889                 PublicEditor& editor (trackview.editor());
1890                 editor.get_selection().remove (this);
1891         }
1892 }
1893
1894 void
1895 MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
1896 {
1897         bool add_mrv_selection = false;
1898
1899         if (_selection.empty()) {
1900                 add_mrv_selection = true;
1901         }
1902
1903         if (_selection.insert (ev).second) {
1904                 ev->selected (true);
1905                 play_midi_note ((ev)->note());
1906         }
1907
1908         if (add_mrv_selection) {
1909                 PublicEditor& editor (trackview.editor());
1910                 editor.get_selection().add (this);
1911         }
1912 }
1913
1914 void
1915 MidiRegionView::move_selection(double dx, double dy)
1916 {
1917         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1918                 (*i)->move_event(dx, dy);
1919         }
1920 }
1921
1922 void
1923 MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
1924 {
1925         assert (!_selection.empty());
1926
1927         uint8_t lowest_note_in_selection  = 127;
1928         uint8_t highest_note_in_selection = 0;
1929         uint8_t highest_note_difference = 0;
1930
1931         // find highest and lowest notes first
1932
1933         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1934                 uint8_t pitch = (*i)->note()->note();
1935                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
1936                 highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1937         }
1938
1939         /*
1940         cerr << "dnote: " << (int) dnote << endl;
1941         cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
1942              << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1943         cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
1944              << int(highest_note_in_selection) << endl;
1945         cerr << "selection size: " << _selection.size() << endl;
1946         cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1947         */
1948
1949         // Make sure the note pitch does not exceed the MIDI standard range
1950         if (highest_note_in_selection + dnote > 127) {
1951                 highest_note_difference = highest_note_in_selection - 127;
1952         }
1953
1954         start_diff_command(_("move notes"));
1955
1956         for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
1957
1958                 nframes64_t start_frames = beats_to_frames((*i)->note()->time());
1959
1960                 if (dt >= 0) {
1961                         start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
1962                 } else {
1963                         start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
1964                 }
1965
1966                 Evoral::MusicalTime new_time = frames_to_beats(start_frames);
1967
1968                 if (new_time < 0) {
1969                         continue;
1970                 }
1971
1972                 diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
1973
1974                 uint8_t original_pitch = (*i)->note()->note();
1975                 uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
1976
1977                 // keep notes in standard midi range
1978                 clamp_to_0_127(new_pitch);
1979
1980                 // keep original pitch if note is dragged outside valid midi range
1981                 if ((original_pitch != 0 && new_pitch == 0)
1982                                 || (original_pitch != 127 && new_pitch == 127)) {
1983                         new_pitch = original_pitch;
1984                 }
1985
1986                 lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
1987                 highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1988
1989                 diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
1990         }
1991
1992         apply_diff();
1993
1994         // care about notes being moved beyond the upper/lower bounds on the canvas
1995         if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
1996             highest_note_in_selection > midi_stream_view()->highest_note()) {
1997                 midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1998         }
1999 }
2000
2001 nframes64_t
2002 MidiRegionView::snap_pixel_to_frame(double x)
2003 {
2004         PublicEditor& editor = trackview.editor();
2005         // x is region relative, convert it to global absolute frames
2006         nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
2007         editor.snap_to(frame);
2008         return frame - _region->position(); // convert back to region relative
2009 }
2010
2011 nframes64_t
2012 MidiRegionView::snap_frame_to_frame(nframes64_t x)
2013 {
2014         PublicEditor& editor = trackview.editor();
2015         // x is region relative, convert it to global absolute frames
2016         nframes64_t frame = x + _region->position();
2017         editor.snap_to(frame);
2018         return frame - _region->position(); // convert back to region relative
2019 }
2020
2021 double
2022 MidiRegionView::snap_to_pixel(double x)
2023 {
2024         return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
2025 }
2026
2027 double
2028 MidiRegionView::get_position_pixels()
2029 {
2030         nframes64_t region_frame = get_position();
2031         return trackview.editor().frame_to_pixel(region_frame);
2032 }
2033
2034 double
2035 MidiRegionView::get_end_position_pixels()
2036 {
2037         nframes64_t frame = get_position() + get_duration ();
2038         return trackview.editor().frame_to_pixel(frame);
2039 }
2040
2041 nframes64_t
2042 MidiRegionView::beats_to_frames(double beats) const
2043 {
2044         return _time_converter.to(beats);
2045 }
2046
2047 double
2048 MidiRegionView::frames_to_beats(nframes64_t frames) const
2049 {
2050         return _time_converter.from(frames);
2051 }
2052
2053 void
2054 MidiRegionView::begin_resizing (bool /*at_front*/)
2055 {
2056         _resize_data.clear();
2057
2058         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2059                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
2060
2061                 // only insert CanvasNotes into the map
2062                 if (note) {
2063                         NoteResizeData *resize_data = new NoteResizeData();
2064                         resize_data->canvas_note = note;
2065
2066                         // create a new SimpleRect from the note which will be the resize preview
2067                         SimpleRect *resize_rect = new SimpleRect(
2068                                         *group, note->x1(), note->y1(), note->x2(), note->y2());
2069
2070                         // calculate the colors: get the color settings
2071                         uint32_t fill_color = UINT_RGBA_CHANGE_A(
2072                                         ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
2073                                         128);
2074
2075                         // make the resize preview notes more transparent and bright
2076                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
2077
2078                         // calculate color based on note velocity
2079                         resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
2080                                         CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
2081                                         fill_color,
2082                                         0.85);
2083
2084                         resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
2085                                         ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
2086
2087                         resize_data->resize_rect = resize_rect;
2088                         _resize_data.push_back(resize_data);
2089                 }
2090         }
2091 }
2092
2093 void
2094 MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
2095 {
2096         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2097                 SimpleRect* resize_rect = (*i)->resize_rect;
2098                 CanvasNote* canvas_note = (*i)->canvas_note;
2099                 double current_x;
2100
2101                 if (at_front) {
2102                         if (relative) {
2103                                 current_x = canvas_note->x1() + delta_x;
2104                         } else {
2105                                 // x is in track relative, transform it to region relative
2106                                 current_x = delta_x - get_position_pixels();
2107                         }
2108                 } else {
2109                         if (relative) {
2110                                 current_x = canvas_note->x2() + delta_x;
2111                         } else {
2112                                 // x is in track relative, transform it to region relative
2113                                 current_x = delta_x - get_end_position_pixels ();
2114                         }
2115                 }
2116
2117                 if (at_front) {
2118                         resize_rect->property_x1() = snap_to_pixel(current_x);
2119                         resize_rect->property_x2() = canvas_note->x2();
2120                 } else {
2121                         resize_rect->property_x2() = snap_to_pixel(current_x);
2122                         resize_rect->property_x1() = canvas_note->x1();
2123                 }
2124         }
2125 }
2126
2127 void
2128 MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
2129 {
2130         start_diff_command(_("resize notes"));
2131
2132         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
2133                 CanvasNote*  canvas_note = (*i)->canvas_note;
2134                 SimpleRect*  resize_rect = (*i)->resize_rect;
2135                 const double region_start = get_position_pixels();
2136                 double current_x;
2137
2138                 if (at_front) {
2139                         if (relative) {
2140                                 current_x = canvas_note->x1() + delta_x;
2141                         } else {
2142                                 // x is in track relative, transform it to region relative
2143                                 current_x = region_start + delta_x;
2144                         }
2145                 } else {
2146                         if (relative) {
2147                                 current_x = canvas_note->x2() + delta_x;
2148                         } else {
2149                                 // x is in track relative, transform it to region relative
2150                                 current_x = region_start + delta_x;
2151                         }
2152                 }
2153
2154                 current_x = snap_pixel_to_frame (current_x);
2155                 current_x = frames_to_beats (current_x);
2156
2157                 if (at_front && current_x < canvas_note->note()->end_time()) {
2158                         diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
2159
2160                         double len = canvas_note->note()->time() - current_x;
2161                         len += canvas_note->note()->length();
2162
2163                         if (len > 0) {
2164                                 /* XXX convert to beats */
2165                                 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2166                         }
2167                 }
2168
2169                 if (!at_front) {
2170                         double len = current_x - canvas_note->note()->time();
2171
2172                         if (len > 0) {
2173                                 /* XXX convert to beats */
2174                                 diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
2175                         }
2176                 }
2177
2178                 delete resize_rect;
2179                 delete (*i);
2180         }
2181
2182         _resize_data.clear();
2183         apply_diff();
2184 }
2185
2186 void
2187 MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
2188 {
2189         uint8_t new_velocity;
2190
2191         if (relative) {
2192                 new_velocity = event->note()->velocity() + velocity;
2193                 clamp_to_0_127(new_velocity);
2194         } else {
2195                 new_velocity = velocity;
2196         }
2197
2198         event->show_velocity ();
2199
2200         diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
2201 }
2202
2203 void
2204 MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
2205 {
2206         uint8_t new_note;
2207
2208         if (relative) {
2209                 new_note = event->note()->note() + note;
2210         } else {
2211                 new_note = note;
2212         }
2213
2214         clamp_to_0_127 (new_note);
2215         diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
2216 }
2217
2218 void
2219 MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
2220 {
2221         bool change_start = false;
2222         bool change_length = false;
2223         Evoral::MusicalTime new_start;
2224         Evoral::MusicalTime new_length;
2225
2226         /* NOTE: the semantics of the two delta arguments are slightly subtle:
2227
2228            front_delta: if positive - move the start of the note later in time (shortening it)
2229                         if negative - move the start of the note earlier in time (lengthening it)
2230
2231            end_delta:   if positive - move the end of the note later in time (lengthening it)
2232                         if negative - move the end of the note earlier in time (shortening it)
2233          */
2234
2235         if (front_delta) {
2236                 if (front_delta < 0) {
2237
2238                         if (event->note()->time() < -front_delta) {
2239                                 new_start = 0;
2240                         } else {
2241                                 new_start = event->note()->time() + front_delta; // moves earlier
2242                         }
2243
2244                         /* start moved toward zero, so move the end point out to where it used to be.
2245                            Note that front_delta is negative, so this increases the length.
2246                         */
2247
2248                         new_length = event->note()->length() - front_delta;
2249                         change_start = true;
2250                         change_length = true;
2251
2252                 } else {
2253
2254                         Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
2255
2256                         if (new_pos < event->note()->end_time()) {
2257                                 new_start = event->note()->time() + front_delta;
2258                                 /* start moved toward the end, so move the end point back to where it used to be */
2259                                 new_length = event->note()->length() - front_delta;
2260                                 change_start = true;
2261                                 change_length = true;
2262                         }
2263                 }
2264
2265         }
2266
2267         if (end_delta) {
2268                 bool can_change = true;
2269                 if (end_delta < 0) {
2270                         if (event->note()->length() < -end_delta) {
2271                                 can_change = false;
2272                         }
2273                 }
2274
2275                 if (can_change) {
2276                         new_length = event->note()->length() + end_delta;
2277                         change_length = true;
2278                 }
2279         }
2280
2281         if (change_start) {
2282                 diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
2283         }
2284
2285         if (change_length) {
2286                 diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
2287         }
2288 }
2289
2290 void
2291 MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
2292 {
2293         Evoral::MusicalTime new_time;
2294
2295         if (relative) {
2296                 if (delta < 0.0) {
2297                         if (event->note()->time() < -delta) {
2298                                 new_time = 0;
2299                         } else {
2300                                 new_time = event->note()->time() + delta;
2301                         }
2302                 } else {
2303                         new_time = event->note()->time() + delta;
2304                 }
2305         } else {
2306                 new_time = delta;
2307         }
2308
2309         diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
2310 }
2311
2312 void
2313 MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
2314 {
2315         int8_t delta;
2316
2317         if (_selection.empty()) {
2318                 return;
2319         }
2320
2321         if (fine) {
2322                 delta = 1;
2323         } else {
2324                 delta = 10;
2325         }
2326
2327         if (!up) {
2328                 delta = -delta;
2329         }
2330
2331         if (!allow_smush) {
2332                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2333                         if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
2334                                 return;
2335                         }
2336                 }
2337         }
2338
2339         start_diff_command(_("change velocities"));
2340
2341         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
2342                 Selection::iterator next = i;
2343                 ++next;
2344                 change_note_velocity (*i, delta, true);
2345                 i = next;
2346         }
2347
2348         apply_diff();
2349 }
2350
2351
2352 void
2353 MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
2354 {
2355         if (_selection.empty()) {
2356                 return;
2357         }
2358
2359         int8_t delta;
2360
2361         if (fine) {
2362                 delta = 1;
2363         } else {
2364                 delta = 12;
2365         }
2366
2367         if (!up) {
2368                 delta = -delta;
2369         }
2370
2371         if (!allow_smush) {
2372                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2373                         if (!up) {
2374                                 if ((int8_t) (*i)->note()->note() + delta <= 0) {
2375                                         return;
2376                                 }
2377                         } else {
2378                                 if ((int8_t) (*i)->note()->note() + delta > 127) {
2379                                         return;
2380                                 }
2381                         }
2382                 }
2383         }
2384
2385         start_diff_command (_("transpose"));
2386
2387         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2388                 Selection::iterator next = i;
2389                 ++next;
2390                 change_note_note (*i, delta, true);
2391                 i = next;
2392         }
2393
2394         apply_diff ();
2395 }
2396
2397 void
2398 MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
2399 {
2400         Evoral::MusicalTime delta;
2401
2402         if (fine) {
2403                 delta = 1.0/128.0;
2404         } else {
2405                 /* grab the current grid distance */
2406                 bool success;
2407                 delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
2408                 if (!success) {
2409                         /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
2410                         cerr << "Grid type not available as beats - TO BE FIXED\n";
2411                         return;
2412                 }
2413         }
2414
2415         if (shorter) {
2416                 delta = -delta;
2417         }
2418
2419         start_diff_command (_("change note lengths"));
2420
2421         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2422                 Selection::iterator next = i;
2423                 ++next;
2424
2425                 /* note the negation of the delta for start */
2426
2427                 trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
2428                 i = next;
2429         }
2430
2431         apply_diff ();
2432
2433 }
2434
2435 void
2436 MidiRegionView::nudge_notes (bool forward)
2437 {
2438         if (_selection.empty()) {
2439                 return;
2440         }
2441
2442         /* pick a note as the point along the timeline to get the nudge distance.
2443            its not necessarily the earliest note, so we may want to pull the notes out
2444            into a vector and sort before using the first one.
2445         */
2446
2447         nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
2448         nframes64_t unused;
2449         nframes64_t distance;
2450
2451         if (trackview.editor().snap_mode() == Editing::SnapOff) {
2452                 
2453                 /* grid is off - use nudge distance */
2454
2455                 distance = trackview.editor().get_nudge_distance (ref_point, unused);
2456
2457         } else {
2458
2459                 /* use grid */
2460
2461                 nframes64_t next_pos = ref_point;
2462
2463                 if (forward) {
2464                         /* XXX need check on max_frames, but that needs max_frames64 or something */
2465                         next_pos += 1;
2466                 } else {
2467                         if (next_pos == 0) {
2468                                 return;
2469                         }
2470                         next_pos -= 1;
2471                 }
2472
2473                 trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
2474                 distance = ref_point - next_pos;
2475         }
2476
2477         if (distance == 0) {
2478                 return;
2479         }
2480
2481         Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
2482
2483         if (!forward) {
2484                 delta = -delta;
2485         }
2486
2487         start_diff_command (_("nudge"));
2488
2489         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
2490                 Selection::iterator next = i;
2491                 ++next;
2492                 change_note_time (*i, delta, true);
2493                 i = next;
2494         }
2495
2496         apply_diff ();
2497 }
2498
2499 void
2500 MidiRegionView::change_channel(uint8_t channel)
2501 {
2502         start_diff_command(_("change channel"));
2503         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2504                 diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
2505         }
2506
2507         apply_diff();
2508 }
2509
2510
2511 void
2512 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
2513 {
2514         if (_mouse_state == SelectTouchDragging) {
2515                 note_selected (ev, true);
2516         }
2517
2518         show_verbose_canvas_cursor (ev->note ());
2519 }
2520
2521 void
2522 MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent* note)
2523 {
2524         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2525                 (*i)->hide_velocity ();
2526         }
2527
2528         trackview.editor().hide_verbose_canvas_cursor ();
2529 }
2530
2531 void
2532 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
2533 {
2534         boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
2535         if (msrc)
2536                 display_model(msrc->model());
2537 }
2538
2539 void
2540 MidiRegionView::set_frame_color()
2541 {
2542         if (frame) {
2543                 if (_selected && should_show_selection) {
2544                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
2545                 } else {
2546                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
2547                 }
2548         }
2549 }
2550
2551 void
2552 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
2553 {
2554         switch (mode) {
2555         case AllChannels:
2556         case FilterChannels:
2557                 _force_channel = -1;
2558                 break;
2559         case ForceChannel:
2560                 _force_channel = mask;
2561                 mask = 0xFFFF; // Show all notes as active (below)
2562         };
2563
2564         // Update notes for selection
2565         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2566                 (*i)->on_channel_selection_change(mask);
2567         }
2568
2569         _last_channel_selection = mask;
2570 }
2571
2572 void
2573 MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
2574 {
2575         _model_name         = model;
2576         _custom_device_mode = custom_device_mode;
2577         redisplay_model();
2578 }
2579
2580 void
2581 MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
2582 {
2583         if (_selection.empty()) {
2584                 return;
2585         }
2586
2587         PublicEditor& editor (trackview.editor());
2588
2589         switch (op) {
2590         case Cut:
2591         case Copy:
2592                 editor.get_cut_buffer().add (selection_as_cut_buffer());
2593                 break;
2594         default:
2595                 break;
2596         }
2597
2598         if (op != Copy) {
2599
2600                 start_delta_command();
2601                 
2602                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2603                         switch (op) {
2604                         case Copy:
2605                                 break;
2606                         case Cut:
2607                         case Clear:
2608                                 delta_remove_note (*i);
2609                                 break;
2610                         }
2611                 }
2612                 
2613                 apply_delta();
2614         }
2615 }
2616
2617 MidiCutBuffer*
2618 MidiRegionView::selection_as_cut_buffer () const
2619 {
2620         Notes notes;
2621
2622         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
2623                 NoteType* n = (*i)->note().get();
2624                 notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
2625         }
2626
2627         MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
2628         cb->set (notes);
2629
2630         return cb;
2631 }
2632
2633 void
2634 MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
2635 {
2636         if (mcb.empty()) {
2637                 return;
2638         }
2639
2640         start_delta_command (_("paste"));
2641
2642         Evoral::MusicalTime beat_delta;
2643         Evoral::MusicalTime paste_pos_beats;
2644         Evoral::MusicalTime duration;
2645         Evoral::MusicalTime end_point;
2646
2647         duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
2648         paste_pos_beats = frames_to_beats (pos - _region->position());
2649         beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
2650         paste_pos_beats = 0;
2651
2652         _selection.clear ();
2653
2654         for (int n = 0; n < (int) times; ++n) {
2655
2656                 cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
2657                 
2658                 for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
2659
2660                         boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
2661                         copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta);
2662
2663                         /* make all newly added notes selected */
2664
2665                         delta_add_note (copied_note, true);
2666                         end_point = copied_note->end_time();
2667                 }
2668
2669                 paste_pos_beats += duration;
2670         }
2671
2672         /* if we pasted past the current end of the region, extend the region */
2673
2674         nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
2675         nframes64_t region_end = _region->position() + _region->length() - 1;
2676
2677         if (end_frame > region_end) {
2678
2679                 cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
2680
2681                 trackview.session()->begin_reversible_command (_("paste"));
2682
2683                 _region->clear_history ();
2684                 _region->set_length (end_frame, this);
2685                 trackview.session()->add_command (new StatefulDiffCommand (_region));
2686         }
2687
2688         cerr << "region end finally at " << _region->position() + _region->length() - 1;
2689         apply_delta ();
2690 }
2691
2692 struct EventNoteTimeEarlyFirstComparator {
2693     bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
2694             return a->note()->time() < b->note()->time();
2695     }
2696 };
2697
2698 void
2699 MidiRegionView::time_sort_events ()
2700 {
2701         if (!_sort_needed) {
2702                 return;
2703         }
2704
2705         EventNoteTimeEarlyFirstComparator cmp;
2706         _events.sort (cmp);
2707
2708         _sort_needed = false;
2709 }
2710
2711 void
2712 MidiRegionView::goto_next_note ()
2713 {
2714         // nframes64_t pos = -1;
2715         bool use_next = false;
2716
2717         if (_events.back()->selected()) {
2718                 return;
2719         }
2720
2721         time_sort_events ();
2722
2723         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2724                 if ((*i)->selected()) {
2725                         use_next = true;
2726                         continue;
2727                 } else if (use_next) {
2728                         unique_select (*i);
2729                         // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2730                         return;
2731                 }
2732         }
2733
2734         /* use the first one */
2735
2736         unique_select (_events.front());
2737
2738 }
2739
2740 void
2741 MidiRegionView::goto_previous_note ()
2742 {
2743         // nframes64_t pos = -1;
2744         bool use_next = false;
2745
2746         if (_events.front()->selected()) {
2747                 return;
2748         }
2749
2750         time_sort_events ();
2751
2752         for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
2753                 if ((*i)->selected()) {
2754                         use_next = true;
2755                         continue;
2756                 } else if (use_next) {
2757                         unique_select (*i);
2758                         // pos = _region->position() + beats_to_frames ((*i)->note()->time());
2759                         return;
2760                 }
2761         }
2762
2763         /* use the last one */
2764
2765         unique_select (*(_events.rbegin()));
2766 }
2767
2768 void
2769 MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
2770 {
2771         bool had_selected = false;
2772
2773         time_sort_events ();
2774
2775         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2776                 if ((*i)->selected()) {
2777                         selected.insert ((*i)->note());
2778                         had_selected = true;
2779                 }
2780         }
2781         
2782         if (allow_all_if_none_selected && !had_selected) {
2783                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
2784                         selected.insert ((*i)->note());
2785                 }
2786         }
2787 }
2788
2789 void
2790 MidiRegionView::update_ghost_note (double x, double y)
2791 {
2792         _last_ghost_x = x;
2793         _last_ghost_y = y;
2794         
2795         group->w2i (x, y);
2796         nframes64_t f = trackview.editor().pixel_to_frame (x) + _region->position ();
2797         trackview.editor().snap_to (f);
2798         f -= _region->position ();
2799
2800         bool success;
2801         Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f);
2802         if (!success) {
2803                 beats = 1;
2804         }
2805         
2806         double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f);
2807         
2808         _ghost_note->note()->set_time (frames_to_beats (f + _region->start()));
2809         _ghost_note->note()->set_length (length);
2810         _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
2811
2812         update_note (_ghost_note);
2813
2814         show_verbose_canvas_cursor (_ghost_note->note ());
2815 }
2816
2817 void
2818 MidiRegionView::create_ghost_note (double x, double y)
2819 {
2820         delete _ghost_note;
2821         _ghost_note = 0;
2822
2823         boost::shared_ptr<NoteType> g (new NoteType);
2824         _ghost_note = new NoEventCanvasNote (*this, *group, g);
2825         update_ghost_note (x, y);
2826         _ghost_note->show ();
2827
2828         _last_ghost_x = x;
2829         _last_ghost_y = y;
2830
2831         show_verbose_canvas_cursor (_ghost_note->note ());
2832 }
2833
2834 void
2835 MidiRegionView::snap_changed ()
2836 {
2837         if (!_ghost_note) {
2838                 return;
2839         }
2840         
2841         create_ghost_note (_last_ghost_x, _last_ghost_y);
2842 }
2843
2844 void
2845 MidiRegionView::show_verbose_canvas_cursor (boost::shared_ptr<NoteType> n) const
2846 {
2847         char buf[12];
2848         snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (n->note()).c_str(), (int) n->note ());
2849         trackview.editor().show_verbose_canvas_cursor_with (buf);
2850 }