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