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