initial semi-working attempt at getting waveview cache to work correctly
[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
38 #include <gdkmm/general.h>
39
40 using namespace std;
41 using namespace ARDOUR;
42 using namespace ArdourCanvas;
43
44 double WaveView::_global_gradient_depth = 0.6;
45 bool WaveView::_global_logscaled = false;
46 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
47
48 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
49
50 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
51         : Item (parent)
52         , Outline (parent)
53         , Fill (parent)
54         , _region (region)
55         , _channel (0)
56         , _samples_per_pixel (0)
57         , _height (64)
58         , _wave_color (0xffffffff)
59         , _show_zero (true)
60         , _zero_color (0xff0000ff)
61         , _clip_color (0xff0000ff)
62         , _logscaled (_global_logscaled)
63         , _shape (_global_shape)
64         , _gradient_depth (_global_gradient_depth)
65         , _shape_independent (false)
66         , _logscaled_independent (false)
67         , _gradient_depth_independent (false)
68         , _amplitude_above_axis (1.0)
69         , _region_start (region->start())
70 {
71         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
72 }
73
74 void
75 WaveView::handle_visual_property_change ()
76 {
77         bool changed = false;
78
79         if (!_shape_independent && (_shape != global_shape())) {
80                 _shape = global_shape();
81                 changed = true;
82         }
83
84         if (!_logscaled_independent && (_logscaled != global_logscaled())) {
85                 _logscaled = global_logscaled();
86                 changed = true;
87         }
88
89         if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
90                 _gradient_depth = global_gradient_depth();
91                 changed = true;
92         }
93         
94         if (changed) {
95                 invalidate_image_cache ();
96         }
97 }
98
99 void
100 WaveView::set_fill_color (Color c)
101 {
102         if (c != _fill_color) {
103                 invalidate_image_cache ();
104                 Fill::set_fill_color (c);
105         }
106 }
107
108 void
109 WaveView::set_outline_color (Color c)
110 {
111         if (c != _outline_color) {
112                 invalidate_image_cache ();
113                 Outline::set_outline_color (c);
114         }
115 }
116
117 void
118 WaveView::set_samples_per_pixel (double samples_per_pixel)
119 {
120         if (samples_per_pixel != _samples_per_pixel) {
121                 begin_change ();
122
123                 _samples_per_pixel = samples_per_pixel;
124                 
125                 _bounding_box_dirty = true;
126                 
127                 end_change ();
128                 
129                 invalidate_whole_cache ();
130         }
131 }
132
133 static inline double
134 to_src_sample_offset (frameoffset_t src_sample_start, double pixel_offset, double spp)
135 {
136         return llrintf (src_sample_start + (pixel_offset * spp));
137 }
138
139 static inline double
140 to_pixel_offset (frameoffset_t src_sample_start, double sample_offset, double spp)
141 {
142         return llrintf ((sample_offset - src_sample_start) / spp);
143 }
144
145 void
146 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
147 {
148         assert (_samples_per_pixel != 0);
149
150         if (!_region) {
151                 return;
152         }
153
154         Rect self = item_to_window (Rect (0.0, 0.0, floor (_region->length() / _samples_per_pixel), _height));
155         boost::optional<Rect> d = self.intersection (area);
156
157         if (!d) {
158                 return;
159         }
160         
161         Rect draw = d.get();
162
163         /* pixel coordinates */
164
165         double       start = floor (draw.x0);
166         double const end   = ceil (draw.x1);
167
168         list<CacheEntry*>::iterator cache = _cache.begin ();
169
170         cache = _cache.begin ();
171
172         while ((end - start) > 1.0) {
173
174                 /* Step through cache entries that end at or before our current position */
175
176                 for (; cache != _cache.end(); ++cache) {
177                         if ((*cache)->pixel_start() <= start) {
178                                 break;
179                         }
180                 }
181
182                 /* Now either:
183
184                    1. we have run out of cache entries
185
186                    2. we have found a cache entry that starts after start
187                       create a new cache entry to "fill in" before the one we have found.
188
189                    3. we have found a cache entry that starts at or before
190                       start, but finishes before end: create a new cache entry
191                       to extend the cache further along the timeline.
192
193                    Set up a pointer to the cache entry that we will use on this iteration.
194                 */
195
196                 CacheEntry* image = 0;
197                 const double BIG_IMAGE_SIZE = 32767.0;
198
199                 if (cache == _cache.end ()) {
200
201                         /* Case 1: we have run out of cache entries, so make a new one for
202                            the whole required area and put it in the list.
203                            
204                            We would like to avoid lots of little images in the
205                            cache, so when we create a new one, make it as wide
206                            as possible, within the limits inherent in Cairo.
207
208                            However, we don't want to try to make it larger than 
209                            the region actually is, so clamp with that too.
210                         */
211
212                         double const rend  = floor (_region->length() / _samples_per_pixel);
213                         double const end_pixel = min (rend, max (end, BIG_IMAGE_SIZE));
214
215                         if ((end_pixel - start) < 1.0) {
216                                 /* nothing more to draw */
217                                 break;
218                         }
219                         
220                         cerr << "Create new cache entry to grow cache,"
221                              << " range is " << start << " .. " << end_pixel
222                              << endl;
223
224                         CacheEntry* c = new CacheEntry (this, start, end_pixel);
225
226                         _cache.push_back (c);
227                         image = c;
228
229                 } else if ((*cache)->pixel_start() > start) {
230
231                         /* Case 2: we have a cache entry, but it begins after
232                          * start, so we need another one for the missing section.
233                          *  
234                          * Create a new cached image that extends as far as the
235                          * next cached image's start, or the end of the region,
236                          * or the end of a BIG_IMAGE, whichever comes first.
237                          */
238
239                         double end_pixel;
240
241                         if (end < (*cache)->pixel_start()) {
242                                 double const rend  = floor (_region->length() / _samples_per_pixel);
243                                 end_pixel = min (rend, max (end, BIG_IMAGE_SIZE));
244                         } else {
245                                 end_pixel = (*cache)->pixel_start();
246                         }
247
248                         cerr << "Create new cache entry to reach " << (*cache)->pixel_start()
249                              << " range is " << start << " .. " << end_pixel
250                              << endl;
251                 
252                         CacheEntry* c = new CacheEntry (this, start, end_pixel);
253
254                         cache = _cache.insert (cache, c);
255                         ++cache;
256                         image = c;
257
258                 } else {
259
260                         /* Case 3: we have a cache entry that will do at least some of what
261                            we have left, so render it.
262                         */
263
264                         image = *cache;
265                         ++cache;
266                 }
267
268                 double this_end = min (end, image->pixel_end ());
269                 double const image_origin = image->pixel_start ();
270 #if 0
271                 cerr << "\t\tDraw image between "
272                      << start
273                      << " .. "
274                      << this_end
275                      << " using image spanning "
276                      << image->pixel_start()
277                      << " .. "
278                      << image->pixel_end ()
279                      << endl;
280 #endif
281
282                 context->rectangle (start, draw.y0, this_end - start, _height);
283                 context->set_source (image->image(), self.x0 - image_origin, self.y0);
284                 context->fill ();
285                 
286                 start = this_end;
287                 
288         }
289 }
290
291 void
292 WaveView::compute_bounding_box () const
293 {
294         if (_region) {
295                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
296         } else {
297                 _bounding_box = boost::optional<Rect> ();
298         }
299         
300         _bounding_box_dirty = false;
301 }
302         
303 void
304 WaveView::set_height (Distance height)
305 {
306         if (height != _height) {
307                 begin_change ();
308                 
309                 _height = height;
310                 
311                 _bounding_box_dirty = true;
312                 end_change ();
313                 
314                 invalidate_image_cache ();
315         }
316 }
317
318 void
319 WaveView::set_channel (int channel)
320 {
321         if (channel != _channel) {
322                 begin_change ();
323                 
324                 _channel = channel;
325                 
326                 _bounding_box_dirty = true;
327                 end_change ();
328                 
329                 invalidate_whole_cache ();
330         }
331 }
332
333 void
334 WaveView::invalidate_whole_cache ()
335 {
336         begin_visual_change ();
337
338         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
339                 delete *i;
340         }
341
342         _cache.clear ();
343
344         end_visual_change ();
345 }
346
347 void
348 WaveView::invalidate_image_cache ()
349 {
350         begin_visual_change ();
351         
352         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
353                 (*i)->clear_image ();
354         }
355         
356         end_visual_change ();
357 }
358
359 void
360 WaveView::set_logscaled (bool yn)
361 {
362         if (_logscaled != yn) {
363                 _logscaled = yn;
364                 invalidate_image_cache ();
365         }
366 }
367
368 void
369 WaveView::gain_changed ()
370 {
371         invalidate_whole_cache ();
372 }
373
374 void
375 WaveView::set_zero_color (Color c)
376 {
377         if (_zero_color != c) {
378                 _zero_color = c;
379                 invalidate_image_cache ();
380         }
381 }
382
383 void
384 WaveView::set_clip_color (Color c)
385 {
386         if (_clip_color != c) {
387                 _clip_color = c;
388                 invalidate_image_cache ();
389         }
390 }
391
392 void
393 WaveView::set_show_zero_line (bool yn)
394 {
395         if (_show_zero != yn) {
396                 _show_zero = yn;
397                 invalidate_image_cache ();
398         }
399 }
400
401 void
402 WaveView::set_shape (Shape s)
403 {
404         if (_shape != s) {
405                 _shape = s;
406                 invalidate_image_cache ();
407         }
408 }
409
410 void
411 WaveView::set_amplitude_above_axis (double a)
412 {
413         if (_amplitude_above_axis != a) {
414                 _amplitude_above_axis = a;
415                 invalidate_image_cache ();
416         }
417 }
418
419 void
420 WaveView::set_global_shape (Shape s)
421 {
422         if (_global_shape != s) {
423                 _global_shape = s;
424                 VisualPropertiesChanged (); /* EMIT SIGNAL */
425         }
426 }
427
428 void
429 WaveView::set_global_logscaled (bool yn)
430 {
431         if (_global_logscaled != yn) {
432                 _global_logscaled = yn;
433                 VisualPropertiesChanged (); /* EMIT SIGNAL */
434                 
435         }
436 }
437
438 void
439 WaveView::region_resized ()
440 {
441         if (!_region) {
442                 return;
443         }
444
445         /* special: do not use _region->length() here to compute
446            bounding box because it will already have changed.
447            
448            if we have a bounding box, use it.
449         */
450
451         _pre_change_bounding_box = _bounding_box;
452
453         frameoffset_t s = _region->start();
454
455         if (s != _region_start) {
456                 /* if the region start changes, the information we have 
457                    in the image cache is out of date and not useful
458                    since it will fragmented into little pieces. invalidate
459                    the cache.
460                 */
461                 _region_start = _region->start();
462                 invalidate_whole_cache ();
463         }
464
465         _bounding_box_dirty = true;
466         compute_bounding_box ();
467
468         end_change ();
469 }
470
471 WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end)
472         : _wave_view (wave_view)
473         , _pixel_start (pixel_start)
474         , _pixel_end (pixel_end)
475         , _n_peaks (_pixel_end - _pixel_start)
476 {
477         _peaks.reset (new PeakData[_n_peaks]);
478
479         _sample_start = pixel_start * _wave_view->_samples_per_pixel;
480         _sample_end = pixel_end * _wave_view->_samples_per_pixel;
481
482         _wave_view->_region->read_peaks (_peaks.get(), _n_peaks, 
483                                          _sample_start, _sample_end,
484                                          _wave_view->_channel, 
485                                          _wave_view->_samples_per_pixel);
486 }
487
488 WaveView::CacheEntry::~CacheEntry ()
489 {
490 }
491
492 static inline float
493 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
494 {
495         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
496 }
497
498 static inline float
499 alt_log_meter (float power)
500 {
501         return _log_meter (power, -192.0, 0.0, 8.0);
502 }
503
504 Cairo::RefPtr<Cairo::ImageSurface>
505 WaveView::CacheEntry::image ()
506 {
507         if (!_image) {
508
509                 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
510                 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
511
512                 /* Draw the edge of the waveform, top half first, the loop back
513                  * for the bottom half to create a clockwise path
514                  */
515
516                 context->begin_new_path();
517
518                 if (_wave_view->_shape == WaveView::Rectified) {
519
520                         /* top edge of waveform is based on max (fabs (peak_min, peak_max))
521                          */
522
523                         if (_wave_view->_logscaled) {
524                                 for (int i = 0; i < _n_peaks; ++i) {
525                                         context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (
526                                                                                                     max (fabs (_peaks[i].max), fabs (_peaks[i].min))))));
527                                 }
528                         } else {
529                                 for (int i = 0; i < _n_peaks; ++i) {
530                                         context->line_to (i + 0.5, position (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
531                                 }
532                         }
533
534                 } else {
535                         if (_wave_view->_logscaled) {
536                                 for (int i = 0; i < _n_peaks; ++i) {
537                                         Coord y = _peaks[i].max;
538                                         
539                                         if (y > 0.0) {
540                                                 y = position (alt_log_meter (fast_coefficient_to_dB (y)));
541                                         } else if (y < 0.0) {
542                                                 y = position (-alt_log_meter (fast_coefficient_to_dB (-y)));
543                                         } else {
544                                                 y = position (0.0);
545                                         }
546                                         context->line_to (i + 0.5, y);
547                                 } 
548                         } else {
549                                 for (int i = 0; i < _n_peaks; ++i) {
550                                         context->line_to (i + 0.5, position (_peaks[i].max));
551                                 }
552                         }
553                 }
554
555                 /* from final top point, move out of the clip zone */
556
557                 context->line_to (_n_peaks + 10, position (0.0));
558         
559                 /* bottom half, in reverse */
560         
561                 if (_wave_view->_shape == WaveView::Rectified) {
562                         
563                         /* lower half: drop to the bottom, then a line back to
564                          * beyond the left edge of the clip region 
565                          */
566
567                         context->line_to (_n_peaks + 10, _wave_view->_height);
568                         context->line_to (-10.0, _wave_view->_height);
569
570                 } else {
571
572                         if (_wave_view->_logscaled) {
573                                 for (int i = _n_peaks-1; i >= 0; --i) {
574                                         Coord y = _peaks[i].min;
575                                         if (y > 0.0) {
576                                                 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (y))));
577                                         } else if (y < 0.0) {
578                                                 context->line_to (i + 0.5, position (-alt_log_meter (fast_coefficient_to_dB (-y))));
579                                         } else {
580                                                 context->line_to (i + 0.5, position (0.0));
581                                         }
582                                 } 
583                         } else {
584                                 for (int i = _n_peaks-1; i >= 0; --i) {
585                                         context->line_to (i + 0.5, position (_peaks[i].min));
586                                 }
587                         }
588                 
589                         /* from final bottom point, move out of the clip zone */
590                         
591                         context->line_to (-10.0, position (0.0));
592                 }
593
594                 context->close_path ();
595
596                 if (_wave_view->gradient_depth() != 0.0) {
597                         
598                         Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
599                         
600                         double stops[3];
601                         
602                         double r, g, b, a;
603
604                         if (_wave_view->_shape == Rectified) {
605                                 stops[0] = 0.1;
606                                 stops[0] = 0.3;
607                                 stops[0] = 0.9;
608                         } else {
609                                 stops[0] = 0.1;
610                                 stops[1] = 0.5;
611                                 stops[2] = 0.9;
612                         }
613
614                         color_to_rgba (_wave_view->_fill_color, r, g, b, a);
615                         gradient->add_color_stop_rgba (stops[0], r, g, b, a);
616                         gradient->add_color_stop_rgba (stops[2], r, g, b, a);
617                         
618                         /* generate a new color for the middle of the gradient */
619                         double h, s, v;
620                         color_to_hsv (_wave_view->_fill_color, h, s, v);
621                         /* tone down the saturation */
622                         s *= 1.0 - _wave_view->gradient_depth();
623                         Color center = hsv_to_color (h, s, v, a);
624                         color_to_rgba (center, r, g, b, a);
625                         gradient->add_color_stop_rgba (stops[1], r, g, b, a);
626                         
627                         context->set_source (gradient);
628                 } else {
629                         set_source_rgba (context, _wave_view->_fill_color);
630                 }
631
632                 context->fill_preserve ();
633                 _wave_view->setup_outline_context (context);
634                 context->stroke ();
635
636                 if (_wave_view->show_zero_line()) {
637                         set_source_rgba (context, _wave_view->_zero_color);
638                         context->move_to (0, position (0.0));
639                         context->line_to (_n_peaks, position (0.0));
640                         context->stroke ();
641                 }
642         }
643
644         return _image;
645 }
646
647
648 Coord
649 WaveView::CacheEntry::position (double s) const
650 {
651         switch (_wave_view->_shape) {
652         case Rectified:
653                 return _wave_view->_height - (s * _wave_view->_height);
654         default:
655                 break;
656         }
657         return (1.0-s) * (_wave_view->_height / 2.0);
658 }
659
660 void
661 WaveView::CacheEntry::clear_image ()
662 {
663         _image.clear ();
664 }
665             
666 void
667 WaveView::set_global_gradient_depth (double depth)
668 {
669         if (_global_gradient_depth != depth) {
670                 _global_gradient_depth = depth;
671                 VisualPropertiesChanged (); /* EMIT SIGNAL */
672         }
673 }