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