replace ::cast_dynamic() with relevant ActionManager::get_*_action() calls
[ardour.git] / gtk2_ardour / midi_streamview.cc
1 /*
2     Copyright (C) 2001-2007 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <cmath>
20 #include <utility>
21
22 #include <gtkmm.h>
23
24 #include <gtkmm2ext/gtk_ui.h>
25
26 #include "canvas/line_set.h"
27 #include "canvas/rectangle.h"
28
29 #include "ardour/midi_region.h"
30 #include "ardour/midi_source.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/operations.h"
33 #include "ardour/region_factory.h"
34 #include "ardour/session.h"
35 #include "ardour/smf_source.h"
36 #include "ardour/evoral_types_convert.h"
37
38 #include "gui_thread.h"
39 #include "midi_region_view.h"
40 #include "midi_streamview.h"
41 #include "midi_time_axis.h"
42 #include "midi_util.h"
43 #include "public_editor.h"
44 #include "region_selection.h"
45 #include "region_view.h"
46 #include "rgb_macros.h"
47 #include "selection.h"
48 #include "ui_config.h"
49 #include "utils.h"
50
51 #include "pbd/i18n.h"
52
53 using namespace std;
54 using namespace ARDOUR;
55 using namespace ARDOUR_UI_UTILS;
56 using namespace PBD;
57 using namespace Editing;
58
59 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
60         : StreamView (tv)
61         , note_range_adjustment(0.0f, 0.0f, 0.0f)
62         , _range_dirty(false)
63         , _range_sum_cache(-1.0)
64         , _lowest_note(60)
65         , _highest_note(71)
66         , _data_note_min(60)
67         , _data_note_max(71)
68         , _note_lines (0)
69         , _updates_suspended (false)
70 {
71         /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
72         midi_underlay_group = new ArdourCanvas::Container (_canvas_group);
73         midi_underlay_group->lower_to_bottom();
74
75         /* put the note lines in the timeaxisview's group, so it
76            can be put below ghost regions from MIDI underlays
77         */
78         _note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
79
80         _note_lines->Event.connect(
81                 sigc::bind(sigc::mem_fun(_trackview.editor(),
82                                          &PublicEditor::canvas_stream_view_event),
83                            _note_lines, &_trackview));
84
85         _note_lines->lower_to_bottom();
86
87         color_handler ();
88
89         UIConfiguration::instance().ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
90
91         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
92         note_range_adjustment.set_value(_lowest_note);
93
94         note_range_adjustment.signal_value_changed().connect(
95                 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
96 }
97
98 MidiStreamView::~MidiStreamView ()
99 {
100 }
101
102 RegionView*
103 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool recording)
104 {
105         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
106
107         if (region == 0) {
108                 return 0;
109         }
110
111         RegionView* region_view = NULL;
112         if (recording) {
113                 region_view = new MidiRegionView (
114                         _canvas_group, _trackview, region,
115                         _samples_per_pixel, region_color, recording,
116                         TimeAxisViewItem::Visibility(TimeAxisViewItem::ShowFrame));
117         } else {
118                 region_view = new MidiRegionView (_canvas_group, _trackview, region,
119                                                   _samples_per_pixel, region_color);
120         }
121
122         region_view->init (false);
123
124         return region_view;
125 }
126
127 RegionView*
128 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
129 {
130         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
131
132         if (!region) {
133                 return 0;
134         }
135
136         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
137                 if ((*i)->region() == r) {
138
139                         /* great. we already have a MidiRegionView for this Region. use it again. */
140
141                         (*i)->set_valid (true);
142
143                         display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
144
145                         return 0;
146                 }
147         }
148
149         MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
150         if (region_view == 0) {
151                 return 0;
152         }
153
154         region_views.push_front (region_view);
155
156         /* display events and find note range */
157         display_region (region_view, wait_for_data);
158
159         /* fit note range if we are importing */
160         if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
161                 set_note_range (ContentsRange);
162         }
163
164         /* catch regionview going away */
165         boost::weak_ptr<Region> wr (region); // make this explicit
166         region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
167
168         RegionViewAdded (region_view);
169
170         return region_view;
171 }
172
173 void
174 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
175 {
176         if (!region_view) {
177                 return;
178         }
179
180         region_view->enable_display (true);
181         region_view->set_height (child_height());
182
183         boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
184         if (!source) {
185                 error << _("attempt to display MIDI region with no source") << endmsg;
186                 return;
187         }
188
189         if (load_model) {
190                 Glib::Threads::Mutex::Lock lm(source->mutex());
191                 source->load_model(lm);
192         }
193
194         if (!source->model()) {
195                 error << _("attempt to display MIDI region with no model") << endmsg;
196                 return;
197         }
198
199         _range_dirty = update_data_note_range(
200                 source->model()->lowest_note(),
201                 source->model()->highest_note());
202
203         // Display region contents
204         region_view->display_model(source->model());
205 }
206
207
208 void
209 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
210 {
211         StreamView::display_track (tr);
212
213         draw_note_lines();
214
215         NoteRangeChanged();
216 }
217
218 void
219 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
220 {
221         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
222         if (mr) {
223                 Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
224                 mr->midi_source(0)->load_model(lm);
225                 _range_dirty = update_data_note_range(
226                         mr->model()->lowest_note(),
227                         mr->model()->highest_note());
228         }
229 }
230
231 bool
232 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
233 {
234         bool dirty = false;
235         if (min < _data_note_min) {
236                 _data_note_min = min;
237                 dirty = true;
238         }
239         if (max > _data_note_max) {
240                 _data_note_max = max;
241                 dirty = true;
242         }
243         return dirty;
244 }
245
246 void
247 MidiStreamView::set_layer_display (LayerDisplay d)
248 {
249
250 //revert this change for now.  Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to manage layered midi regions.
251 //      if (d != Overlaid) {
252 //              return;
253 //      }
254
255         StreamView::set_layer_display (d);
256 }
257
258 void
259 MidiStreamView::redisplay_track ()
260 {
261         if (!_trackview.is_midi_track()) {
262                 return;
263         }
264
265         list<RegionView*>::iterator i;
266
267         // Load models if necessary, and find note range of all our contents
268         _range_dirty = false;
269         _data_note_min = 127;
270         _data_note_max = 0;
271         _trackview.track()->playlist()->foreach_region(
272                 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
273
274         // No notes, use default range
275         if (!_range_dirty) {
276                 _data_note_min = 60;
277                 _data_note_max = 71;
278         }
279
280         // Flag region views as invalid and disable drawing
281         for (i = region_views.begin(); i != region_views.end(); ++i) {
282                 (*i)->set_valid(false);
283                 (*i)->enable_display(false);
284         }
285
286         // Add and display region views, and flag them as valid
287         _trackview.track()->playlist()->foreach_region(
288                 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
289
290         // Stack regions by layer, and remove invalid regions
291         layer_regions();
292
293         // Update note range (not regions which are correct) and draw note lines
294         apply_note_range(_lowest_note, _highest_note, false);
295 }
296
297
298 void
299 MidiStreamView::update_contents_height ()
300 {
301         StreamView::update_contents_height();
302
303         _note_lines->set_extent (ArdourCanvas::COORD_MAX);
304
305         apply_note_range (lowest_note(), highest_note(), true);
306 }
307
308 void
309 MidiStreamView::draw_note_lines()
310 {
311         if (!_note_lines || _updates_suspended) {
312                 return;
313         }
314
315         double y;
316         double prev_y = .5;
317         uint32_t color;
318
319         _note_lines->clear();
320
321         if (child_height() < 140 || note_height() < 3) {
322                 /* track is too small for note lines, or there are too many */
323                 return;
324         }
325
326         /* do this is order of highest ... lowest since that matches the
327          * coordinate system in which y=0 is at the top
328          */
329
330         for (int i = highest_note() + 1; i >= lowest_note(); --i) {
331
332                 y = floor(note_to_y (i)) + .5;
333
334                 /* this is the line actually corresponding to the division
335                  * between notes
336                  */
337
338                 if (i <= highest_note()) {
339                         _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
340                 }
341
342                 /* now add a thicker line/bar which covers the entire vertical
343                  * height of this note.
344                  */
345
346                 switch (i % 12) {
347                 case 1:
348                 case 3:
349                 case 6:
350                 case 8:
351                 case 10:
352                         color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
353                         break;
354                 default:
355                         color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
356                         break;
357                 }
358
359                 double h = y - prev_y;
360                 double mid = y + (h/2.0);
361
362                 if (mid >= 0 && h > 1.0) {
363                         _note_lines->add (mid, h, color);
364                 }
365
366                 prev_y = y;
367         }
368 }
369
370 void
371 MidiStreamView::set_note_range(VisibleNoteRange r)
372 {
373         if (r == FullRange) {
374                 _lowest_note = 0;
375                 _highest_note = 127;
376         } else {
377                 _lowest_note = _data_note_min;
378                 _highest_note = _data_note_max;
379         }
380
381         apply_note_range(_lowest_note, _highest_note, true);
382 }
383
384 void
385 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
386 {
387         _highest_note = highest;
388         _lowest_note = lowest;
389
390         int const max_note_height = 20;  // This should probably be based on text size...
391         int const range = _highest_note - _lowest_note;
392         int const pixels_per_note = floor (child_height () / range);
393
394         /* do not grow note height beyond 10 pixels */
395         if (pixels_per_note > max_note_height) {
396
397                 int const available_note_range = floor (child_height() / max_note_height);
398                 int additional_notes = available_note_range - range;
399
400                 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
401                 for (int i = 0; i < additional_notes; i++){
402
403                         if (i % 2 && _highest_note < 127){
404                                 _highest_note++;
405                         }
406                         else if (i % 2) {
407                                 _lowest_note--;
408                         }
409                         else if (_lowest_note > 0){
410                                 _lowest_note--;
411                         }
412                         else {
413                                 _highest_note++;
414                         }
415                 }
416         }
417
418         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
419         note_range_adjustment.set_value(_lowest_note);
420
421         draw_note_lines();
422
423         if (to_region_views) {
424                 apply_note_range_to_regions ();
425         }
426
427         NoteRangeChanged();
428 }
429
430 void
431 MidiStreamView::apply_note_range_to_regions ()
432 {
433         if (!_updates_suspended) {
434                 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
435                         ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
436                 }
437         }
438 }
439
440 void
441 MidiStreamView::update_note_range(uint8_t note_num)
442 {
443         _data_note_min = min(_data_note_min, note_num);
444         _data_note_max = max(_data_note_max, note_num);
445 }
446
447 void
448 MidiStreamView::setup_rec_box ()
449 {
450         // cerr << _trackview.name() << " streamview SRB\n";
451
452         if (!_trackview.session()->transport_stopped()) {
453
454                 if (!rec_active &&
455                     _trackview.session()->record_status() == Session::Recording &&
456                     _trackview.track()->rec_enable_control()->get_value()) {
457
458                         if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
459
460                                 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
461
462                                 MidiRegion::SourceList sources;
463
464                                 rec_data_ready_connections.drop_connections ();
465
466                                 sources.push_back (_trackview.midi_track()->write_source());
467
468                                 // handle multi
469
470                                 samplepos_t start = 0;
471                                 if (rec_regions.size() > 0) {
472                                         start = rec_regions.back().first->start()
473                                                 + _trackview.track()->get_captured_samples(rec_regions.size()-1);
474                                 }
475
476                                 if (!rec_regions.empty()) {
477                                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
478                                         mrv->end_write ();
479                                 }
480
481                                 PropertyList plist;
482
483                                 plist.add (ARDOUR::Properties::start, start);
484                                 plist.add (ARDOUR::Properties::length, 1);
485                                 /* Just above we're setting this nascent region's length to 1.  I think this
486                                    is so that the RegionView gets created with a non-zero width, as apparently
487                                    creating a RegionView with a zero width causes it never to be displayed
488                                    (there is a warning in TimeAxisViewItem::init about this).  However, we
489                                    must also set length_beats to something non-zero, otherwise the sample length
490                                    of 1 causes length_beats to be set to some small quantity << 1.  Then
491                                    when the position is set up below, this length_beats is used to recompute
492                                    length using BeatsSamplesConverter::to, which is slightly innacurate for small
493                                    beats values because it converts floating point beats to bars, beats and
494                                    integer ticks.  The upshot of which being that length gets set back to 0,
495                                    meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
496                                 */
497                                 plist.add (ARDOUR::Properties::length_beats, 1);
498                                 plist.add (ARDOUR::Properties::name, string());
499                                 plist.add (ARDOUR::Properties::layer, 0);
500
501                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
502                                                                       (RegionFactory::create (sources, plist, false)));
503                                 if (region) {
504                                         region->set_start (_trackview.track()->current_capture_start()
505                                                            - _trackview.track()->get_capture_start_sample (0));
506                                         region->set_position (_trackview.session()->transport_sample());
507
508                                         RegionView* rv = add_region_view_internal (region, false, true);
509                                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
510                                         mrv->begin_write ();
511
512                                         /* rec region will be destroyed in setup_rec_box */
513                                         rec_regions.push_back (make_pair (region, rv));
514
515                                         /* we add the region later */
516                                         setup_new_rec_layer_time (region);
517                                 } else {
518                                         error << _("failed to create MIDI region") << endmsg;
519                                 }
520                         }
521
522                         /* start a new rec box */
523
524                         create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
525
526                 } else if (rec_active &&
527                            (_trackview.session()->record_status() != Session::Recording ||
528                             !_trackview.track()->rec_enable_control()->get_value())) {
529                         screen_update_connection.disconnect();
530                         rec_active = false;
531                         rec_updating = false;
532                 }
533
534         } else {
535
536                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
537
538                 if (!rec_rects.empty() || !rec_regions.empty()) {
539
540                         /* disconnect rapid update */
541                         screen_update_connection.disconnect();
542                         rec_data_ready_connections.drop_connections ();
543                         rec_updating = false;
544                         rec_active = false;
545
546                         /* remove temp regions */
547
548                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
549                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
550
551                                 tmp = iter;
552                                 ++tmp;
553
554                                 (*iter).first->drop_references ();
555
556                                 iter = tmp;
557                         }
558
559                         rec_regions.clear();
560
561                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
562
563                         /* transport stopped, clear boxes */
564                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
565                                 RecBoxInfo &rect = (*iter);
566                                 delete rect.rectangle;
567                         }
568
569                         rec_rects.clear();
570
571                 }
572         }
573 }
574
575 void
576 MidiStreamView::color_handler ()
577 {
578         draw_note_lines ();
579
580         if (_trackview.is_midi_track()) {
581                 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
582         } else {
583                 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
584         }
585 }
586
587 void
588 MidiStreamView::note_range_adjustment_changed()
589 {
590         double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
591         int lowest = (int) floor(note_range_adjustment.get_value());
592         int highest;
593
594         if (sum == _range_sum_cache) {
595                 //cerr << "cached" << endl;
596                 highest = (int) floor(sum);
597         } else {
598                 //cerr << "recalc" << endl;
599                 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
600                 _range_sum_cache = sum;
601         }
602
603         if (lowest == _lowest_note && highest == _highest_note) {
604                 return;
605         }
606
607         //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
608         //cerr << "  val=" << v_zoom_adjustment.get_value() << " page=" << v_zoom_adjustment.get_page_size() << " sum=" << v_zoom_adjustment.get_value() + v_zoom_adjustment.get_page_size() << endl;
609
610         _lowest_note = lowest;
611         _highest_note = highest;
612         apply_note_range(lowest, highest, true);
613 }
614
615 void
616 MidiStreamView::update_rec_box ()
617 {
618         StreamView::update_rec_box ();
619
620         if (rec_regions.empty()) {
621                 return;
622         }
623
624         /* Update the region being recorded to reflect where we currently are */
625         boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
626         region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), 0);
627
628         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
629         mrv->extend_active_notes ();
630 }
631
632 uint8_t
633 MidiStreamView::y_to_note (double y) const
634 {
635         int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
636                 + lowest_note();
637
638         if (n < 0) {
639                 return 0;
640         } else if (n > 127) {
641                 return 127;
642         }
643
644         /* min due to rounding and/or off-by-one errors */
645         return min ((uint8_t) n, highest_note());
646 }
647
648 /** Suspend updates to the regions' note ranges and our
649  *  note lines until resume_updates() is called.
650  */
651 void
652 MidiStreamView::suspend_updates ()
653 {
654         _updates_suspended = true;
655 }
656
657 /** Resume updates to region note ranges and note lines,
658  *  and update them now.
659  */
660 void
661 MidiStreamView::resume_updates ()
662 {
663         _updates_suspended = false;
664
665         draw_note_lines ();
666         apply_note_range_to_regions ();
667
668         _canvas_group->redraw ();
669 }
670
671 struct RegionPositionSorter {
672         bool operator() (RegionView* a, RegionView* b) {
673                 return a->region()->position() < b->region()->position();
674         }
675 };
676
677 bool
678 MidiStreamView::paste (ARDOUR::samplepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
679 {
680         /* Paste into the first region which starts on or before pos.  Only called when
681            using an internal editing tool. */
682
683         if (region_views.empty()) {
684                 return false;
685         }
686
687         region_views.sort (RegionView::PositionOrder());
688
689         list<RegionView*>::const_iterator prev = region_views.begin ();
690
691         for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
692                 if ((*i)->region()->position() > pos) {
693                         break;
694                 }
695                 prev = i;
696         }
697
698         boost::shared_ptr<Region> r = (*prev)->region ();
699
700         /* If *prev doesn't cover pos, it's no good */
701         if (r->position() > pos || ((r->position() + r->length()) < pos)) {
702                 return false;
703         }
704
705         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
706         return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;
707 }