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