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