Use conf.fatal for fatal configuration errors
[ardour.git] / libs / waveview / wave_view.cc
1 /*
2  * Copyright (C) 2011-2013 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2017 Tim Mayberry <mojofunk@gmail.com>
4  * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <cmath>
22
23 #include <boost/scoped_array.hpp>
24
25 #include <cairomm/cairomm.h>
26
27 #include <glibmm/threads.h>
28 #include <gdkmm/general.h>
29
30 #include "pbd/base_ui.h"
31 #include "pbd/compose.h"
32 #include "pbd/convert.h"
33 #include "pbd/signals.h"
34 #include "pbd/stacktrace.h"
35
36 #include "ardour/types.h"
37 #include "ardour/dB.h"
38 #include "ardour/lmath.h"
39 #include "ardour/audioregion.h"
40 #include "ardour/audiosource.h"
41 #include "ardour/session.h"
42
43 #include "gtkmm2ext/colors.h"
44 #include "gtkmm2ext/gui_thread.h"
45 #include "gtkmm2ext/utils.h"
46
47 #include "canvas/canvas.h"
48 #include "canvas/debug.h"
49
50 #include "waveview/wave_view.h"
51 #include "waveview/wave_view_private.h"
52
53 #ifdef __APPLE__
54 #define Rect ArdourCanvas::Rect
55 #endif
56
57 using namespace std;
58 using namespace PBD;
59 using namespace ARDOUR;
60 using namespace Gtkmm2ext;
61 using namespace ArdourCanvas;
62 using namespace ArdourWaveView;
63
64 double WaveView::_global_gradient_depth = 0.6;
65 bool WaveView::_global_logscaled = false;
66 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
67 bool WaveView::_global_show_waveform_clipping = true;
68 double WaveView::_global_clip_level = 0.98853;
69
70 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
71 PBD::Signal0<void> WaveView::ClipLevelChanged;
72
73 /* NO_THREAD_WAVEVIEWS is defined by the top level wscript
74  * if --no-threaded-waveviws is provided at the configure step.
75  */
76
77 #ifndef NO_THREADED_WAVEVIEWS
78 #define ENABLE_THREADED_WAVEFORM_RENDERING
79 #endif
80
81 WaveView::WaveView (Canvas* c, boost::shared_ptr<ARDOUR::AudioRegion> region)
82         : Item (c)
83         , _region (region)
84         , _props (new WaveViewProperties (region))
85         , _shape_independent (false)
86         , _logscaled_independent (false)
87         , _gradient_depth_independent (false)
88         , _draw_image_in_gui_thread (false)
89         , _always_draw_image_in_gui_thread (false)
90 {
91         init ();
92 }
93
94 WaveView::WaveView (Item* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
95         : Item (parent)
96         , _region (region)
97         , _props (new WaveViewProperties (region))
98         , _shape_independent (false)
99         , _logscaled_independent (false)
100         , _gradient_depth_independent (false)
101         , _draw_image_in_gui_thread (false)
102         , _always_draw_image_in_gui_thread (false)
103 {
104         init ();
105 }
106
107 void
108 WaveView::init ()
109 {
110 #ifdef ENABLE_THREADED_WAVEFORM_RENDERING
111         WaveViewThreads::initialize ();
112 #endif
113
114         _props->fill_color = _fill_color;
115         _props->outline_color = _outline_color;
116
117         VisualPropertiesChanged.connect_same_thread (
118             invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
119         ClipLevelChanged.connect_same_thread (invalidation_connection,
120                                               boost::bind (&WaveView::handle_clip_level_change, this));
121 }
122
123 WaveView::~WaveView ()
124 {
125 #ifdef ENABLE_THREADED_WAVEFORM_RENDERING
126         WaveViewThreads::deinitialize ();
127 #endif
128
129         reset_cache_group ();
130 }
131
132 string
133 WaveView::debug_name() const
134 {
135         return _region->name () + string (":") + PBD::to_string (_props->channel + 1);
136 }
137
138 void
139 WaveView::set_always_get_image_in_thread (bool yn)
140 {
141         _always_draw_image_in_gui_thread = yn;
142 }
143
144 void
145 WaveView::handle_visual_property_change ()
146 {
147         bool changed = false;
148
149         if (!_shape_independent && (_props->shape != global_shape())) {
150                 _props->shape = global_shape();
151                 changed = true;
152         }
153
154         if (!_logscaled_independent && (_props->logscaled != global_logscaled())) {
155                 _props->logscaled = global_logscaled();
156                 changed = true;
157         }
158
159         if (!_gradient_depth_independent && (_props->gradient_depth != global_gradient_depth())) {
160                 _props->gradient_depth = global_gradient_depth();
161                 changed = true;
162         }
163
164         if (changed) {
165                 begin_visual_change ();
166                 end_visual_change ();
167         }
168 }
169
170 void
171 WaveView::handle_clip_level_change ()
172 {
173         begin_visual_change ();
174         end_visual_change ();
175 }
176
177 void
178 WaveView::set_fill_color (Color c)
179 {
180         if (c != _fill_color) {
181                 begin_visual_change ();
182                 Fill::set_fill_color (c);
183                 _props->fill_color = _fill_color; // ugh
184                 end_visual_change ();
185         }
186 }
187
188 void
189 WaveView::set_outline_color (Color c)
190 {
191         if (c != _outline_color) {
192                 begin_visual_change ();
193                 Outline::set_outline_color (c);
194                 _props->outline_color = c;
195                 end_visual_change ();
196         }
197 }
198
199 void
200 WaveView::set_samples_per_pixel (double samples_per_pixel)
201 {
202         if (_props->samples_per_pixel != samples_per_pixel) {
203                 begin_change ();
204
205                 _props->samples_per_pixel = samples_per_pixel;
206                 _bounding_box_dirty = true;
207
208                 end_change ();
209         }
210 }
211
212 static inline float
213 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
214 {
215         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
216 }
217
218 static inline float
219 alt_log_meter (float power)
220 {
221         return _log_meter (power, -192.0, 0.0, 8.0);
222 }
223
224 void
225 WaveView::set_clip_level (double dB)
226 {
227         const double clip_level = dB_to_coefficient (dB);
228         if (_global_clip_level != clip_level) {
229                 _global_clip_level = clip_level;
230                 ClipLevelChanged ();
231         }
232 }
233
234 boost::shared_ptr<WaveViewDrawRequest>
235 WaveView::create_draw_request (WaveViewProperties const& props) const
236 {
237         assert (props.is_valid());
238
239         boost::shared_ptr<WaveViewDrawRequest> request (new WaveViewDrawRequest);
240
241         request->image = boost::shared_ptr<WaveViewImage> (new WaveViewImage (_region, props));
242         return request;
243 }
244
245 void
246 WaveView::prepare_for_render (Rect const& area) const
247 {
248         if (draw_image_in_gui_thread()) {
249                 // Drawing image in GUI thread in WaveView::render
250                 return;
251         }
252
253         Rect draw_rect;
254         Rect self_rect;
255
256         // all in window coordinate space
257         if (!get_item_and_draw_rect_in_window_coords (area, self_rect, draw_rect)) {
258                 return;
259         }
260
261         double const image_start_pixel_offset = draw_rect.x0 - self_rect.x0;
262         double const image_end_pixel_offset = draw_rect.x1 - self_rect.x0;
263
264         WaveViewProperties required_props = *_props;
265
266         required_props.set_sample_positions_from_pixel_offsets (image_start_pixel_offset,
267                                                                 image_end_pixel_offset);
268
269         if (!required_props.is_valid ()) {
270                 return;
271         }
272
273         if (_image) {
274                 if (_image->props.is_equivalent (required_props)) {
275                         return;
276                 } else {
277                         // Image does not contain sample area required
278                 }
279         }
280
281         boost::shared_ptr<WaveViewDrawRequest> request = create_draw_request (required_props);
282
283         queue_draw_request (request);
284 }
285
286 bool
287 WaveView::get_item_and_draw_rect_in_window_coords (Rect const& canvas_rect, Rect& item_rect,
288                                                    Rect& draw_rect) const
289 {
290         /* a WaveView is intimately connected to an AudioRegion. It will
291          * display the waveform within the region, anywhere from the start of
292          * the region to its end.
293          *
294          * the area we've been asked to render may overlap with area covered
295          * by the region in any of the normal ways:
296          *
297          *  - it may begin and end within the area covered by the region
298          *  - it may start before and end after the area covered by region
299          *  - it may start before and end within the area covered by the region
300          *  - it may start within and end after the area covered by the region
301          *  - it may be precisely coincident with the area covered by region.
302          *
303          * So let's start by determining the area covered by the region, in
304          * window coordinates. It begins at zero (in item coordinates for this
305          * waveview, and extends to region_length() / _samples_per_pixel.
306          */
307
308         double const width = region_length() / _props->samples_per_pixel;
309         item_rect = item_to_window (Rect (0.0, 0.0, width, _props->height));
310
311         /* Now lets get the intersection with the area we've been asked to draw */
312
313         draw_rect = item_rect.intersection (canvas_rect);
314
315         if (!draw_rect) {
316                 // No intersection with drawing area
317                 return false;
318         }
319
320         /* draw_rect now defines the rectangle we need to update/render the waveview
321          * into, in window coordinate space.
322          *
323          * We round down in case we were asked to draw "between" pixels at the start
324          * and/or end.
325          */
326         draw_rect.x0 = floor (draw_rect.x0);
327         draw_rect.x1 = floor (draw_rect.x1);
328
329         return true;
330 }
331
332 void
333 WaveView::queue_draw_request (boost::shared_ptr<WaveViewDrawRequest> const& request) const
334 {
335         // Don't enqueue any requests without a thread to dequeue them.
336         assert (WaveViewThreads::enabled());
337
338         if (!request || !request->is_valid()) {
339                 return;
340         }
341
342         if (current_request) {
343                 current_request->cancel ();
344         }
345
346         boost::shared_ptr<WaveViewImage> cached_image =
347             get_cache_group ()->lookup_image (request->image->props);
348
349         if (cached_image) {
350                 // The image may not be finished at this point but that is fine, great in
351                 // fact as it means it should only need to be drawn once.
352                 request->image = cached_image;
353                 current_request = request;
354         } else {
355                 // now we can finally set an optimal image now that we are not using the
356                 // properties for comparisons.
357                 request->image->props.set_width_samples (optimal_image_width_samples ());
358
359                 current_request = request;
360
361                 // Add it to the cache so that other WaveViews can refer to the same image
362                 get_cache_group()->add_image (current_request->image);
363
364                 WaveViewThreads::enqueue_draw_request (current_request);
365         }
366 }
367
368 void
369 WaveView::compute_tips (ARDOUR::PeakData const& peak, WaveView::LineTips& tips,
370                         double const effective_height)
371 {
372         /* remember: canvas (and cairo) coordinate space puts the origin at the upper left.
373
374            So, a sample value of 1.0 (0dbFS) will be computed as:
375
376                  (1.0 - 1.0) * 0.5 * effective_height
377
378            which evaluates to 0, or the top of the image.
379
380            A sample value of -1.0 will be computed as
381
382                 (1.0 + 1.0) * 0.5 * effective height
383
384            which evaluates to effective height, or the bottom of the image.
385         */
386
387         const double pmax = (1.0 - peak.max) * 0.5 * effective_height;
388         const double pmin = (1.0 - peak.min) * 0.5 * effective_height;
389
390         /* remember that the bottom of the image (pmin) has larger y-coordinates
391            than the top (pmax).
392         */
393
394         double spread = (pmin - pmax) * 0.5;
395
396         /* find the nearest pixel to the nominal center. */
397         const double center = round (pmin - spread);
398
399         if (spread < 1.0) {
400                 /* minimum distance between line ends is 1 pixel, and we want it "centered" on a pixel,
401                    as per cairo single-pixel line issues.
402
403                    NOTE: the caller will not draw a line between these two points if the spread is
404                    less than 2 pixels. So only the tips.top value matters, which is where we will
405                    draw a single pixel as part of the outline.
406                  */
407                 tips.top = center;
408                 tips.bot = center + 1.0;
409         } else {
410                 /* round spread above and below center to an integer number of pixels */
411                 spread = round (spread);
412                 /* top and bottom are located equally either side of the center */
413                 tips.top = center - spread;
414                 tips.bot = center + spread;
415         }
416
417         tips.top = min (effective_height, max (0.0, tips.top));
418         tips.bot = min (effective_height, max (0.0, tips.bot));
419 }
420
421
422 Coord
423 WaveView::y_extent (double s, Shape const shape, double const height)
424 {
425         assert (shape == Rectified);
426         return floor ((1.0 - s) * height);
427 }
428
429 void
430 WaveView::draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* peaks, int n_peaks)
431 {
432         const double height = image->get_height();
433
434         Cairo::RefPtr<Cairo::ImageSurface> stripe = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
435
436         Cairo::RefPtr<Cairo::Context> stripe_context = Cairo::Context::create (stripe);
437         stripe_context->set_antialias (Cairo::ANTIALIAS_NONE);
438
439         uint32_t stripe_separation = 150;
440         double start = - floor (height / stripe_separation) * stripe_separation;
441         int stripe_x = 0;
442
443         while (start < n_peaks) {
444
445                 stripe_context->move_to (start, 0);
446                 stripe_x = start + height;
447                 stripe_context->line_to (stripe_x, height);
448                 start += stripe_separation;
449         }
450
451         stripe_context->set_source_rgba (1.0, 1.0, 1.0, 1.0);
452         stripe_context->set_line_cap (Cairo::LINE_CAP_SQUARE);
453         stripe_context->set_line_width(50);
454         stripe_context->stroke();
455
456         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
457
458         context->set_source_rgba (1.0, 1.0, 0.0, 0.3);
459         context->mask (stripe, 0, 0);
460         context->fill ();
461 }
462
463 struct ImageSet {
464         Cairo::RefPtr<Cairo::ImageSurface> wave;
465         Cairo::RefPtr<Cairo::ImageSurface> outline;
466         Cairo::RefPtr<Cairo::ImageSurface> clip;
467         Cairo::RefPtr<Cairo::ImageSurface> zero;
468
469         ImageSet() :
470                 wave (0), outline (0), clip (0), zero (0) {}
471 };
472
473 void
474 WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* peaks, int n_peaks,
475                       boost::shared_ptr<WaveViewDrawRequest> req)
476 {
477         const double height = image->get_height();
478
479         ImageSet images;
480
481         images.wave = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
482         images.outline = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
483         images.clip = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
484         images.zero = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
485
486         Cairo::RefPtr<Cairo::Context> wave_context = Cairo::Context::create (images.wave);
487         Cairo::RefPtr<Cairo::Context> outline_context = Cairo::Context::create (images.outline);
488         Cairo::RefPtr<Cairo::Context> clip_context = Cairo::Context::create (images.clip);
489         Cairo::RefPtr<Cairo::Context> zero_context = Cairo::Context::create (images.zero);
490         wave_context->set_antialias (Cairo::ANTIALIAS_NONE);
491         outline_context->set_antialias (Cairo::ANTIALIAS_NONE);
492         clip_context->set_antialias (Cairo::ANTIALIAS_NONE);
493         zero_context->set_antialias (Cairo::ANTIALIAS_NONE);
494
495         boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
496
497         /* Clip level nominally set to -0.9dBFS to account for inter-sample
498            interpolation possibly clipping (value may be too low).
499
500            We adjust by the region's own gain (but note: not by any gain
501            automation or its gain envelope) so that clip indicators are closer
502            to providing data about on-disk data. This multiplication is
503            needed because the data we get from AudioRegion::read_peaks()
504            has been scaled by scale_amplitude() already.
505         */
506
507         const double clip_level = _global_clip_level * req->image->props.amplitude;
508
509         const Shape shape = req->image->props.shape;
510         const bool logscaled = req->image->props.logscaled;
511
512         if (req->image->props.shape == WaveView::Rectified) {
513
514                 /* each peak is a line from the bottom of the waveview
515                  * to a point determined by max (peaks[i].max,
516                  * peaks[i].min)
517                  */
518
519                 if (logscaled) {
520                         for (int i = 0; i < n_peaks; ++i) {
521
522                                 tips[i].bot = height - 1.0;
523                                 const double p = alt_log_meter (fast_coefficient_to_dB (max (fabs (peaks[i].max), fabs (peaks[i].min))));
524                                 tips[i].top = y_extent (p, shape, height);
525                                 tips[i].spread = p * height;
526
527                                 if (peaks[i].max >= clip_level) {
528                                         tips[i].clip_max = true;
529                                 }
530
531                                 if (-(peaks[i].min) >= clip_level) {
532                                         tips[i].clip_min = true;
533                                 }
534                         }
535
536                 } else {
537                         for (int i = 0; i < n_peaks; ++i) {
538
539                                 tips[i].bot = height - 1.0;
540                                 const double p = max(fabs (peaks[i].max), fabs (peaks[i].min));
541                                 tips[i].top = y_extent (p, shape, height);
542                                 tips[i].spread = p * height;
543                                 if (p >= clip_level) {
544                                         tips[i].clip_max = true;
545                                 }
546                         }
547
548                 }
549
550         } else {
551
552                 if (logscaled) {
553                         for (int i = 0; i < n_peaks; ++i) {
554                                 PeakData p;
555                                 p.max = peaks[i].max;
556                                 p.min = peaks[i].min;
557
558                                 if (peaks[i].max >= clip_level) {
559                                         tips[i].clip_max = true;
560                                 }
561                                 if (-(peaks[i].min) >= clip_level) {
562                                         tips[i].clip_min = true;
563                                 }
564
565                                 if (p.max > 0.0) {
566                                         p.max = alt_log_meter (fast_coefficient_to_dB (p.max));
567                                 } else if (p.max < 0.0) {
568                                         p.max =-alt_log_meter (fast_coefficient_to_dB (-p.max));
569                                 } else {
570                                         p.max = 0.0;
571                                 }
572
573                                 if (p.min > 0.0) {
574                                         p.min = alt_log_meter (fast_coefficient_to_dB (p.min));
575                                 } else if (p.min < 0.0) {
576                                         p.min = -alt_log_meter (fast_coefficient_to_dB (-p.min));
577                                 } else {
578                                         p.min = 0.0;
579                                 }
580
581                                 compute_tips (p, tips[i], height);
582                                 tips[i].spread = tips[i].bot - tips[i].top;
583                         }
584
585                 } else {
586                         for (int i = 0; i < n_peaks; ++i) {
587                                 if (peaks[i].max >= clip_level) {
588                                         tips[i].clip_max = true;
589                                 }
590                                 if (-(peaks[i].min) >= clip_level) {
591                                         tips[i].clip_min = true;
592                                 }
593
594                                 compute_tips (peaks[i], tips[i], height);
595                                 tips[i].spread = tips[i].bot - tips[i].top;
596                         }
597
598                 }
599         }
600
601         if (req->stopped()) {
602                 return;
603         }
604
605         Color alpha_one = rgba_to_color (0, 0, 0, 1.0);
606
607         set_source_rgba (wave_context, alpha_one);
608         set_source_rgba (outline_context, alpha_one);
609         set_source_rgba (clip_context, alpha_one);
610         set_source_rgba (zero_context, alpha_one);
611
612         /* ensure single-pixel lines */
613
614         wave_context->set_line_width (1.0);
615         wave_context->translate (0.5, 0.5);
616
617         outline_context->set_line_width (1.0);
618         outline_context->translate (0.5, 0.5);
619
620         clip_context->set_line_width (1.0);
621         clip_context->translate (0.5, 0.5);
622
623         zero_context->set_line_width (1.0);
624         zero_context->translate (0.5, 0.5);
625
626         /* the height of the clip-indicator should be at most 7 pixels,
627          * or 5% of the height of the waveview item.
628          */
629
630         const double clip_height = min (7.0, ceil (height * 0.05));
631
632         /* There are 3 possible components to draw at each x-axis position: the
633            waveform "line", the zero line and an outline/clip indicator.  We
634            have to decide which of the 3 to draw at each position, pixel by
635            pixel. This makes the rendering less efficient but it is the only
636            way I can see to do this correctly.
637
638            To avoid constant source swapping and stroking, we draw the components separately
639            onto four alpha only image surfaces for use as a mask.
640
641            With only 1 pixel of spread between the top and bottom of the line,
642            we just draw the upper outline/clip indicator.
643
644            With 2 pixels of spread, we draw the upper and lower outline clip
645            indicators.
646
647            With 3 pixels of spread we draw the upper and lower outline/clip
648            indicators and at least 1 pixel of the waveform line.
649
650            With 5 pixels of spread, we draw all components.
651
652            We can do rectified as two separate passes because we have a much
653            easier decision regarding whether to draw the waveform line. We
654            always draw the clip/outline indicators.
655         */
656
657         if (shape == WaveView::Rectified) {
658
659                 for (int i = 0; i < n_peaks; ++i) {
660
661                         /* waveform line */
662
663                         if (tips[i].spread >= 1.0) {
664                                 wave_context->move_to (i, tips[i].top);
665                                 wave_context->line_to (i, tips[i].bot);
666                         }
667
668                         /* clip indicator */
669
670                         if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
671                                 clip_context->move_to (i, tips[i].top);
672                                 /* clip-indicating upper terminal line */
673                                 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + .5)));
674                         } else {
675                                 outline_context->move_to (i, tips[i].top);
676                                 /* normal upper terminal dot */
677                                 outline_context->rel_line_to (0, -1.0);
678                         }
679                 }
680
681                 wave_context->stroke ();
682                 clip_context->stroke ();
683                 outline_context->stroke ();
684
685         } else {
686                 const int height_zero = floor(height * .5);
687
688                 for (int i = 0; i < n_peaks; ++i) {
689
690                         /* waveform line */
691
692                         if (tips[i].spread >= 2.0) {
693                                 wave_context->move_to (i, tips[i].top);
694                                 wave_context->line_to (i, tips[i].bot);
695                         }
696
697                         /* draw square waves and other discontiguous points clearly */
698                         if (i > 0) {
699                                 if (tips[i-1].top + 2 < tips[i].top) {
700                                         wave_context->move_to (i-1, tips[i-1].top);
701                                         wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2);
702                                         wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2);
703                                         wave_context->line_to (i, tips[i].top);
704                                 } else if (tips[i-1].bot > tips[i].bot + 2) {
705                                         wave_context->move_to (i-1, tips[i-1].bot);
706                                         wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2);
707                                         wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2);
708                                         wave_context->line_to (i, tips[i].bot);
709                                 }
710                         }
711
712                         /* zero line, show only if there is enough spread
713                         or the waveform line does not cross zero line */
714                         bool const show_zero_line = req->image->props.show_zero;
715
716                         if (show_zero_line && ((tips[i].spread >= 5.0) || (tips[i].top > height_zero ) || (tips[i].bot < height_zero)) ) {
717                                 zero_context->move_to (i, height_zero);
718                                 zero_context->rel_line_to (1.0, 0);
719                         }
720
721                         if (tips[i].spread > 1.0) {
722                                 bool clipped = false;
723                                 /* outline/clip indicators */
724                                 if (_global_show_waveform_clipping && tips[i].clip_max) {
725                                         clip_context->move_to (i, tips[i].top);
726                                         /* clip-indicating upper terminal line */
727                                         clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + 0.5)));
728                                         clipped = true;
729                                 }
730
731                                 if (_global_show_waveform_clipping && tips[i].clip_min) {
732                                         clip_context->move_to (i, tips[i].bot);
733                                         /* clip-indicating lower terminal line */
734                                         clip_context->rel_line_to (0, - min (clip_height, ceil(tips[i].spread + 0.5)));
735                                         clipped = true;
736                                 }
737
738                                 if (!clipped && tips[i].spread > 2.0) {
739                                         /* only draw the outline if the spread
740                                            implies 3 or more pixels (so that we see 1
741                                            white pixel in the middle).
742                                         */
743                                         outline_context->move_to (i, tips[i].bot);
744                                         /* normal lower terminal dot; line moves up */
745                                         outline_context->rel_line_to (0, -1.0);
746
747                                         outline_context->move_to (i, tips[i].top);
748                                         /* normal upper terminal dot, line moves down */
749                                         outline_context->rel_line_to (0, 1.0);
750                                 }
751                         } else {
752                                 bool clipped = false;
753                                 /* outline/clip indicator */
754                                 if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
755                                         clip_context->move_to (i, tips[i].top);
756                                         /* clip-indicating upper / lower terminal line */
757                                         clip_context->rel_line_to (0, 1.0);
758                                         clipped = true;
759                                 }
760
761                                 if (!clipped) {
762                                         /* special case where only 1 pixel of
763                                          * the waveform line is drawn (and
764                                          * nothing else).
765                                          *
766                                          * we draw a 1px "line", pretending
767                                          * that the span is 1.0 (whether it is
768                                          * zero or 1.0)
769                                          */
770                                         wave_context->move_to (i, tips[i].top);
771                                         wave_context->rel_line_to (0, 1.0);
772                                 }
773                         }
774                 }
775
776                 wave_context->stroke ();
777                 outline_context->stroke ();
778                 clip_context->stroke ();
779                 zero_context->stroke ();
780         }
781
782         if (req->stopped()) {
783                 return;
784         }
785
786         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
787
788         /* Here we set a source colour and use the various components as a mask. */
789
790         const Color fill_color = req->image->props.fill_color;
791         const double gradient_depth = req->image->props.gradient_depth;
792
793         if (gradient_depth != 0.0) {
794
795                 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, height));
796
797                 double stops[3];
798
799                 double r, g, b, a;
800
801
802                 if (shape == Rectified) {
803                         stops[0] = 0.1;
804                         stops[1] = 0.3;
805                         stops[2] = 0.9;
806                 } else {
807                         stops[0] = 0.1;
808                         stops[1] = 0.5;
809                         stops[2] = 0.9;
810                 }
811
812                 color_to_rgba (fill_color, r, g, b, a);
813                 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
814                 /* generate a new color for the middle of the gradient */
815                 double h, s, v;
816                 color_to_hsv (fill_color, h, s, v);
817                 /* change v towards white */
818                 v *= 1.0 - gradient_depth;
819                 Color center = hsva_to_color (h, s, v, a);
820                 color_to_rgba (center, r, g, b, a);
821
822                 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
823                 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
824
825                 context->set_source (gradient);
826         } else {
827                 set_source_rgba (context, fill_color);
828         }
829
830         if (req->stopped()) {
831                 return;
832         }
833
834         context->mask (images.wave, 0, 0);
835         context->fill ();
836
837         set_source_rgba (context, req->image->props.outline_color);
838         context->mask (images.outline, 0, 0);
839         context->fill ();
840
841         set_source_rgba (context, req->image->props.clip_color);
842         context->mask (images.clip, 0, 0);
843         context->fill ();
844
845         set_source_rgba (context, req->image->props.zero_color);
846         context->mask (images.zero, 0, 0);
847         context->fill ();
848 }
849
850 samplecnt_t
851 WaveView::optimal_image_width_samples () const
852 {
853         /* Compute how wide the image should be in samples.
854          *
855          * The resulting image should be wider than the canvas width so that the
856          * image does not have to be redrawn each time the canvas offset changes, but
857          * drawing too much unnecessarily, for instance when zooming into the canvas
858          * the part of the image that is outside of the visible canvas area may never
859          * be displayed and will just increase apparent render time and reduce
860          * responsiveness in non-threaded rendering and cause "flashing" waveforms in
861          * threaded rendering mode.
862          *
863          * Another thing to consider is that if there are a number of waveforms on
864          * the canvas that are the width of the canvas then we don't want to have to
865          * draw the images for them all at once as it will cause a spike in render
866          * time, or in threaded rendering mode it will mean all the draw requests will
867          * the queued during the same sample/expose event. This issue can be
868          * alleviated by using an element of randomness in selecting the image width.
869          *
870          * If the value of samples per pixel is less than 1/10th of a second, use
871          * 1/10th of a second instead.
872          */
873
874         samplecnt_t canvas_width_samples = _canvas->visible_area().width() * _props->samples_per_pixel;
875         const samplecnt_t one_tenth_of_second = _region->session().sample_rate() / 10;
876
877         /* If zoomed in where a canvas item interects with the canvas area but
878          * stretches for many pages either side, to avoid having draw all images when
879          * the canvas scrolls by a page width the multiplier would have to be a
880          * randomized amount centered around 3 times the visible canvas width, but
881          * for other operations like zooming or even with a stationary playhead it is
882          * a lot of extra drawing that can affect performance.
883          *
884          * So without making things too complicated with different widths for
885          * different operations, try to use a width that is a balance and will work
886          * well for scrolling(non-page width) so all the images aren't redrawn at the
887          * same time but also faster for sequential zooming operations.
888          *
889          * Canvas items that don't intersect with the edges of the visible canvas
890          * will of course only draw images that are the pixel width of the item.
891          *
892          * It is a perhaps a coincidence that these values are centered roughly
893          * around the golden ratio but they did work well in my testing.
894          */
895         const double min_multiplier = 1.4;
896         const double max_multiplier = 1.8;
897
898         /**
899          * A combination of high resolution screens, high samplerates and high
900          * zoom levels(1 sample per pixel) can cause 1/10 of a second(in
901          * pixels) to exceed the cairo image size limit.
902          */
903         const double cairo_image_limit = 32767.0;
904         const double max_image_width = cairo_image_limit / max_multiplier;
905
906         samplecnt_t max_width_samples = floor (max_image_width / _props->samples_per_pixel);
907
908         const samplecnt_t one_tenth_of_second_limited = std::min (one_tenth_of_second, max_width_samples);
909
910         samplecnt_t new_sample_count = std::max (canvas_width_samples, one_tenth_of_second_limited);
911
912         const double multiplier = g_random_double_range (min_multiplier, max_multiplier);
913
914         return new_sample_count * multiplier;
915 }
916
917 void
918 WaveView::set_image (boost::shared_ptr<WaveViewImage> img) const
919 {
920         get_cache_group ()->add_image (img);
921         _image = img;
922 }
923
924 void
925 WaveView::process_draw_request (boost::shared_ptr<WaveViewDrawRequest> req)
926 {
927         boost::shared_ptr<const ARDOUR::AudioRegion> region = req->image->region.lock();
928
929         if (!region) {
930                 return;
931         }
932
933         if (req->stopped()) {
934                 return;
935         }
936
937         WaveViewProperties const& props = req->image->props;
938
939         const int n_peaks = props.get_width_pixels ();
940
941         assert (n_peaks > 0 && n_peaks < 32767);
942
943         boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
944
945         /* Note that Region::read_peaks() takes a start position based on an
946            offset into the Region's **SOURCE**, rather than an offset into
947            the Region itself.
948         */
949
950         samplecnt_t peaks_read =
951             region->read_peaks (peaks.get (), n_peaks, props.get_sample_start (),
952                                 props.get_length_samples (), props.channel, props.samples_per_pixel);
953
954         if (req->stopped()) {
955                 return;
956         }
957
958         Cairo::RefPtr<Cairo::ImageSurface> cairo_image =
959             Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, req->image->props.height);
960
961         // http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create
962         // This function always returns a valid pointer, but it will return a pointer to a "nil" surface..
963         // but there's some evidence that req->image can be NULL.
964         // http://tracker.ardour.org/view.php?id=6478
965         assert (cairo_image);
966
967         if (peaks_read > 0) {
968
969                 /* region amplitude will have been used to generate the
970                  * peak values already, but not the visual-only
971                  * amplitude_above_axis. So apply that here before
972                  * rendering.
973                  */
974
975                 const double amplitude_above_axis = props.amplitude_above_axis;
976
977                 if (amplitude_above_axis != 1.0) {
978                         for (samplecnt_t i = 0; i < n_peaks; ++i) {
979                                 peaks[i].max *= amplitude_above_axis;
980                                 peaks[i].min *= amplitude_above_axis;
981                         }
982                 }
983
984                 draw_image (cairo_image, peaks.get(), n_peaks, req);
985
986         } else {
987                 draw_absent_image (cairo_image, peaks.get(), n_peaks);
988         }
989
990         if (req->stopped ()) {
991                 return;
992         }
993
994         // Assign now that we are sure all drawing is complete as that is what
995         // determines whether a request was finished.
996         req->image->cairo_image = cairo_image;
997 }
998
999 bool
1000 WaveView::draw_image_in_gui_thread () const
1001 {
1002         return _draw_image_in_gui_thread || _always_draw_image_in_gui_thread || !rendered () ||
1003                !WaveViewThreads::enabled ();
1004 }
1005
1006 void
1007 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
1008 {
1009         assert (_props->samples_per_pixel != 0);
1010
1011         if (!_region) { // assert?
1012                 return;
1013         }
1014
1015         Rect draw;
1016         Rect self;
1017
1018         if (!get_item_and_draw_rect_in_window_coords (area, self, draw)) {
1019                 assert(true);
1020                 return;
1021         }
1022
1023         double const image_start_pixel_offset = draw.x0 - self.x0;
1024         double const image_end_pixel_offset = draw.x1 - self.x0;
1025
1026         if (image_start_pixel_offset == image_end_pixel_offset) {
1027                 // this may happen if zoomed very far out with a small region
1028                 return;
1029         }
1030
1031         WaveViewProperties required_props = *_props;
1032
1033         required_props.set_sample_positions_from_pixel_offsets (image_start_pixel_offset,
1034                                                                 image_end_pixel_offset);
1035
1036         assert (required_props.is_valid());
1037
1038         boost::shared_ptr<WaveViewImage> image_to_draw;
1039
1040         if (current_request) {
1041                 if (!current_request->image->props.is_equivalent (required_props)) {
1042                         // The WaveView properties may have been updated during recording between
1043                         // prepare_for_render and render calls and the new required props have
1044                         // different end sample value.
1045                         current_request->cancel ();
1046                         current_request.reset ();
1047                 } else if (current_request->finished ()) {
1048                         image_to_draw = current_request->image;
1049                         current_request.reset ();
1050                 }
1051         } else {
1052                 // No current Request
1053         }
1054
1055         if (!image_to_draw && _image) {
1056                 if (_image->props.is_equivalent (required_props)) {
1057                         // Image contains required properties
1058                         image_to_draw = _image;
1059                 } else {
1060                         // Image does not contain properties required
1061                 }
1062         }
1063
1064         if (!image_to_draw) {
1065                 image_to_draw = get_cache_group ()->lookup_image (required_props);
1066                 if (image_to_draw && !image_to_draw->finished ()) {
1067                         // Found equivalent but unfinished Image in cache
1068                         image_to_draw.reset ();
1069                 }
1070         }
1071
1072         if (!image_to_draw) {
1073                 // No existing image to draw
1074
1075                 boost::shared_ptr<WaveViewDrawRequest> const request = create_draw_request (required_props);
1076
1077                 if (draw_image_in_gui_thread ()) {
1078                         // now that we have to draw something, draw more than required.
1079                         request->image->props.set_width_samples (optimal_image_width_samples ());
1080
1081                         process_draw_request (request);
1082
1083                         image_to_draw = request->image;
1084
1085                 } else if (current_request) {
1086                         if (current_request->finished ()) {
1087                                 // There is a chance the request is now finished since checking above
1088                                 image_to_draw = current_request->image;
1089                                 current_request.reset ();
1090                         } else if (_canvas->get_microseconds_since_render_start () < 15000) {
1091                                 current_request->cancel ();
1092                                 current_request.reset ();
1093
1094                                 // Drawing image in GUI thread as we have time
1095
1096                                 // now that we have to draw something, draw more than required.
1097                                 request->image->props.set_width_samples (optimal_image_width_samples ());
1098
1099                                 process_draw_request (request);
1100
1101                                 image_to_draw = request->image;
1102                         } else {
1103                                 // Waiting for current request to finish
1104                                 redraw ();
1105                                 return;
1106                         }
1107                 } else {
1108                         // Defer the rendering to another thread or perhaps render pass if
1109                         // a thread cannot generate it in time.
1110                         queue_draw_request (request);
1111                         redraw ();
1112                         return;
1113                 }
1114         }
1115
1116         /* reset this so that future missing images can be generated in a worker thread. */
1117         _draw_image_in_gui_thread = false;
1118
1119         assert (image_to_draw);
1120
1121         /* compute the first pixel of the image that should be used when we
1122          * render the specified range.
1123          */
1124
1125         double image_origin_in_self_coordinates =
1126             (image_to_draw->props.get_sample_start () - _props->region_start) / _props->samples_per_pixel;
1127
1128         /* the image may only be a best-effort ... it may not span the entire
1129          * range requested, though it is guaranteed to cover the start. So
1130          * determine how many pixels we can actually draw.
1131          */
1132
1133         const double draw_start_pixel = draw.x0;
1134         const double draw_end_pixel = draw.x1;
1135
1136         double draw_width_pixels = draw_end_pixel - draw_start_pixel;
1137
1138         if (image_to_draw != _image) {
1139
1140                 /* the image is guaranteed to start at or before
1141                  * draw_start. But if it starts before draw_start, that reduces
1142                  * the maximum available width we can render with.
1143                  *
1144                  * so .. clamp the draw width to the smaller of what we need to
1145                  * draw or the available width of the image.
1146                  */
1147                 draw_width_pixels = min ((double)image_to_draw->cairo_image->get_width (), draw_width_pixels);
1148
1149                 set_image (image_to_draw);
1150         }
1151
1152         context->rectangle (draw_start_pixel, draw.y0, draw_width_pixels, draw.height());
1153
1154         /* round image origin position to an exact pixel in device space to
1155          * avoid blurring
1156          */
1157
1158         double x  = self.x0 + image_origin_in_self_coordinates;
1159         double y  = self.y0;
1160         context->user_to_device (x, y);
1161         x = floor (x);
1162         y = floor (y);
1163         context->device_to_user (x, y);
1164
1165         /* the coordinates specify where in "user coordinates" (i.e. what we
1166          * generally call "canvas coordinates" in this code) the image origin
1167          * will appear. So specifying (10,10) will put the upper left corner of
1168          * the image at (10,10) in user space.
1169          */
1170
1171         context->set_source (image_to_draw->cairo_image, x, y);
1172         context->fill ();
1173 }
1174
1175 void
1176 WaveView::compute_bounding_box () const
1177 {
1178         if (_region) {
1179                 _bounding_box = Rect (0.0, 0.0, region_length() / _props->samples_per_pixel, _props->height);
1180         } else {
1181                 _bounding_box = Rect ();
1182         }
1183
1184         _bounding_box_dirty = false;
1185 }
1186
1187 void
1188 WaveView::set_height (Distance height)
1189 {
1190         if (_props->height != height) {
1191                 begin_change ();
1192
1193                 _props->height = height;
1194                 _draw_image_in_gui_thread = true;
1195
1196                 _bounding_box_dirty = true;
1197                 end_change ();
1198         }
1199 }
1200
1201 void
1202 WaveView::set_channel (int channel)
1203 {
1204         if (_props->channel != channel) {
1205                 begin_change ();
1206                 _props->channel = channel;
1207                 reset_cache_group ();
1208                 _bounding_box_dirty = true;
1209                 end_change ();
1210         }
1211 }
1212
1213 void
1214 WaveView::set_logscaled (bool yn)
1215 {
1216         if (_props->logscaled != yn) {
1217                 begin_visual_change ();
1218                 _props->logscaled = yn;
1219                 end_visual_change ();
1220         }
1221 }
1222
1223 void
1224 WaveView::set_gradient_depth (double)
1225 {
1226         // TODO ??
1227 }
1228
1229 double
1230 WaveView::gradient_depth () const
1231 {
1232         return _props->gradient_depth;
1233 }
1234
1235 void
1236 WaveView::gain_changed ()
1237 {
1238         begin_visual_change ();
1239         _props->amplitude = _region->scale_amplitude ();
1240         _draw_image_in_gui_thread = true;
1241         end_visual_change ();
1242 }
1243
1244 void
1245 WaveView::set_zero_color (Color c)
1246 {
1247         if (_props->zero_color != c) {
1248                 begin_visual_change ();
1249                 _props->zero_color = c;
1250                 end_visual_change ();
1251         }
1252 }
1253
1254 void
1255 WaveView::set_clip_color (Color c)
1256 {
1257         if (_props->clip_color != c) {
1258                 begin_visual_change ();
1259                 _props->clip_color = c;
1260                 end_visual_change ();
1261         }
1262 }
1263
1264 void
1265 WaveView::set_show_zero_line (bool yn)
1266 {
1267         if (_props->show_zero != yn) {
1268                 begin_visual_change ();
1269                 _props->show_zero = yn;
1270                 end_visual_change ();
1271         }
1272 }
1273
1274 bool
1275 WaveView::show_zero_line () const
1276 {
1277         return _props->show_zero;
1278 }
1279
1280 void
1281 WaveView::set_shape (Shape s)
1282 {
1283         if (_props->shape != s) {
1284                 begin_visual_change ();
1285                 _props->shape = s;
1286                 end_visual_change ();
1287         }
1288 }
1289
1290 void
1291 WaveView::set_amplitude_above_axis (double a)
1292 {
1293         if (fabs (_props->amplitude_above_axis - a) > 0.01) {
1294                 begin_visual_change ();
1295                 _props->amplitude_above_axis = a;
1296                 _draw_image_in_gui_thread = true;
1297                 end_visual_change ();
1298         }
1299 }
1300
1301 double
1302 WaveView::amplitude_above_axis () const
1303 {
1304         return _props->amplitude_above_axis;
1305 }
1306
1307 void
1308 WaveView::set_global_shape (Shape s)
1309 {
1310         if (_global_shape != s) {
1311                 _global_shape = s;
1312                 WaveViewCache::get_instance()->clear_cache ();
1313                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1314         }
1315 }
1316
1317 void
1318 WaveView::set_global_logscaled (bool yn)
1319 {
1320         if (_global_logscaled != yn) {
1321                 _global_logscaled = yn;
1322                 WaveViewCache::get_instance()->clear_cache ();
1323                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1324         }
1325 }
1326
1327 samplecnt_t
1328 WaveView::region_length() const
1329 {
1330         return _region->length() - (_props->region_start - _region->start());
1331 }
1332
1333 samplepos_t
1334 WaveView::region_end() const
1335 {
1336         return _props->region_start + region_length();
1337 }
1338
1339 void
1340 WaveView::set_region_start (sampleoffset_t start)
1341 {
1342         if (!_region) {
1343                 return;
1344         }
1345
1346         if (_props->region_start == start) {
1347                 return;
1348         }
1349
1350         begin_change ();
1351         _props->region_start = start;
1352         _bounding_box_dirty = true;
1353         end_change ();
1354 }
1355
1356 void
1357 WaveView::region_resized ()
1358 {
1359         /* Called when the region start or end (thus length) has changed.
1360         */
1361
1362         if (!_region) {
1363                 return;
1364         }
1365
1366         begin_change ();
1367         _props->region_start = _region->start();
1368         _props->region_end = _region->start() + _region->length();
1369         _bounding_box_dirty = true;
1370         end_change ();
1371 }
1372
1373 void
1374 WaveView::set_global_gradient_depth (double depth)
1375 {
1376         if (_global_gradient_depth != depth) {
1377                 _global_gradient_depth = depth;
1378                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1379         }
1380 }
1381
1382 void
1383 WaveView::set_global_show_waveform_clipping (bool yn)
1384 {
1385         if (_global_show_waveform_clipping != yn) {
1386                 _global_show_waveform_clipping = yn;
1387                 ClipLevelChanged ();
1388         }
1389 }
1390
1391 void
1392 WaveView::set_start_shift (double pixels)
1393 {
1394         if (pixels < 0) {
1395                 return;
1396         }
1397
1398         begin_visual_change ();
1399         //_start_shift = pixels;
1400         end_visual_change ();
1401 }
1402
1403 void
1404 WaveView::set_image_cache_size (uint64_t sz)
1405 {
1406         WaveViewCache::get_instance()->set_image_cache_threshold (sz);
1407 }
1408
1409 boost::shared_ptr<WaveViewCacheGroup>
1410 WaveView::get_cache_group () const
1411 {
1412         if (_cache_group) {
1413                 return _cache_group;
1414         }
1415
1416         boost::shared_ptr<AudioSource> source = _region->audio_source (_props->channel);
1417         assert (source);
1418
1419         _cache_group = WaveViewCache::get_instance ()->get_cache_group (source);
1420
1421         return _cache_group;
1422 }
1423
1424 void
1425 WaveView::reset_cache_group ()
1426 {
1427         WaveViewCache::get_instance()->reset_cache_group (_cache_group);
1428 }