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