Fix menu missing fit-tracks.
[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
24 #include <gtkmm.h>
25
26 #include <gtkmm2ext/gtk_ui.h>
27
28 #include <sigc++/signal.h>
29
30 #include <ardour/playlist.h>
31 #include <ardour/tempo.h>
32 #include <ardour/midi_region.h>
33 #include <ardour/midi_source.h>
34 #include <ardour/midi_diskstream.h>
35 #include <ardour/midi_model.h>
36
37 #include "streamview.h"
38 #include "midi_region_view.h"
39 #include "midi_streamview.h"
40 #include "midi_time_axis.h"
41 #include "simpleline.h"
42 #include "canvas-hit.h"
43 #include "canvas-note.h"
44 #include "canvas-program-change.h"
45 #include "public_editor.h"
46 #include "ghostregion.h"
47 #include "midi_time_axis.h"
48 #include "automation_time_axis.h"
49 #include "automation_region_view.h"
50 #include "utils.h"
51 #include "midi_util.h"
52 #include "gui_thread.h"
53 #include "keyboard.h"
54
55 #include "i18n.h"
56
57 using namespace sigc;
58 using namespace ARDOUR;
59 using namespace PBD;
60 using namespace Editing;
61 using namespace ArdourCanvas;
62
63 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color)
64         : RegionView (parent, tv, r, spu, basic_color)
65         , _force_channel(-1)
66         , _last_channel_selection(0xFFFF)
67         , _default_note_length(0.0)
68         , _current_range_min(0)
69         , _current_range_max(0)
70         , _active_notes(0)
71         , _note_group(new ArdourCanvas::Group(*parent))
72         , _delta_command(NULL)
73         , _mouse_state(None)
74         , _pressed_button(0)
75 {
76         _note_group->raise_to_top();
77 }
78
79 MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility)
80         : RegionView (parent, tv, r, spu, basic_color, false, visibility)
81         , _force_channel(-1)
82         , _last_channel_selection(0xFFFF)
83         , _default_note_length(0.0)
84         , _active_notes(0)
85         , _note_group(new ArdourCanvas::Group(*parent))
86         , _delta_command(NULL)
87         , _mouse_state(None)
88         , _pressed_button(0)
89         
90 {
91         _note_group->raise_to_top();
92 }
93
94
95 MidiRegionView::MidiRegionView (const MidiRegionView& other)
96         : RegionView (other)
97         , _force_channel(-1)
98         , _last_channel_selection(0xFFFF)
99         , _default_note_length(0.0)
100         , _active_notes(0)
101         , _note_group(new ArdourCanvas::Group(*get_canvas_group()))
102         , _delta_command(NULL)
103         , _mouse_state(None)
104         , _pressed_button(0)
105 {
106         Gdk::Color c;
107         int r,g,b,a;
108
109         UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
110         c.set_rgb_p (r/255.0, g/255.0, b/255.0);
111         
112         init (c, false);
113 }
114
115 MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> other_region)
116         : RegionView (other, boost::shared_ptr<Region> (other_region))
117         , _force_channel(-1)
118         , _last_channel_selection(0xFFFF)
119         , _default_note_length(0.0)
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, true);
133 }
134
135 void
136 MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
137 {
138         if (wfd)
139                 midi_region()->midi_source(0)->load_model();
140
141         const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
142         const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
143         _default_note_length = m.frames_per_bar(t, trackview.session().frame_rate())
144                         / m.beats_per_bar();
145
146         _model = midi_region()->midi_source(0)->model();
147         _enable_display = false;
148
149         RegionView::init (basic_color, false);
150
151         compute_colors (basic_color);
152
153         set_height (trackview.current_height());
154
155         region_muted ();
156         region_sync_changed ();
157         region_resized (BoundsChanged);
158         region_locked ();
159         
160         reset_width_dependent_items (_pixel_width);
161         //reset_width_dependent_items ((double) _region->length() / samples_per_unit);
162
163         set_colors ();
164
165         _enable_display = true;
166         if (_model) {
167                 if (wfd) {
168                         redisplay_model();
169                 }
170                 _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
171         }
172
173         group->raise_to_top();
174         group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false);
175
176         midi_view()->signal_channel_mode_changed().connect(
177                         mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
178 }
179
180 bool
181 MidiRegionView::canvas_event(GdkEvent* ev)
182 {
183         static bool delete_mod = false;
184         static Editing::MidiEditMode original_mode;
185
186         static double drag_start_x, drag_start_y;
187         static double last_x, last_y;
188         double event_x, event_y;
189         nframes64_t event_frame = 0;
190
191         static ArdourCanvas::SimpleRect* drag_rect = NULL;
192
193         if (trackview.editor.current_mouse_mode() != MouseNote)
194                 return false;
195
196         // Mmmm, spaghetti
197
198         switch (ev->type) {
199         case GDK_KEY_PRESS:
200                 if (ev->key.keyval == GDK_Delete && !delete_mod) {
201                         delete_mod = true;
202                         original_mode = trackview.editor.current_midi_edit_mode();
203                         trackview.editor.set_midi_edit_mode(MidiEditErase);
204                         start_delta_command(_("erase notes"));
205                         _mouse_state = EraseTouchDragging;
206                         return true;
207                 } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
208                         _mouse_state = SelectTouchDragging;
209                         return true;
210                 } else if (ev->key.keyval == GDK_Escape) {
211                         clear_selection();
212                         _mouse_state = None;
213                 }
214                 return false;
215
216         case GDK_KEY_RELEASE:
217                 if (ev->key.keyval == GDK_Delete) {
218                         if (_mouse_state == EraseTouchDragging) {
219                                 delete_selection();
220                                 apply_command();
221                         }
222                         if (delete_mod) {
223                                 trackview.editor.set_midi_edit_mode(original_mode);
224                                 _mouse_state = None;
225                                 delete_mod = false;
226                         }
227                         return true;
228                 } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
229                         _mouse_state = None;
230                         return true;
231                 }
232                 return false;
233
234         case GDK_BUTTON_PRESS:
235                 if (_mouse_state != SelectTouchDragging && 
236                         _mouse_state != EraseTouchDragging &&
237                         ev->button.button == 1) {
238                         _pressed_button = ev->button.button;
239                         _mouse_state = Pressed;
240                         return true;
241                 }
242                 _pressed_button = ev->button.button;
243                 return true;
244
245         case GDK_2BUTTON_PRESS:
246                 return true;
247
248         case GDK_ENTER_NOTIFY:
249                 /* FIXME: do this on switch to note tool, too, if the pointer is already in */
250                 Keyboard::magic_widget_grab_focus();
251                 group->grab_focus();
252                 break;
253
254         case GDK_MOTION_NOTIFY:
255                 event_x = ev->motion.x;
256                 event_y = ev->motion.y;
257                 group->w2i(event_x, event_y);
258
259                 // convert event_x to global frame
260                 event_frame = trackview.editor.pixel_to_frame(event_x) + _region->position();
261                 trackview.editor.snap_to(event_frame);
262                 // convert event_frame back to local coordinates relative to position
263                 event_frame -= _region->position();
264
265                 switch (_mouse_state) {
266                 case Pressed: // Drag start
267
268                         // Select drag start
269                         if (_pressed_button == 1 && trackview.editor.current_midi_edit_mode() == MidiEditSelect) {
270                                 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
271                                                 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
272                                 last_x = event_x;
273                                 last_y = event_y;
274                                 drag_start_x = event_x;
275                                 drag_start_y = event_y;
276
277                                 drag_rect = new ArdourCanvas::SimpleRect(*group);
278                                 drag_rect->property_x1() = event_x;
279                                 drag_rect->property_y1() = event_y;
280                                 drag_rect->property_x2() = event_x;
281                                 drag_rect->property_y2() = event_y;
282                                 drag_rect->property_outline_what() = 0xFF;
283                                 drag_rect->property_outline_color_rgba()
284                                         = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
285                                 drag_rect->property_fill_color_rgba()
286                                         = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
287
288                                 _mouse_state = SelectRectDragging;
289                                 return true;
290
291                         // Add note drag start
292                         } else if (trackview.editor.current_midi_edit_mode() == MidiEditPencil) {
293                                 group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
294                                                 Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
295                                 last_x = event_x;
296                                 last_y = event_y;
297                                 drag_start_x = event_x;
298                                 drag_start_y = event_y;
299
300                                 drag_rect = new ArdourCanvas::SimpleRect(*group);
301                                 drag_rect->property_x1() = trackview.editor.frame_to_pixel(event_frame);
302
303                                 drag_rect->property_y1() = midi_stream_view()->note_to_y(midi_stream_view()->y_to_note(event_y));
304                                 drag_rect->property_x2() = event_x;
305                                 drag_rect->property_y2() = drag_rect->property_y1() + floor(midi_stream_view()->note_height());
306                                 drag_rect->property_outline_what() = 0xFF;
307                                 drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
308
309                                 drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
310
311                                 _mouse_state = AddDragging;
312                                 return true;
313                         }
314
315                         return false;
316
317                 case SelectRectDragging: // Select drag motion
318                 case AddDragging: // Add note drag motion
319                         if (ev->motion.is_hint) {
320                                 int t_x;
321                                 int t_y;
322                                 GdkModifierType state;
323                                 gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
324                                 event_x = t_x;
325                                 event_y = t_y;
326                         }
327
328                         if (_mouse_state == AddDragging)
329                                 event_x = trackview.editor.frame_to_pixel(event_frame);
330
331                         if (drag_rect) {
332                                 if (event_x > drag_start_x)
333                                         drag_rect->property_x2() = event_x;
334                                 else
335                                         drag_rect->property_x1() = event_x;
336                         }
337
338                         if (drag_rect && _mouse_state == SelectRectDragging) {
339                                 if (event_y > drag_start_y)
340                                         drag_rect->property_y2() = event_y;
341                                 else
342                                         drag_rect->property_y1() = event_y;
343
344                                 update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
345                         }
346
347                         last_x = event_x;
348                         last_y = event_y;
349
350                 case EraseTouchDragging:
351                 case SelectTouchDragging:
352                         return false;
353
354                 default:
355                         break;
356                 }
357                 break;
358
359         case GDK_BUTTON_RELEASE:
360                 event_x = ev->motion.x;
361                 event_y = ev->motion.y;
362                 group->w2i(event_x, event_y);
363                 group->ungrab(ev->button.time);
364                 event_frame = trackview.editor.pixel_to_frame(event_x);
365
366                 if (_pressed_button != 1) {
367                         return false;
368                 }
369                         
370                 switch (_mouse_state) {
371                 case Pressed: // Clicked
372                         switch (trackview.editor.current_midi_edit_mode()) {
373                         case MidiEditSelect:
374                         case MidiEditResize:
375                                 clear_selection();
376                                 break;
377                         case MidiEditPencil:
378                                 create_note_at(event_x, event_y, _default_note_length);
379                         default: break;
380                         }
381                         _mouse_state = None;
382                         break;
383                 case SelectRectDragging: // Select drag done
384                         _mouse_state = None;
385                         delete drag_rect;
386                         drag_rect = NULL;
387                         break;
388                 case AddDragging: // Add drag done
389                         _mouse_state = None;
390                         if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
391                                 const double x      = drag_rect->property_x1();
392                                 const double length = trackview.editor.pixel_to_frame(
393                                                         drag_rect->property_x2() - drag_rect->property_x1());
394                                         
395                                 create_note_at(x, drag_rect->property_y1(), length);
396                         }
397
398                         delete drag_rect;
399                         drag_rect = NULL;
400                 default: break;
401                 }
402
403         default: break;
404         }
405
406         return false;
407 }
408
409
410 /** Add a note to the model, and the view, at a canvas (click) coordinate */
411 void
412 MidiRegionView::create_note_at(double x, double y, double length)
413 {
414         MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
415         MidiStreamView* const view = mtv->midi_view();
416
417         double note = midi_stream_view()->y_to_note(y);
418
419         assert(note >= 0.0);
420         assert(note <= 127.0);
421
422         nframes64_t new_note_time = trackview.editor.pixel_to_frame (x);
423         assert(new_note_time >= 0);
424         new_note_time += _region->start();
425
426         /*
427         const Meter& m = trackview.session().tempo_map().meter_at(new_note_time);
428         const Tempo& t = trackview.session().tempo_map().tempo_at(new_note_time);
429         double length = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
430         */
431         
432         // we need to snap here again in nframes64_t in order to be sample accurate 
433         // since note time is region-absolute but snap_to_frame expects position-relative
434         // time we have to coordinate transform back and forth here.
435         nframes64_t new_note_time_position_relative = new_note_time      - _region->start(); 
436         new_note_time = snap_to_frame(new_note_time_position_relative) + _region->start();
437         
438         // we need to snap the length too to be sample accurate
439         nframes64_t new_note_length = nframes_t(length);
440         new_note_length = snap_to_frame(new_note_time_position_relative + new_note_length) + _region->start() 
441                             - new_note_time;
442
443         const boost::shared_ptr<Evoral::Note> new_note(new Evoral::Note(
444                         0, new_note_time, new_note_length, (uint8_t)note, 0x40));
445         view->update_note_range(new_note->note());
446
447         MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
448         cmd->add(new_note);
449         _model->apply_command(trackview.session(), cmd);
450 }
451
452
453 void
454 MidiRegionView::clear_events()
455 {
456         clear_selection();
457
458         MidiGhostRegion* gr;
459         for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
460                 if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
461                         gr->clear_events();
462                 }
463         }
464
465         for (Events::iterator i = _events.begin(); i != _events.end(); ++i)
466                 delete *i;
467
468         _events.clear();
469         _pgm_changes.clear();
470 }
471
472
473 void
474 MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
475 {
476         _model = model;
477
478         if (_enable_display)
479                 redisplay_model();
480 }
481         
482         
483 void
484 MidiRegionView::start_delta_command(string name)
485 {
486         if (!_delta_command)
487                 _delta_command = _model->new_delta_command(name);
488 }
489
490 void
491 MidiRegionView::command_add_note(const boost::shared_ptr<Evoral::Note> note, bool selected)
492 {
493         if (_delta_command)
494                 _delta_command->add(note);
495
496         if (selected)
497                 _marked_for_selection.insert(note);
498 }
499
500 void
501 MidiRegionView::command_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
502 {
503         if (_delta_command && ev->note()) {
504                 _delta_command->remove(ev->note());
505         }
506 }
507         
508 void
509 MidiRegionView::apply_command()
510 {
511         if (!_delta_command) {
512                 return;
513         }
514
515         // Mark all selected notes for selection when model reloads
516         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
517                 _marked_for_selection.insert((*i)->note());
518         }
519         
520         _model->apply_command(trackview.session(), _delta_command);
521         _delta_command = NULL; 
522         midi_view()->midi_track()->diskstream()->playlist_modified();
523
524         _marked_for_selection.clear();
525 }
526         
527
528 void
529 MidiRegionView::abort_command()
530 {
531         delete _delta_command;
532         _delta_command = NULL;
533         clear_selection();
534 }
535
536
537 void
538 MidiRegionView::redisplay_model()
539 {
540         // Don't redisplay the model if we're currently recording and displaying that
541         if (_active_notes)
542                 return;
543
544         if (_model) {
545
546                 clear_events();
547                 _model->read_lock();
548                 
549                 /*MidiModel::Notes notes = _model->notes();
550                 cerr << endl << _model->midi_source()->name() << " : redisplaying " << notes.size() << " notes:" << endl;
551                 for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) {
552                         cerr << "NOTE  time: " << (*i)->time()
553                                  << "  pitch: " << int((*i)->note()) 
554                              << "  length: " << (*i)->length() 
555                              << "  end-time: " << (*i)->end_time() 
556                              << "  velocity: " << int((*i)->velocity()) 
557                              << endl;
558                 }*/
559                 
560                 for (size_t i = 0; i < _model->n_notes(); ++i)
561                         add_note(_model->note_at(i));
562
563                 // Draw program change 'flags'
564                 for (Automatable::Controls::iterator control = _model->controls().begin();
565                                 control != _model->controls().end(); ++control) {
566                         if (control->first.type() == MidiPgmChangeAutomation) {
567                                 Glib::Mutex::Lock list_lock (control->second->list()->lock());
568                                 
569                                 for (AutomationList::const_iterator event = control->second->list()->begin();
570                                                 event != control->second->list()->end(); ++event) {
571                                         Evoral::ControlIterator iter(control->second->list(), (*event)->when, (*event)->value);
572                                         boost::shared_ptr<Evoral::Event> event(new Evoral::Event());
573                                         _model->control_to_midi_event(event, iter);
574                                         add_pgm_change(event);
575                                 }
576                                 break;
577                         }
578                 }
579
580                 // Is this necessary?
581                 /*for (Automatable::Controls::const_iterator i = _model->controls().begin();
582                                 i != _model->controls().end(); ++i) {
583
584                         assert(i->second);
585
586                         boost::shared_ptr<AutomationTimeAxisView> at
587                                 = midi_view()->automation_child(i->second->parameter());
588                         if (!at)
589                                 continue;
590
591                         Gdk::Color col = midi_stream_view()->get_region_color();
592
593                         boost::shared_ptr<AutomationRegionView> arv;
594
595                         {
596                                 Glib::Mutex::Lock list_lock (i->second->list()->lock());
597
598                                 arv = boost::shared_ptr<AutomationRegionView>(
599                                                 new AutomationRegionView(at->canvas_display,
600                                                         *at.get(), _region, i->second->list(),
601                                                         midi_stream_view()->get_samples_per_unit(), col));
602                         }
603
604                         arv->set_duration(_region->length(), this);
605                         arv->init(col, true);
606
607                         _automation_children.insert(std::make_pair(i->second->parameter(), arv));
608                 }*/
609
610                 _model->read_unlock();
611
612         } else {
613                 cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
614         }
615 }
616
617
618 MidiRegionView::~MidiRegionView ()
619 {
620         in_destructor = true;
621
622         RegionViewGoingAway (this); /* EMIT_SIGNAL */
623
624         if (_active_notes) {
625                 end_write();
626         }
627
628         _selection.clear();
629         clear_events();
630         delete _note_group;
631         delete _delta_command;
632 }
633
634
635 void
636 MidiRegionView::region_resized (Change what_changed)
637 {
638         RegionView::region_resized(what_changed);
639         
640         if (what_changed & ARDOUR::PositionChanged) {
641                 if (_enable_display)
642                         redisplay_model();
643         } 
644 }
645
646 void
647 MidiRegionView::reset_width_dependent_items (double pixel_width)
648 {
649         RegionView::reset_width_dependent_items(pixel_width);
650         assert(_pixel_width == pixel_width);
651
652         if (_enable_display)
653                 redisplay_model();
654 }
655
656 void
657 MidiRegionView::set_height (gdouble height)
658 {
659         static const double FUDGE = 2;
660         const double old_height = _height;
661         RegionView::set_height(height);
662         _height = height - FUDGE;
663         
664         apply_note_range(midi_stream_view()->lowest_note(),
665                          midi_stream_view()->highest_note(),
666                          height != old_height + FUDGE);
667         
668         if (name_text) {
669                 name_text->raise_to_top();
670         }
671 }
672
673
674 /** Apply the current note range from the stream view
675  * by repositioning/hiding notes as necessary
676  */
677 void
678 MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
679 {
680         if (_enable_display) {
681                 if (!force && _current_range_min == min && _current_range_max == max) {
682                         return;
683                 }
684                 
685                 _current_range_min = min;
686                 _current_range_max = max;
687
688                 for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
689                         CanvasNoteEvent* event = *i;
690                         Item* item = dynamic_cast<Item*>(event);
691                         assert(item);
692                         if (event && event->note()) {
693                                 if (event->note()->note() < _current_range_min || event->note()->note() > _current_range_max) {
694                                         if (canvas_item_visible(item)) {
695                                                 item->hide();
696                                         }
697                                 } else {
698                                         if (!canvas_item_visible(item)) {
699                                                 item->show();
700                                         }
701
702                                         event->hide_velocity();
703                                         if (CanvasNote* note = dynamic_cast<CanvasNote*>(event)) {
704                                                 const double y1 = midi_stream_view()->note_to_y(event->note()->note());
705                                                 const double y2 = y1 + floor(midi_stream_view()->note_height());
706
707                                                 note->property_y1() = y1;
708                                                 note->property_y2() = y2;
709                                         } else if (CanvasHit* hit = dynamic_cast<CanvasHit*>(event)) {
710                                                 double x = trackview.editor.frame_to_pixel((nframes64_t)
711                                                                 event->note()->time() - _region->start());
712                                                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
713                                                 double y = midi_stream_view()->note_to_y(event->note()->note()) 
714                                                                  + ((diamond_size-2.0) / 4.0);
715                                                 
716                                                 hit->set_height(diamond_size);
717                                                 hit->move(x-hit->x1(), y-hit->y1());
718                                                 hit->show();
719                                         }
720                                         if (event->selected()) {
721                                                 event->show_velocity();
722                                         }
723                                 }
724                         }
725                 }
726
727         }
728 }
729
730 GhostRegion*
731 MidiRegionView::add_ghost (TimeAxisView& tv)
732 {
733         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&trackview);
734         CanvasNote* note;
735         assert(rtv);
736
737         double unit_position = _region->position () / samples_per_unit;
738         MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
739         MidiGhostRegion* ghost;
740
741         if (mtv && mtv->midi_view()) {
742                 /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group.
743                    this is because it's nice to have midi notes on top of the note lines and
744                    audio waveforms under it.
745                  */
746                 ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
747         }
748         else {
749                 ghost = new MidiGhostRegion (tv, trackview, unit_position);
750         }
751
752         ghost->set_height ();
753         ghost->set_duration (_region->length() / samples_per_unit);
754         ghosts.push_back (ghost);
755
756         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
757                 if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
758                         ghost->add_note(note);
759                 }
760         }
761
762         ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
763
764         return ghost;
765 }
766
767
768 /** Begin tracking note state for successive calls to add_event
769  */
770 void
771 MidiRegionView::begin_write()
772 {
773         assert(!_active_notes);
774         _active_notes = new CanvasNote*[128];
775         for (unsigned i=0; i < 128; ++i) {
776                 _active_notes[i] = NULL;
777         }
778 }
779
780
781 /** Destroy note state for add_event
782  */
783 void
784 MidiRegionView::end_write()
785 {
786         delete[] _active_notes;
787         _active_notes = NULL;
788         _marked_for_selection.clear();
789 }
790
791
792 /** Resolve an active MIDI note (while recording).
793  */
794 void
795 MidiRegionView::resolve_note(uint8_t note, double end_time)
796 {
797         if (midi_view()->note_mode() != Sustained)
798                 return;
799
800         if (_active_notes && _active_notes[note]) {
801                 _active_notes[note]->property_x2() = trackview.editor.frame_to_pixel((nframes64_t)end_time);
802                 _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
803                 _active_notes[note] = NULL;
804         }
805 }
806
807
808 /** Extend active notes to rightmost edge of region (if length is changed)
809  */
810 void
811 MidiRegionView::extend_active_notes()
812 {
813         if (!_active_notes) {
814                 return;
815         }
816
817         for (unsigned i=0; i < 128; ++i) {
818                 if (_active_notes[i]) {
819                         _active_notes[i]->property_x2() = trackview.editor.frame_to_pixel(_region->length());
820                 }
821         }
822 }
823
824
825 /** Add a MIDI note to the view (with length).
826  *
827  * If in sustained mode, notes with length 0 will be considered active
828  * notes, and resolve_note should be called when the corresponding note off
829  * event arrives, to properly display the note.
830  */
831 void
832 MidiRegionView::add_note(const boost::shared_ptr<Evoral::Note> note)
833 {
834         assert(note->time() >= 0);
835         assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
836         
837         // dont display notes beyond the region bounds
838         if ( note->time() - _region->start() >= _region->length() ||
839                 note->time() <  _region->start() ||
840                 note->note() < midi_stream_view()->lowest_note() ||
841                 note->note() > midi_stream_view()->highest_note() ) {
842                 return;
843         }
844         
845         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
846
847         CanvasNoteEvent* event = 0;
848         
849         const double x = trackview.editor.frame_to_pixel((nframes64_t)note->time() - _region->start());
850         
851         if (midi_view()->note_mode() == Sustained) {
852
853                 const double y1 = midi_stream_view()->note_to_y(note->note());
854                 const double note_endpixel = 
855                         trackview.editor.frame_to_pixel((nframes64_t)note->end_time() - _region->start());
856                 
857                 CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
858                 ev_rect->property_x1() = x;
859                 ev_rect->property_y1() = y1;
860                 if (note->length() > 0)
861                         ev_rect->property_x2() = note_endpixel;
862                 else
863                         ev_rect->property_x2() = trackview.editor.frame_to_pixel(_region->length());
864                 ev_rect->property_y2() = y1 + floor(midi_stream_view()->note_height());
865
866                 if (note->length() == 0) {
867
868                         if (_active_notes) {
869                                 assert(note->note() < 128);
870                                 // If this note is already active there's a stuck note,
871                                 // finish the old note rectangle
872                                 if (_active_notes[note->note()]) {
873                                         CanvasNote* const old_rect = _active_notes[note->note()];
874                                         boost::shared_ptr<Evoral::Note> old_note = old_rect->note();
875                                         cerr << "MidiModel: WARNING: Note has length 0: chan " << old_note->channel()
876                                                 << "note " << (int)old_note->note() << " @ " << old_note->time() << endl;
877                                         /* FIXME: How large to make it?  Make it a diamond? */
878                                         old_rect->property_x2() = old_rect->property_x1() + 2.0;
879                                         old_rect->property_outline_what() = (guint32) 0xF;
880                                 }
881                                 _active_notes[note->note()] = ev_rect;
882                         }
883                         /* outline all but right edge */
884                         ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
885                 } else {
886                         /* outline all edges */
887                         ev_rect->property_outline_what() = (guint32) 0xF;
888                 }
889
890                 ev_rect->show();
891                 _events.push_back(ev_rect);
892                 event = ev_rect;
893
894                 MidiGhostRegion* gr;
895
896                 for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
897                         if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
898                                 gr->add_note(ev_rect);
899                         }
900                 }
901
902         } else if (midi_view()->note_mode() == Percussive) {
903
904                 //cerr << "MRV::add_note percussive " << note->note() << " @ " << note->time()
905                 //      << " .. " << note->end_time() << endl;
906
907                 const double diamond_size = midi_stream_view()->note_height() / 2.0;
908                 const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
909
910                 CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
911                 ev_diamond->move(x, y);
912                 ev_diamond->show();
913                 _events.push_back(ev_diamond);
914                 event = ev_diamond;
915         } else {
916                 event = 0;
917         }
918
919         if (event) {                    
920                 if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
921                         note_selected(event, true);
922                 }
923                 event->on_channel_selection_change(_last_channel_selection);
924         }
925 }
926
927 void
928 MidiRegionView::add_pgm_change(boost::shared_ptr<Evoral::Event> event)
929 {
930         assert(event->time() >= 0);
931         
932         // dont display notes beyond the region bounds
933         if (event->time() - _region->start() >= _region->length() || event->time() <  _region->start()) 
934                 return;
935         
936         ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
937         const double x = trackview.editor.frame_to_pixel((nframes64_t)event->time() - _region->start());
938         
939         double height = midi_stream_view()->contents_height();
940         _pgm_changes.push_back(
941                 boost::shared_ptr<CanvasProgramChange>(
942                         new CanvasProgramChange(*this, *group, event, height, x, 1.0)));
943 }
944
945 void
946 MidiRegionView::delete_selection()
947 {
948         assert(_delta_command);
949
950         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
951                 if ((*i)->selected()) {
952                         _delta_command->remove((*i)->note());
953                 }
954         }
955
956         _selection.clear();
957 }
958
959 void
960 MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
961 {
962         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
963                 if ((*i)->selected() && (*i) != ev) {
964                         (*i)->selected(false);
965                 }
966         }
967
968         _selection.clear();
969 }
970
971 void
972 MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
973 {
974         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
975                 if ((*i) != ev) {
976                         (*i)->selected(false);
977                 }
978         }
979
980         _selection.clear();
981         _selection.insert(ev);
982
983         if ( ! ev->selected()) {
984                 ev->selected(true);
985         }
986 }
987
988 void
989 MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add)
990 {
991         if ( ! add) {
992                 clear_selection_except(ev);
993         }
994
995         _selection.insert(ev);
996
997         if ( ! ev->selected()) {
998                 ev->selected(true);
999         }
1000 }
1001
1002
1003 void
1004 MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev, bool add)
1005 {
1006         if ( ! add) {
1007                 clear_selection_except(ev);
1008         }
1009
1010         _selection.erase(ev);
1011
1012         if (ev->selected()) {
1013                 ev->selected(false);
1014         }
1015 }
1016
1017
1018 void
1019 MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
1020 {
1021         const double last_y = std::min(y1, y2);
1022         const double y      = std::max(y1, y2);
1023
1024         // TODO: Make this faster by storing the last updated selection rect, and only
1025         // adjusting things that are in the area that appears/disappeared.
1026         // We probably need a tree to be able to find events in O(log(n)) time.
1027
1028 #ifndef NDEBUG
1029         double last_x1 = 0.0;
1030 #endif
1031
1032         if (x1 < x2) {
1033                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1034 #ifndef NDEBUG
1035                         // Events should always be sorted by increasing x1() here
1036                         assert((*i)->x1() >= last_x1);
1037                         last_x1 = (*i)->x1();
1038 #endif
1039                         // Inside rectangle
1040                         if ((*i)->x1() >= x1 && (*i)->x1() <= x2 && (*i)->y1() >= last_y && (*i)->y1() <= y) {
1041                                 if (!(*i)->selected()) {
1042                                         (*i)->selected(true);
1043                                         _selection.insert(*i);
1044                                 }
1045                         // Not inside rectangle
1046                         } else if ((*i)->selected()) {
1047                                 (*i)->selected(false);
1048                                 _selection.erase(*i);
1049                         }
1050                 }
1051         } else {
1052                 for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1053 #ifndef NDEBUG
1054                         // Events should always be sorted by increasing x1() here
1055                         assert((*i)->x1() >= last_x1);
1056                         last_x1 = (*i)->x1();
1057 #endif
1058                         // Inside rectangle
1059                         if ((*i)->x2() <= x1 && (*i)->x2() >= x2 && (*i)->y1() >= last_y && (*i)->y1() <= y) {
1060                                 if (!(*i)->selected()) {
1061                                         (*i)->selected(true);
1062                                         _selection.insert(*i);
1063                                 }
1064                         // Not inside rectangle
1065                         } else if ((*i)->selected()) {
1066                                 (*i)->selected(false);
1067                                 _selection.erase(*i);
1068                         }
1069                 }
1070         }
1071 }
1072
1073
1074 void
1075 MidiRegionView::move_selection(double dx, double dy)
1076 {
1077         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i)
1078                 (*i)->move_event(dx, dy);
1079 }
1080
1081
1082 void
1083 MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
1084 {
1085         // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
1086         if (_selection.find(ev) != _selection.end()) {
1087                 uint8_t lowest_note_in_selection  = midi_stream_view()->lowest_note();
1088                 uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
1089                 uint8_t highest_note_difference = 0;
1090
1091                 // find highest and lowest notes first
1092                 for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1093                         uint8_t pitch = (*i)->note()->note();
1094                         lowest_note_in_selection  = std::min(lowest_note_in_selection,  pitch);
1095                         highest_note_in_selection = std::max(highest_note_in_selection, pitch);
1096                 }
1097                 
1098                 /*
1099                 cerr << "dnote: " << (int) dnote << endl;
1100                 cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note()) 
1101                      << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
1102                 cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): " 
1103                      << int(highest_note_in_selection) << endl;
1104                 cerr << "selection size: " << _selection.size() << endl;
1105                 cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
1106                 */
1107                 
1108                 // Make sure the note pitch does not exceed the MIDI standard range
1109                 if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
1110                         highest_note_difference = highest_note_in_selection - 127;
1111                 }
1112                 
1113                 start_delta_command(_("move notes"));
1114
1115                 for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
1116                         Selection::iterator next = i;
1117                         ++next;
1118
1119                         const boost::shared_ptr<Evoral::Note> copy(new Evoral::Note(*(*i)->note().get()));
1120
1121                         // we need to snap here again in nframes64_t in order to be sample accurate 
1122                         double new_note_time = (*i)->note()->time();
1123                         new_note_time +=  dt;
1124
1125                         // keep notes inside region if dragged beyond left region bound
1126                         if (new_note_time < _region->start()) {                         
1127                                 new_note_time = _region->start();
1128                         }
1129                         
1130                         // since note time is region-absolute but snap_to_frame expects position-relative
1131                         // time we have to coordinate transform back and forth here.
1132                         new_note_time = snap_to_frame(nframes64_t(new_note_time) - _region->start()) + _region->start();
1133                         
1134                         copy->set_time(new_note_time);
1135
1136                         uint8_t original_pitch = (*i)->note()->note();
1137                         uint8_t new_pitch =  original_pitch + dnote - highest_note_difference;
1138                         
1139                         // keep notes in standard midi range
1140                         clamp_0_to_127(new_pitch);
1141                         
1142                         //notes which are dragged beyond the standard midi range snap back to their original place
1143                         if ((original_pitch != 0 && new_pitch == 0) || (original_pitch != 127 && new_pitch == 127)) {
1144                                 new_pitch = original_pitch;
1145                         }
1146
1147                         lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
1148                         highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
1149
1150                         copy->set_note(new_pitch);
1151                         
1152                         command_remove_note(*i);
1153                         command_add_note(copy, true);
1154
1155                         i = next;
1156                 }
1157
1158                 apply_command();
1159                 
1160                 // care about notes being moved beyond the upper/lower bounds on the canvas
1161                 if (lowest_note_in_selection  < midi_stream_view()->lowest_note() ||
1162                                 highest_note_in_selection > midi_stream_view()->highest_note()) {
1163                         midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
1164                 }
1165         }
1166 }
1167
1168 nframes64_t
1169 MidiRegionView::snap_to_frame(double x)
1170 {
1171         PublicEditor &editor = trackview.editor;
1172         // x is region relative
1173         // convert x to global frame
1174         nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
1175         editor.snap_to(frame);
1176         // convert event_frame back to local coordinates relative to position
1177         frame -= _region->position();
1178         return frame;
1179 }
1180
1181 nframes64_t
1182 MidiRegionView::snap_to_frame(nframes64_t x)
1183 {
1184         PublicEditor &editor = trackview.editor;
1185         // x is region relative
1186         // convert x to global frame
1187         nframes64_t frame = x + _region->position();
1188         editor.snap_to(frame);
1189         // convert event_frame back to local coordinates relative to position
1190         frame -= _region->position();
1191         return frame;
1192 }
1193
1194 double
1195 MidiRegionView::snap_to_pixel(double x)
1196 {
1197         return (double) trackview.editor.frame_to_pixel(snap_to_frame(x));
1198 }
1199
1200 double
1201 MidiRegionView::get_position_pixels(void)
1202 {
1203         nframes64_t  region_frame  = get_position();
1204         return trackview.editor.frame_to_pixel(region_frame);
1205 }
1206
1207 void
1208 MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
1209 {
1210         _resize_data.clear();
1211
1212         for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
1213                 CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
1214
1215                 // only insert CanvasNotes into the map
1216                 if (note) {
1217                         NoteResizeData *resize_data = new NoteResizeData();
1218                         resize_data->canvas_note = note;
1219
1220                         // create a new SimpleRect from the note which will be the resize preview
1221                         SimpleRect *resize_rect =
1222                                 new SimpleRect(
1223                                                 *group,
1224                                                 note->x1(),
1225                                                 note->y1(),
1226                                                 note->x2(),
1227                                                 note->y2());
1228
1229                         // calculate the colors: get the color settings
1230                         uint32_t fill_color =
1231                                 UINT_RGBA_CHANGE_A(
1232                                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(),
1233                                                 128);
1234
1235                         // make the resize preview notes more transparent and bright
1236                         fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
1237
1238                         // calculate color based on note velocity
1239                         resize_rect->property_fill_color_rgba() =
1240                                 UINT_INTERPOLATE(
1241                                         note_fill_color(note->note()->velocity()),
1242                                         fill_color,
1243                                         0.85);
1244
1245                         resize_rect->property_outline_color_rgba() =
1246                                 ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get();
1247
1248                         resize_data->resize_rect = resize_rect;
1249
1250                         if (note_end == CanvasNote::NOTE_ON) {
1251                                 resize_data->current_x = note->x1();
1252                         } else { // NOTE_OFF
1253                                 resize_data->current_x = note->x2();
1254                         }
1255
1256                         _resize_data.push_back(resize_data);
1257                 }
1258         }
1259 }
1260
1261 void
1262 MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool relative)
1263 {
1264         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1265                 SimpleRect     *resize_rect = (*i)->resize_rect;
1266                 CanvasNote     *canvas_note = (*i)->canvas_note;
1267
1268                 const double region_start = get_position_pixels();
1269
1270                 if (relative) {
1271                         (*i)->current_x = (*i)->current_x + x;
1272                 } else {
1273                         // x is in track relative, transform it to region relative
1274                         (*i)->current_x = x - region_start;
1275                 }
1276
1277                 double current_x = (*i)->current_x;
1278
1279                 if (note_end == CanvasNote::NOTE_ON) {
1280                         resize_rect->property_x1() = snap_to_pixel(current_x);
1281                         resize_rect->property_x2() = canvas_note->x2();
1282                 } else {
1283                         resize_rect->property_x2() = snap_to_pixel(current_x);
1284                         resize_rect->property_x1() = canvas_note->x1();
1285                 }
1286         }
1287 }
1288
1289 void
1290 MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bool relative)
1291 {
1292         start_delta_command(_("resize notes"));
1293
1294         for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
1295                 CanvasNote*  canvas_note = (*i)->canvas_note;
1296                 SimpleRect*  resize_rect = (*i)->resize_rect;
1297                 double       current_x   = (*i)->current_x;
1298                 const double position    = get_position_pixels();
1299
1300                 if (!relative) {
1301                         // event_x is in track relative, transform it to region relative
1302                         current_x = event_x - position;
1303                 }
1304
1305                 // because snapping works on world coordinates we have to transform current_x
1306                 // to world coordinates before snapping and transform it back afterwards
1307                 nframes64_t current_frame = snap_to_frame(current_x);
1308                 // transform to region start relative
1309                 current_frame += _region->start();
1310                 
1311                 const boost::shared_ptr<Evoral::Note> copy(new Evoral::Note(*(canvas_note->note().get())));
1312
1313                 // resize beginning of note
1314                 if (note_end == CanvasNote::NOTE_ON && current_frame < copy->end_time()) {
1315                         command_remove_note(canvas_note);
1316                         copy->on_event().time() = current_frame;
1317                         command_add_note(copy, _selection.find(canvas_note) != _selection.end());
1318                 }
1319                 // resize end of note
1320                 if (note_end == CanvasNote::NOTE_OFF && current_frame > copy->time()) {
1321                         command_remove_note(canvas_note);
1322                         copy->off_event().time() = current_frame;
1323                         command_add_note(copy, _selection.find(canvas_note) != _selection.end());
1324                 }
1325
1326                 delete resize_rect;
1327                 delete (*i);
1328         }
1329
1330         _resize_data.clear();
1331         apply_command();
1332 }
1333
1334
1335 void
1336 MidiRegionView::change_velocity(uint8_t velocity, bool relative)
1337 {
1338         start_delta_command(_("change velocity"));
1339         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
1340                 Selection::iterator next = i;
1341                 ++next;
1342
1343                 CanvasNoteEvent *event = *i;
1344                 const boost::shared_ptr<Evoral::Note> copy(new Evoral::Note(*(event->note().get())));
1345
1346                 if (relative) {
1347                         uint8_t new_velocity = copy->velocity() + velocity;
1348                         clamp_0_to_127(new_velocity);
1349                                 
1350                         copy->set_velocity(new_velocity);
1351                 } else { // absolute
1352                         copy->set_velocity(velocity);                   
1353                 }
1354                 
1355                 command_remove_note(event);
1356                 command_add_note(copy, true);
1357                 
1358                 i = next;
1359         }
1360         
1361         apply_command();
1362 }
1363
1364 void
1365 MidiRegionView::change_channel(uint8_t channel)
1366 {
1367         start_delta_command(_("change channel"));
1368         for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
1369                 Selection::iterator next = i;
1370                 ++next;
1371
1372                 CanvasNoteEvent *event = *i;
1373                 const boost::shared_ptr<Evoral::Note> copy(new Evoral::Note(*(event->note().get())));
1374
1375                 copy->set_channel(channel);
1376                 
1377                 command_remove_note(event);
1378                 command_add_note(copy, true);
1379                 
1380                 i = next;
1381         }
1382         
1383         apply_command();
1384 }
1385
1386
1387 void
1388 MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
1389 {
1390         if (ev->note() && _mouse_state == EraseTouchDragging) {
1391                 start_delta_command(_("note entered"));
1392                 ev->selected(true);
1393                 _delta_command->remove(ev->note());
1394         } else if (_mouse_state == SelectTouchDragging) {
1395                 note_selected(ev, true);
1396         }
1397 }
1398
1399
1400 void
1401 MidiRegionView::switch_source(boost::shared_ptr<Source> src)
1402 {
1403         boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
1404         if (msrc)
1405                 display_model(msrc->model());
1406 }
1407
1408 void
1409 MidiRegionView::set_frame_color()
1410 {
1411         if (frame) {
1412                 if (_selected && should_show_selection) {
1413                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
1414                 } else {
1415                         frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
1416                 }
1417         }
1418 }
1419
1420 void 
1421 MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
1422 {
1423         switch (mode) {
1424         case AllChannels:
1425         case FilterChannels:
1426                 _force_channel = -1;
1427                 break;
1428         case ForceChannel:
1429                 _force_channel = mask;
1430                 mask = 0xFFFF; // Show all notes as active (below)
1431         };
1432
1433         // Update notes for selection
1434         for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
1435                 (*i)->on_channel_selection_change(mask);
1436         }
1437
1438         _last_channel_selection = mask;
1439 }
1440