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