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