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