Fix 0006183 (waveview crash).
[ardour.git] / libs / canvas / wave_view.cc
1 /*
2     Copyright (C) 2011-2013 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <cmath>
22 #include <cairomm/cairomm.h>
23
24 #include "gtkmm2ext/utils.h"
25
26 #include "pbd/compose.h"
27 #include "pbd/signals.h"
28 #include "pbd/stacktrace.h"
29
30 #include "ardour/types.h"
31 #include "ardour/dB.h"
32 #include "ardour/audioregion.h"
33
34 #include "canvas/wave_view.h"
35 #include "canvas/utils.h"
36 #include "canvas/canvas.h"
37 #include "canvas/colors.h"
38
39 #include <gdkmm/general.h>
40
41 using namespace std;
42 using namespace ARDOUR;
43 using namespace ArdourCanvas;
44
45 #define CACHE_HIGH_WATER (2)
46
47 std::map <boost::shared_ptr<AudioSource>, std::vector<WaveView::CacheEntry> >  WaveView::_image_cache;
48 double WaveView::_global_gradient_depth = 0.6;
49 bool WaveView::_global_logscaled = false;
50 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
51 bool WaveView::_global_show_waveform_clipping = true;
52 double WaveView::_clip_level = 0.98853;
53
54 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
55 PBD::Signal0<void> WaveView::ClipLevelChanged;
56
57 WaveView::WaveView (Canvas* c, boost::shared_ptr<ARDOUR::AudioRegion> region)
58         : Item (c)
59         , _region (region)
60         , _channel (0)
61         , _samples_per_pixel (0)
62         , _height (64)
63         , _show_zero (false)
64         , _zero_color (0xff0000ff)
65         , _clip_color (0xff0000ff)
66         , _logscaled (_global_logscaled)
67         , _shape (_global_shape)
68         , _gradient_depth (_global_gradient_depth)
69         , _shape_independent (false)
70         , _logscaled_independent (false)
71         , _gradient_depth_independent (false)
72         , _amplitude_above_axis (1.0)
73         , _region_amplitude (_region->scale_amplitude ())
74         , _start_shift (0.0)
75         , _region_start (region->start())
76 {
77         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
78         ClipLevelChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_clip_level_change, this));
79 }
80
81 WaveView::WaveView (Item* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
82         : Item (parent)
83         , _region (region)
84         , _channel (0)
85         , _samples_per_pixel (0)
86         , _height (64)
87         , _show_zero (false)
88         , _zero_color (0xff0000ff)
89         , _clip_color (0xff0000ff)
90         , _logscaled (_global_logscaled)
91         , _shape (_global_shape)
92         , _gradient_depth (_global_gradient_depth)
93         , _shape_independent (false)
94         , _logscaled_independent (false)
95         , _gradient_depth_independent (false)
96         , _amplitude_above_axis (1.0)
97         , _region_amplitude (_region->scale_amplitude ())
98         , _region_start (region->start())
99 {
100         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
101         ClipLevelChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_clip_level_change, this));
102 }
103
104 WaveView::~WaveView ()
105 {
106         invalidate_image_cache ();
107 }
108
109 void
110 WaveView::handle_visual_property_change ()
111 {
112         bool changed = false;
113
114         if (!_shape_independent && (_shape != global_shape())) {
115                 _shape = global_shape();
116                 changed = true;
117         }
118
119         if (!_logscaled_independent && (_logscaled != global_logscaled())) {
120                 _logscaled = global_logscaled();
121                 changed = true;
122         }
123
124         if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
125                 _gradient_depth = global_gradient_depth();
126                 changed = true;
127         }
128
129         if (changed) {
130                 begin_visual_change ();
131                 invalidate_image_cache ();
132                 end_visual_change ();
133         }
134 }
135
136 void
137 WaveView::handle_clip_level_change ()
138 {
139         begin_visual_change ();
140         invalidate_image_cache ();
141         end_visual_change ();
142 }
143
144 void
145 WaveView::set_fill_color (Color c)
146 {
147         if (c != _fill_color) {
148                 begin_visual_change ();
149                 invalidate_image_cache ();
150                 Fill::set_fill_color (c);
151                 end_visual_change ();
152         }
153 }
154
155 void
156 WaveView::set_outline_color (Color c)
157 {
158         if (c != _outline_color) {
159                 begin_visual_change ();
160                 invalidate_image_cache ();
161                 Outline::set_outline_color (c);
162                 end_visual_change ();
163         }
164 }
165
166 void
167 WaveView::set_samples_per_pixel (double samples_per_pixel)
168 {
169         if (samples_per_pixel != _samples_per_pixel) {
170                 begin_change ();
171
172                 invalidate_image_cache ();
173                 _samples_per_pixel = samples_per_pixel;
174                 _bounding_box_dirty = true;
175
176                 end_change ();
177         }
178 }
179
180 static inline double
181 window_to_image (double wave_origin, double image_start)
182 {
183         return image_start - wave_origin;
184 }
185
186 static inline float
187 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
188 {
189         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
190 }
191
192 static inline float
193 alt_log_meter (float power)
194 {
195         return _log_meter (power, -192.0, 0.0, 8.0);
196 }
197
198 void
199 WaveView::set_clip_level (double dB)
200 {
201         const double clip_level = dB_to_coefficient (dB);
202         if (clip_level != _clip_level) {
203                 _clip_level = clip_level;
204                 ClipLevelChanged ();
205         }
206 }
207
208 void
209 WaveView::invalidate_image_cache ()
210 {
211         vector <uint32_t> deletion_list;
212         vector <CacheEntry> caches;
213
214         /* The source may have disappeared in the case of rec regions.*/
215         if (_region->n_channels() == 0) {
216                 std::map <boost::shared_ptr<ARDOUR::AudioSource>, std::vector <CacheEntry> >::iterator i;
217                 for (i = _image_cache.begin(); i != _image_cache.end(); ++i) {
218                         if (i->first.unique()) {
219                                 for (uint32_t n = 0; n < (*i).second.size (); ++n) {
220                                         (*i).second[n].image.clear ();
221                                 }
222                                 (*i).second.clear ();
223                                 _image_cache.erase(i->first);
224                         }
225                 }
226                 return;
227         }
228
229         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
230                 caches = _image_cache.find (_region->audio_source ())->second;
231         } else {
232                 return;
233         }
234
235         for (uint32_t i = 0; i < caches.size (); ++i) {
236
237                 if (_channel != caches[i].channel
238                     || _height != caches[i].height
239                     || _region_amplitude != caches[i].amplitude
240                     || _fill_color != caches[i].fill_color) {
241
242                         continue;
243                 }
244
245                 deletion_list.push_back (i);
246         }
247
248         while (deletion_list.size() > 0) {
249                 caches[deletion_list.back ()].image.clear ();
250                 caches.erase (caches.begin() + deletion_list.back());
251                 deletion_list.pop_back();
252         }
253
254         if (caches.size () == 0) {
255                 _image_cache.erase(_region->audio_source ());
256         } else {
257                 _image_cache[_region->audio_source ()] = caches;
258         }
259 }
260
261 void
262 WaveView::consolidate_image_cache () const
263 {
264         list <uint32_t> deletion_list;
265         vector <CacheEntry> caches;
266         uint32_t other_entries = 0;
267
268         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
269                 caches  = _image_cache.find (_region->audio_source ())->second;
270         }
271
272         for (uint32_t i = 0; i < caches.size (); ++i) {
273
274                 if (_channel != caches[i].channel
275                     || _height != caches[i].height
276                     || _region_amplitude != caches[i].amplitude
277                     || _fill_color != caches[i].fill_color) {
278
279                         other_entries++;
280                         continue;
281                 }
282
283                 framepos_t segment_start = caches[i].start;
284                 framepos_t segment_end = caches[i].end;
285
286                 for (uint32_t j = i; j < caches.size (); ++j) {
287
288                         if (i == j || _channel != caches[j].channel
289                             || _height != caches[i].height
290                             || _region_amplitude != caches[i].amplitude
291                             || _fill_color != caches[i].fill_color) {
292
293                                 continue;
294                         }
295
296                         if (caches[j].start >= segment_start && caches[j].end <= segment_end) {
297
298                                 deletion_list.push_back (j);
299                         }
300                 }
301         }
302
303         deletion_list.sort ();
304         deletion_list.unique ();
305
306         while (deletion_list.size() > 0) {
307                 caches[deletion_list.back ()].image.clear ();
308                 caches.erase (caches.begin() + deletion_list.back ());
309                 deletion_list.pop_back();
310         }
311
312         /* We don't care if this channel/height/amplitude has anything in the cache - just drop the Last Added entries
313            until we reach a size where there is a maximum of CACHE_HIGH_WATER + other entries.
314         */
315
316         while (caches.size() > CACHE_HIGH_WATER + other_entries) {
317                 caches.front ().image.clear ();
318                 caches.erase(caches.begin ());
319         }
320
321         if (caches.size () == 0) {
322                 _image_cache.erase (_region->audio_source ());
323         } else {
324                 _image_cache[_region->audio_source ()] = caches;
325         }
326 }
327
328 Coord
329 WaveView::y_extent (double s, bool /*round_to_lower_edge*/) const
330 {
331         /* it is important that this returns an integral value, so that we
332          * can ensure correct single pixel behaviour.
333          *
334          * we need (_height - max(wave_line_width))
335          * wave_line_width == 1 IFF top==bottom (1 sample per pixel or flat line)
336          * wave_line_width == 2 otherwise
337          * then round away from the zero line, towards peak
338          */
339         if (_shape == Rectified) {
340                 // we only ever have 1 point and align to the bottom (not center)
341                 return floor ((1.0 - s) * (_height - 2.0));
342         } else {
343                 /* currently canvas rectangle is off-by-one and we
344                  * cannot draw a pixel at 0 (-.5 .. +.5) without it being
345                  * clipped. A value 1.0 (ideally one point at y=0) ends
346                  * up a pixel down. and a value of -1.0 (ideally y = _height-1)
347                  * currently is on the bottom separator line :(
348                  * So to make the complete waveform appear centered in
349                  * a region, we translate by +.5 (instead of -.5)
350                  * and waste two pixel of height: -4 (instad of -2)
351                  *
352                  * This needs fixing in canvas/rectangle the intersect
353                  * functions and probably a couple of other places as well...
354                  */
355                 Coord pos;
356                 if (s < 0) {
357                         pos = ceil  ((1.0 - s) * .5 * (_height - 4.0));
358                 } else {
359                         pos = floor ((1.0 - s) * .5 * (_height - 4.0));
360                 }
361                 return min (_height - 4.0, (max (0.0, pos)));
362         }
363 }
364
365 void
366 WaveView::draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peaks, int n_peaks) const
367 {
368         Cairo::RefPtr<Cairo::ImageSurface> stripe = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
369
370         Cairo::RefPtr<Cairo::Context> stripe_context = Cairo::Context::create (stripe);
371         stripe_context->set_antialias (Cairo::ANTIALIAS_NONE);
372
373         uint32_t stripe_separation = 150;
374         double start = - floor (_height / stripe_separation) * stripe_separation;
375         int stripe_x = 0;
376
377         while (start < n_peaks) {
378
379                 stripe_context->move_to (start, 0);
380                 stripe_x = start + _height;
381                 stripe_context->line_to (stripe_x, _height);
382                 start += stripe_separation;
383         }
384
385         stripe_context->set_source_rgba (1.0, 1.0, 1.0, 1.0);
386         stripe_context->set_line_cap (Cairo::LINE_CAP_SQUARE);
387         stripe_context->set_line_width(50);
388         stripe_context->stroke();
389
390         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
391
392         context->set_source_rgba (1.0, 1.0, 0.0, 0.3);
393         context->mask (stripe, 0, 0);
394         context->fill ();
395 }
396
397 struct LineTips {
398         double top;
399         double bot;
400         double spread;
401         bool clip_max;
402         bool clip_min;
403
404         LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
405 };
406
407 struct ImageSet {
408         Cairo::RefPtr<Cairo::ImageSurface> wave;
409         Cairo::RefPtr<Cairo::ImageSurface> outline;
410         Cairo::RefPtr<Cairo::ImageSurface> clip;
411         Cairo::RefPtr<Cairo::ImageSurface> zero;
412
413         ImageSet() :
414                 wave (0), outline (0), clip (0), zero (0) {}
415 };
416
417 void
418 WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peaks, int n_peaks) const
419 {
420
421         ImageSet images;
422
423         images.wave = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
424         images.outline = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
425         images.clip = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
426         images.zero = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
427
428         Cairo::RefPtr<Cairo::Context> wave_context = Cairo::Context::create (images.wave);
429         Cairo::RefPtr<Cairo::Context> outline_context = Cairo::Context::create (images.outline);
430         Cairo::RefPtr<Cairo::Context> clip_context = Cairo::Context::create (images.clip);
431         Cairo::RefPtr<Cairo::Context> zero_context = Cairo::Context::create (images.zero);
432         wave_context->set_antialias (Cairo::ANTIALIAS_NONE);
433         outline_context->set_antialias (Cairo::ANTIALIAS_NONE);
434         clip_context->set_antialias (Cairo::ANTIALIAS_NONE);
435         zero_context->set_antialias (Cairo::ANTIALIAS_NONE);
436
437         boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
438
439         /* Clip level nominally set to -0.9dBFS to account for inter-sample
440            interpolation possibly clipping (value may be too low).
441
442            We adjust by the region's own gain (but note: not by any gain
443            automation or its gain envelope) so that clip indicators are closer
444            to providing data about on-disk data. This multiplication is
445            needed because the data we get from AudioRegion::read_peaks()
446            has been scaled by scale_amplitude() already.
447         */
448
449         const double clip_level = _clip_level * _region_amplitude;
450
451         if (_shape == WaveView::Rectified) {
452
453                 /* each peak is a line from the bottom of the waveview
454                  * to a point determined by max (_peaks[i].max,
455                  * _peaks[i].min)
456                  */
457
458                 if (_logscaled) {
459                         for (int i = 0; i < n_peaks; ++i) {
460
461                                 tips[i].bot = height() - 1.0;
462                                 const double p = alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
463                                 tips[i].top = y_extent (p, false);
464                                 tips[i].spread = p * (_height - 1.0);
465
466                                 if (_peaks[i].max >= clip_level) {
467                                         tips[i].clip_max = true;
468                                 }
469
470                                 if (-(_peaks[i].min) >= clip_level) {
471                                         tips[i].clip_min = true;
472                                 }
473                         }
474
475                 } else {
476                         for (int i = 0; i < n_peaks; ++i) {
477
478                                 tips[i].bot = height() - 1.0;
479                                 const double p = max(fabs (_peaks[i].max), fabs (_peaks[i].min));
480                                 tips[i].top = y_extent (p, false);
481                                 tips[i].spread = p * (_height - 2.0);
482                                 if (p >= clip_level) {
483                                         tips[i].clip_max = true;
484                                 }
485                         }
486
487                 }
488
489         } else {
490
491                 if (_logscaled) {
492                         for (int i = 0; i < n_peaks; ++i) {
493                                 double top = _peaks[i].max;
494                                 double bot = _peaks[i].min;
495
496                                 if (_peaks[i].max >= clip_level) {
497                                                 tips[i].clip_max = true;
498                                 }
499                                 if (-(_peaks[i].min) >= clip_level) {
500                                         tips[i].clip_min = true;
501                                 }
502
503                                 if (top > 0.0) {
504                                         top = alt_log_meter (fast_coefficient_to_dB (top));
505                                 } else if (top < 0.0) {
506                                         top =-alt_log_meter (fast_coefficient_to_dB (-top));
507                                 } else {
508                                         top = 0.0;
509                                 }
510
511                                 if (bot > 0.0) {
512                                         bot = alt_log_meter (fast_coefficient_to_dB (bot));
513                                 } else if (bot < 0.0) {
514                                         bot = -alt_log_meter (fast_coefficient_to_dB (-bot));
515                                 } else {
516                                         bot = 0.0;
517                                 }
518
519                                 tips[i].top = y_extent (top, false);
520                                 tips[i].bot = y_extent (bot, true);
521                                 tips[i].spread = tips[i].bot - tips[i].top;
522                         }
523
524                 } else {
525                         for (int i = 0; i < n_peaks; ++i) {
526                                 if (_peaks[i].max >= clip_level) {
527                                         tips[i].clip_max = true;
528                                 }
529                                 if (-(_peaks[i].min) >= clip_level) {
530                                         tips[i].clip_min = true;
531                                 }
532
533                                 tips[i].top = y_extent (_peaks[i].max, false);
534                                 tips[i].bot = y_extent (_peaks[i].min, true);
535                                 tips[i].spread = tips[i].bot - tips[i].top;
536                         }
537
538                 }
539         }
540         Color alpha_one = rgba_to_color (0, 0, 0, 1.0);
541
542         set_source_rgba (wave_context, alpha_one);
543         set_source_rgba (outline_context, alpha_one);
544         set_source_rgba (clip_context, alpha_one);
545         set_source_rgba (zero_context, alpha_one);
546
547         /* ensure single-pixel lines */
548
549         wave_context->set_line_width (1.0);
550         wave_context->translate (0.5, +0.5);
551
552         outline_context->set_line_width (1.0);
553         outline_context->translate (0.5, +0.5);
554
555         clip_context->set_line_width (1.0);
556         clip_context->translate (0.5, +0.5);
557
558         zero_context->set_line_width (1.0);
559         zero_context->translate (0.5, +0.5);
560
561         /* the height of the clip-indicator should be at most 7 pixels,
562          * or 5% of the height of the waveview item.
563          */
564
565         const double clip_height = min (7.0, ceil (_height * 0.05));
566         bool draw_outline_as_wave = false;
567
568         /* There are 3 possible components to draw at each x-axis position: the
569            waveform "line", the zero line and an outline/clip indicator.  We
570            have to decide which of the 3 to draw at each position, pixel by
571            pixel. This makes the rendering less efficient but it is the only
572            way I can see to do this correctly.
573
574            To avoid constant source swapping and stroking, we draw the components separately
575            onto four alpha only image surfaces for use as a mask.
576
577            With only 1 pixel of spread between the top and bottom of the line,
578            we just draw the upper outline/clip indicator.
579
580            With 2 pixels of spread, we draw the upper and lower outline clip
581            indicators.
582
583            With 3 pixels of spread we draw the upper and lower outline/clip
584            indicators and at least 1 pixel of the waveform line.
585
586            With 5 pixels of spread, we draw all components.
587
588            We can do rectified as two separate passes because we have a much
589            easier decision regarding whether to draw the waveform line. We
590            always draw the clip/outline indicators.
591         */
592
593         if (_shape == WaveView::Rectified) {
594
595                 for (int i = 0; i < n_peaks; ++i) {
596
597                         /* waveform line */
598
599                         if (tips[i].spread >= 1.0) {
600                                 wave_context->move_to (i, tips[i].top);
601                                 wave_context->line_to (i, tips[i].bot);
602                         }
603
604                         if (_global_show_waveform_clipping && (tips[i].clip_max)) {
605                                 clip_context->move_to (i, tips[i].top);
606                                 /* clip-indicating upper terminal line */
607                                 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + .5)));
608                         } else {
609                                 outline_context->move_to (i, tips[i].top);
610                                 /* normal upper terminal dot */
611                                 outline_context->close_path ();
612                         }
613                 }
614
615                 wave_context->stroke ();
616                 clip_context->stroke ();
617                 outline_context->stroke ();
618
619         } else {
620                 const double height_2 = (_height - 4.0) * .5;
621
622                 for (int i = 0; i < n_peaks; ++i) {
623
624                         /* waveform line */
625
626                         if (tips[i].spread >= 2.0) {
627                                 wave_context->move_to (i, tips[i].top);
628                                 wave_context->line_to (i, tips[i].bot);
629                         }
630                         /* draw square waves and other discontiguous points clearly */
631                         if (i > 0) {
632                                 if (tips[i-1].top + 2 < tips[i].top) {
633                                         wave_context->move_to (i-1, tips[i-1].top);
634                                         wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2);
635                                         wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2);
636                                         wave_context->line_to (i, tips[i].top);
637                                 } else if (tips[i-1].bot > tips[i].bot + 2) {
638                                         wave_context->move_to (i-1, tips[i-1].bot);
639                                         wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2);
640                                         wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2);
641                                         wave_context->line_to (i, tips[i].bot);
642                                 }
643                         }
644
645                         /* zero line */
646
647                         if (tips[i].spread >= 5.0 && show_zero_line()) {
648                                 zero_context->move_to (i, floor(height_2));
649                                 zero_context->rel_line_to (1.0, 0);
650                         }
651
652                         if (tips[i].spread > 1.0) {
653                                 draw_outline_as_wave = false;
654                                 /* lower outline/clip indicator */
655                                 if (_global_show_waveform_clipping && tips[i].clip_min) {
656                                         clip_context->move_to (i, tips[i].bot);
657                                         /* clip-indicating lower terminal line */
658                                         const double sign = tips[i].bot > height_2 ? -1 : 1;
659                                         clip_context->rel_line_to (0, sign * min (clip_height, ceil (tips[i].spread + .5)));
660                                 } else {
661                                         outline_context->move_to (i, tips[i].bot + 0.5);
662                                         /* normal lower terminal dot */
663                                         outline_context->rel_line_to (0, -0.5);
664                                 }
665                         } else {
666                                 draw_outline_as_wave = true;
667                                 if (tips[i].clip_min) {
668                                         // make sure we draw the clip
669                                         tips[i].clip_max = true;
670                                 }
671                         }
672
673                         /* upper outline/clip indicator */
674                         if (_global_show_waveform_clipping && tips[i].clip_max) {
675                                 clip_context->move_to (i, tips[i].top);
676                                 /* clip-indicating upper terminal line */
677                                 const double sign = tips[i].top > height_2 ? -1 : 1;
678                                 clip_context->rel_line_to (0, sign * min(clip_height, ceil(tips[i].spread + .5)));
679                         } else {
680                                 if (draw_outline_as_wave) {
681                                         wave_context->move_to (i, tips[i].top + 0.5);
682                                         /* special case where outline only is drawn.
683                                            is this correct? too short by 0.5?
684                                         */
685                                         wave_context->rel_line_to (0, -0.5);
686                                 } else {
687                                         outline_context->move_to (i, tips[i].top + 0.5);
688                                         /* normal upper terminal dot */
689                                         outline_context->rel_line_to (0, -0.5);
690                                 }
691                         }
692                 }
693
694                 wave_context->stroke ();
695                 outline_context->stroke ();
696                 clip_context->stroke ();
697                 zero_context->stroke ();
698         }
699
700         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
701
702         /* Here we set a source colour and use the various components as a mask. */
703
704         if (gradient_depth() != 0.0) {
705
706                 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _height));
707
708                 double stops[3];
709
710                 double r, g, b, a;
711
712                 if (_shape == Rectified) {
713                         stops[0] = 0.1;
714                         stops[1] = 0.3;
715                         stops[2] = 0.9;
716                 } else {
717                         stops[0] = 0.1;
718                         stops[1] = 0.5;
719                         stops[2] = 0.9;
720                 }
721
722                 color_to_rgba (_fill_color, r, g, b, a);
723                 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
724                 /* generate a new color for the middle of the gradient */
725                 double h, s, v;
726                 color_to_hsv (_fill_color, h, s, v);
727                 /* change v towards white */
728                 v *= 1.0 - gradient_depth();
729                 Color center = hsva_to_color (h, s, v, a);
730                 color_to_rgba (center, r, g, b, a);
731
732                 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
733                 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
734
735                 context->set_source (gradient);
736         } else {
737                 set_source_rgba (context, _fill_color);
738         }
739
740         context->mask (images.wave, 0, 0);
741         context->fill ();
742
743         set_source_rgba (context, _outline_color);
744         context->mask (images.outline, 0, 0);
745         context->fill ();
746
747         set_source_rgba (context, _clip_color);
748         context->mask (images.clip, 0, 0);
749         context->fill ();
750
751         set_source_rgba (context, _zero_color);
752         context->mask (images.zero, 0, 0);
753         context->fill ();
754
755 }
756
757 void
758 WaveView::get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const
759 {
760         vector <CacheEntry> caches;
761
762         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
763
764                 caches = _image_cache.find (_region->audio_source ())->second;
765         }
766
767         /* Find a suitable ImageSurface.
768         */
769         for (uint32_t i = 0; i < caches.size (); ++i) {
770
771                 if (_channel != caches[i].channel
772                     || _height != caches[i].height
773                     || _region_amplitude != caches[i].amplitude
774                     || _fill_color != caches[i].fill_color) {
775
776                         continue;
777                 }
778
779                 framepos_t segment_start = caches[i].start;
780                 framepos_t segment_end = caches[i].end;
781
782                 if (end <= segment_end && start >= segment_start) {
783                         image_offset = (segment_start - _region_start) / _samples_per_pixel;
784                         image = caches[i].image;
785
786                         return;
787                 }
788         }
789
790         consolidate_image_cache ();
791
792         /* sample position is canonical here, and we want to generate
793          * an image that spans about twice the canvas width
794          */
795
796         const framepos_t center = start + ((end - start) / 2);
797         const framecnt_t canvas_samples = _canvas->visible_area().width() * _samples_per_pixel; /* one canvas width */
798
799         /* we can request data from anywhere in the Source, between 0 and its length
800          */
801
802         framepos_t sample_start = max ((framepos_t) 0, (center - canvas_samples));
803         framepos_t sample_end = min (center + canvas_samples, _region->source_length (0));
804
805         const int n_peaks = llrintf ((sample_end - sample_start)/ (double) _samples_per_pixel);
806
807         boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
808
809         framecnt_t peaks_read;
810         peaks_read = _region->read_peaks (peaks.get(), n_peaks,
811                              sample_start, sample_end - sample_start,
812                              _channel,
813                              _samples_per_pixel);
814
815         image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, _height);
816
817         if (peaks_read > 0) {
818                 draw_image (image, peaks.get(), n_peaks);
819         } else {
820                 draw_absent_image (image, peaks.get(), n_peaks);
821         }
822
823         _image_cache[_region->audio_source ()].push_back (CacheEntry (_channel, _height, _region_amplitude, _fill_color, sample_start,  sample_end, image));
824
825         image_offset = (sample_start - _region->start()) / _samples_per_pixel;
826
827         //cerr << "_image_cache size is : " << _image_cache.size() << " entries for this audiosource : " << _image_cache.find (_region->audio_source ())->second.size() << endl;
828
829         return;
830 }
831
832 void
833 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
834 {
835         assert (_samples_per_pixel != 0);
836
837         if (!_region) {
838                 return;
839         }
840
841         Rect self = item_to_window (Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height));
842         boost::optional<Rect> d = self.intersection (area);
843
844         if (!d) {
845                 return;
846         }
847
848         Rect draw = d.get();
849
850         /* window coordinates - pixels where x=0 is the left edge of the canvas
851          * window. We round down in case we were asked to
852          * draw "between" pixels at the start and/or end.
853          */
854         
855         double draw_start = floor (draw.x0);
856         const double draw_end = floor (draw.x1);
857
858         // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
859
860         /* image coordnates: pixels where x=0 is the start of this waveview,
861          * wherever it may be positioned. thus image_start=N means "an image
862          * that beings N pixels after the start of region that this waveview is
863          * representing.
864          */
865
866         const framepos_t image_start = window_to_image (self.x0, draw_start);
867         const framepos_t image_end = window_to_image (self.x0, draw_end);
868
869         // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
870
871         /* sample coordinates - note, these are not subject to rounding error */
872         framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
873         framepos_t sample_end   = _region_start + (image_end * _samples_per_pixel);
874         
875         // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
876
877         Cairo::RefPtr<Cairo::ImageSurface> image;
878         double image_offset = 0;
879
880         get_image (image, sample_start, sample_end, image_offset);
881
882         // cerr << "Offset into image to place at zero: " << image_offset << endl;
883
884         if (_start_shift && (sample_start == _region_start) && (self.x0 == draw.x0)) {
885                 /* we are going to draw the first pixel for this region, but 
886                    we may not want this to overlap a border around the
887                    waveform. If so, _start_shift will be set.
888                 */
889                 //cerr << name.substr (23) << " ss = " << sample_start << " rs = " << _region_start << " sf = " << _start_shift << " ds = " << draw_start << " self = " << self << " draw = " << draw << endl;
890                 //draw_start += _start_shift;
891                 //image_offset += _start_shift;
892         }
893         
894         context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
895
896         /* round image origin position to an exact pixel in device space to
897          * avoid blurring
898          */
899
900         double x  = self.x0 + image_offset;
901         double y  = self.y0;
902         context->user_to_device (x, y);
903         x = round (x);
904         y = round (y);
905         context->device_to_user (x, y);
906
907         context->set_source (image, x, y);
908         context->fill ();
909
910 }
911
912 void
913 WaveView::compute_bounding_box () const
914 {
915         if (_region) {
916                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
917         } else {
918                 _bounding_box = boost::optional<Rect> ();
919         }
920
921         _bounding_box_dirty = false;
922 }
923
924 void
925 WaveView::set_height (Distance height)
926 {
927         if (height != _height) {
928                 begin_change ();
929
930                 invalidate_image_cache ();
931                 _height = height;
932
933                 _bounding_box_dirty = true;
934                 end_change ();
935         }
936 }
937
938 void
939 WaveView::set_channel (int channel)
940 {
941         if (channel != _channel) {
942                 begin_change ();
943
944                 invalidate_image_cache ();
945                 _channel = channel;
946
947                 _bounding_box_dirty = true;
948                 end_change ();
949         }
950 }
951
952 void
953 WaveView::set_logscaled (bool yn)
954 {
955         if (_logscaled != yn) {
956                 begin_visual_change ();
957                 invalidate_image_cache ();
958                 _logscaled = yn;
959                 end_visual_change ();
960         }
961 }
962
963 void
964 WaveView::gain_changed ()
965 {
966         begin_visual_change ();
967         invalidate_image_cache ();
968         _region_amplitude = _region->scale_amplitude ();
969         end_visual_change ();
970 }
971
972 void
973 WaveView::set_zero_color (Color c)
974 {
975         if (_zero_color != c) {
976                 begin_visual_change ();
977                 invalidate_image_cache ();
978                 _zero_color = c;
979                 end_visual_change ();
980         }
981 }
982
983 void
984 WaveView::set_clip_color (Color c)
985 {
986         if (_clip_color != c) {
987                 begin_visual_change ();
988                 invalidate_image_cache ();
989                 _clip_color = c;
990                 end_visual_change ();
991         }
992 }
993
994 void
995 WaveView::set_show_zero_line (bool yn)
996 {
997         if (_show_zero != yn) {
998                 begin_visual_change ();
999                 invalidate_image_cache ();
1000                 _show_zero = yn;
1001                 end_visual_change ();
1002         }
1003 }
1004
1005 void
1006 WaveView::set_shape (Shape s)
1007 {
1008         if (_shape != s) {
1009                 begin_visual_change ();
1010                 invalidate_image_cache ();
1011                 _shape = s;
1012                 end_visual_change ();
1013         }
1014 }
1015
1016 void
1017 WaveView::set_amplitude_above_axis (double a)
1018 {
1019         if (_amplitude_above_axis != a) {
1020                 begin_visual_change ();
1021                 invalidate_image_cache ();
1022                 _amplitude_above_axis = a;
1023                 end_visual_change ();
1024         }
1025 }
1026
1027 void
1028 WaveView::set_global_shape (Shape s)
1029 {
1030         if (_global_shape != s) {
1031                 _global_shape = s;
1032                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1033         }
1034 }
1035
1036 void
1037 WaveView::set_global_logscaled (bool yn)
1038 {
1039         if (_global_logscaled != yn) {
1040                 _global_logscaled = yn;
1041                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1042         }
1043 }
1044
1045 void
1046 WaveView::set_region_start (frameoffset_t start)
1047 {
1048         if (!_region) {
1049                 return;
1050         }
1051
1052         if (_region_start == start) {
1053                 return;
1054         }
1055
1056         begin_change ();
1057         _region_start = start;
1058         _bounding_box_dirty = true;
1059         end_change ();
1060 }
1061
1062 void
1063 WaveView::region_resized ()
1064 {
1065         /* Called when the region start or end (thus length) has changed.
1066         */
1067
1068         if (!_region) {
1069                 return;
1070         }
1071
1072         begin_change ();
1073         _region_start = _region->start();
1074         _bounding_box_dirty = true;
1075         end_change ();
1076 }
1077
1078 void
1079 WaveView::set_global_gradient_depth (double depth)
1080 {
1081         if (_global_gradient_depth != depth) {
1082                 _global_gradient_depth = depth;
1083                 VisualPropertiesChanged (); /* EMIT SIGNAL */
1084         }
1085 }
1086
1087 void
1088 WaveView::set_global_show_waveform_clipping (bool yn)
1089 {
1090         if (_global_show_waveform_clipping != yn) {
1091                 _global_show_waveform_clipping = yn;
1092                 ClipLevelChanged ();
1093         }
1094 }
1095
1096 void
1097 WaveView::set_start_shift (double pixels)
1098 {
1099         if (pixels < 0) {
1100                 return;
1101         }
1102
1103         begin_visual_change ();
1104         _start_shift = pixels;
1105         end_visual_change ();
1106 }
1107