basically operational switch to canvas drawing coordinates, although text and waves...
[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, _region->length() / _samples_per_pixel, _height));
155         boost::optional<Rect> draw = self.intersection (area);
156
157         context->rectangle (self.x0, self.y0, self.width(), self.height());
158         context->set_source_rgb (1.0, 0.0, 0.0);
159         context->stroke ();
160
161         if (!draw) {
162                 return;
163         }
164
165         /* pixel coordinates */
166
167         double       start = draw->x0;
168         double const end   = draw->x1;
169
170         list<CacheEntry*>::iterator cache = _cache.begin ();
171
172         cerr << name << " draw " << area << "self = " << self << "\n\twill use " << draw.get() << endl;
173 #if 1
174         cerr << "  Cache contains " << _cache.size() << endl;
175         while (cache != _cache.end()) {
176                 cerr << "\tsample span " << (*cache)->start() << " .. " << (*cache)->end()
177                      << " pixel span " 
178                      << to_pixel_offset (_region_start, (*cache)->start(), _samples_per_pixel)
179                      << " .. "
180                      << to_pixel_offset (_region_start, (*cache)->end(), _samples_per_pixel)
181                      << endl;
182                 ++cache;
183         }
184         cache = _cache.begin();
185 #endif
186
187         while ((end - start) > 1.0) {
188
189                 cerr << "***** RANGE = " << start << " .. " << end << " = " << end - start << endl;
190
191                 frameoffset_t start_sample_offset = to_src_sample_offset (_region_start, start, _samples_per_pixel);
192
193                 /* Step through cache entries that end at or before our current position */
194
195                 while (cache != _cache.end() && (*cache)->end() <= start_sample_offset) {
196                         ++cache;
197                 }
198
199                 /* Now either:
200                    1. we have run out of cache entries
201                    2. the one we are looking at finishes after start(_sample_offset) but also starts after start(_sample_offset).
202                    3. the one we are looking at finishes after start(_sample_offset) and starts before start(_sample_offset).
203
204                    Set up a pointer to the cache entry that we will use on this iteration.
205                 */
206
207                 CacheEntry* image = 0;
208
209                 if (cache == _cache.end ()) {
210
211                         cerr << "Nothing in cache spans\n";
212
213                         /* Case 1: we have run out of cache entries, so make a new one for
214                            the whole required area and put it in the list.
215                            
216                            We would like to avoid lots of little images in the
217                            cache, so when we create a new one, make it as wide
218                            as possible, within a sensible limit (here, the
219                            visible width of the canvas we're on). 
220
221                            However, we don't want to try to make it larger than 
222                            the region actually is, so clamp with that too.
223                         */
224
225                         double const rend  = _region->length() / _samples_per_pixel;
226                         double const endpoint = min (rend, max (end, start + _canvas->visible_area().width()));
227
228                         cerr << "New cache entry for " << start_sample_offset << " .. " << to_src_sample_offset (_region_start, endpoint, _samples_per_pixel)
229                              << endl;
230
231                         CacheEntry* c = new CacheEntry (this, 
232                                                         start_sample_offset,
233                                                         to_src_sample_offset (_region_start, endpoint, _samples_per_pixel),
234                                                         endpoint - start);
235                         _cache.push_back (c);
236                         image = c;
237
238                 } else if ((*cache)->start() > start_sample_offset) {
239
240                         /* Case 2: we have a cache entry, but it starts after
241                          * start(_sample_offset), so we need another one for
242                          * the missing bit.
243                          *  
244                          * Create a new cached image that extends as far as the
245                          * next cached image's start, or the end of the region,
246                          * or the end of the render area, whichever comes first.
247                          */
248
249                         double    end_pixel;
250                         double end_sample_offset;
251                         int npeaks;
252
253                         if (end_sample_offset < (*cache)->start()) {
254                                 double const rend  = _region->length() / _samples_per_pixel;
255                                 end_sample_offset = to_src_sample_offset (_region_start, end_pixel, _samples_per_pixel);
256                                 end_pixel = min (rend, end);
257                         } else {
258                                 end_sample_offset = (*cache)->start();
259                                 end_pixel = to_pixel_offset (_region_start, end_sample_offset, _samples_per_pixel);
260                         }
261                 
262                         npeaks = end_pixel - start;
263                         assert (npeaks > 0);
264
265                         cerr << "New fill-in cache entry for " << start_sample_offset << " .. " << end_sample_offset << endl;
266
267                         CacheEntry* c = new CacheEntry (this,
268                                                         start_sample_offset,
269                                                         end_sample_offset,
270                                                         npeaks);
271
272                         cache = _cache.insert (cache, c);
273                         ++cache;
274                         image = c;
275
276                 } else {
277
278                         /* Case 3: we have a cache entry that will do at least some of what
279                            we have left, so render it.
280                         */
281
282                         cerr << "found suitable cache entry\n";
283
284                         image = *cache;
285                         ++cache;
286
287                 }
288
289                 
290
291                 double this_end = min (end, to_pixel_offset (_region_start, image->end (), _samples_per_pixel));
292                 double const image_origin = to_pixel_offset (_region_start, image->start(), _samples_per_pixel);
293 #if 1
294                 cerr << "\t\tDraw image between "
295                      << start
296                      << " .. "
297                      << this_end
298                      << " using image spanning "
299                      << image->start()
300                      << " .. "
301                      << image->end ()
302                      << " pixels "
303                      << to_pixel_offset (_region_start, image->start(), _samples_per_pixel)
304                      << " .. "
305                      << to_pixel_offset (_region_start, image->end(), _samples_per_pixel)
306                      << endl;
307 #endif
308
309                 cerr << "Fill rect " << draw->x0 << ", " << self.y0 << ' ' << draw->width() << " x " << draw->height() << endl;
310
311                 context->rectangle (start, draw->y0, this_end - start, _height);
312                 // context->set_source (image->image(), image_origin, self.y0 - draw->y0);
313                 context->set_source_rgb (0.0, 0.0, 1.0);
314                 context->fill ();
315                 
316                 start = this_end;
317         }
318 }
319
320 void
321 WaveView::compute_bounding_box () const
322 {
323         if (_region) {
324                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
325         } else {
326                 _bounding_box = boost::optional<Rect> ();
327         }
328         
329         _bounding_box_dirty = false;
330 }
331         
332 void
333 WaveView::set_height (Distance height)
334 {
335         if (height != _height) {
336                 begin_change ();
337                 
338                 _height = height;
339                 
340                 _bounding_box_dirty = true;
341                 end_change ();
342                 
343                 invalidate_image_cache ();
344         }
345 }
346
347 void
348 WaveView::set_channel (int channel)
349 {
350         if (channel != _channel) {
351                 begin_change ();
352                 
353                 _channel = channel;
354                 
355                 _bounding_box_dirty = true;
356                 end_change ();
357                 
358                 invalidate_whole_cache ();
359         }
360 }
361
362 void
363 WaveView::invalidate_whole_cache ()
364 {
365         begin_visual_change ();
366
367         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
368                 delete *i;
369         }
370
371         _cache.clear ();
372
373         end_visual_change ();
374 }
375
376 void
377 WaveView::invalidate_image_cache ()
378 {
379         begin_visual_change ();
380         
381         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
382                 (*i)->clear_image ();
383         }
384         
385         end_visual_change ();
386 }
387
388 void
389 WaveView::set_logscaled (bool yn)
390 {
391         if (_logscaled != yn) {
392                 _logscaled = yn;
393                 invalidate_image_cache ();
394         }
395 }
396
397 void
398 WaveView::gain_changed ()
399 {
400         invalidate_whole_cache ();
401 }
402
403 void
404 WaveView::set_zero_color (Color c)
405 {
406         if (_zero_color != c) {
407                 _zero_color = c;
408                 invalidate_image_cache ();
409         }
410 }
411
412 void
413 WaveView::set_clip_color (Color c)
414 {
415         if (_clip_color != c) {
416                 _clip_color = c;
417                 invalidate_image_cache ();
418         }
419 }
420
421 void
422 WaveView::set_show_zero_line (bool yn)
423 {
424         if (_show_zero != yn) {
425                 _show_zero = yn;
426                 invalidate_image_cache ();
427         }
428 }
429
430 void
431 WaveView::set_shape (Shape s)
432 {
433         if (_shape != s) {
434                 _shape = s;
435                 invalidate_image_cache ();
436         }
437 }
438
439 void
440 WaveView::set_amplitude_above_axis (double a)
441 {
442         if (_amplitude_above_axis != a) {
443                 _amplitude_above_axis = a;
444                 invalidate_image_cache ();
445         }
446 }
447
448 void
449 WaveView::set_global_shape (Shape s)
450 {
451         if (_global_shape != s) {
452                 _global_shape = s;
453                 VisualPropertiesChanged (); /* EMIT SIGNAL */
454         }
455 }
456
457 void
458 WaveView::set_global_logscaled (bool yn)
459 {
460         if (_global_logscaled != yn) {
461                 _global_logscaled = yn;
462                 VisualPropertiesChanged (); /* EMIT SIGNAL */
463                 
464         }
465 }
466
467 void
468 WaveView::region_resized ()
469 {
470         if (!_region) {
471                 return;
472         }
473
474         /* special: do not use _region->length() here to compute
475            bounding box because it will already have changed.
476            
477            if we have a bounding box, use it.
478         */
479
480         _pre_change_bounding_box = _bounding_box;
481
482         frameoffset_t s = _region->start();
483
484         if (s != _region_start) {
485                 /* if the region start changes, the information we have 
486                    in the image cache is out of date and not useful
487                    since it will fragmented into little pieces. invalidate
488                    the cache.
489                 */
490                 _region_start = _region->start();
491                 invalidate_whole_cache ();
492         }
493
494         _bounding_box_dirty = true;
495         compute_bounding_box ();
496
497         end_change ();
498 }
499
500 WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double start, double end, int npeaks)
501         : _wave_view (wave_view)
502         , _start (start)
503         , _end (end)
504         , _n_peaks (npeaks)
505 {
506         _peaks.reset (new PeakData[_n_peaks]);
507
508         _wave_view->_region->read_peaks (_peaks.get(), _n_peaks, 
509                                          (framecnt_t) floor (_start), 
510                                          (framecnt_t) ceil (_end - _start), 
511                                          _wave_view->_channel, _wave_view->_samples_per_pixel);
512 }
513
514 WaveView::CacheEntry::~CacheEntry ()
515 {
516 }
517
518 static inline float
519 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
520 {
521         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
522 }
523
524 static inline float
525 alt_log_meter (float power)
526 {
527         return _log_meter (power, -192.0, 0.0, 8.0);
528 }
529
530 Cairo::RefPtr<Cairo::ImageSurface>
531 WaveView::CacheEntry::image ()
532 {
533         if (!_image) {
534
535                 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
536                 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
537
538                 /* Draw the edge of the waveform, top half first, the loop back
539                  * for the bottom half to create a clockwise path
540                  */
541
542                 context->begin_new_path();
543
544                 if (_wave_view->_shape == WaveView::Rectified) {
545
546                         /* top edge of waveform is based on max (fabs (peak_min, peak_max))
547                          */
548
549                         if (_wave_view->_logscaled) {
550                                 for (int i = 0; i < _n_peaks; ++i) {
551                                         context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (
552                                                                                                     max (fabs (_peaks[i].max), fabs (_peaks[i].min))))));
553                                 }
554                         } else {
555                                 for (int i = 0; i < _n_peaks; ++i) {
556                                         context->line_to (i + 0.5, position (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
557                                 }
558                         }
559
560                 } else {
561                         if (_wave_view->_logscaled) {
562                                 for (int i = 0; i < _n_peaks; ++i) {
563                                         Coord y = _peaks[i].max;
564                                         
565                                         if (y > 0.0) {
566                                                 y = position (alt_log_meter (fast_coefficient_to_dB (y)));
567                                         } else if (y < 0.0) {
568                                                 y = position (-alt_log_meter (fast_coefficient_to_dB (-y)));
569                                         } else {
570                                                 y = position (0.0);
571                                         }
572                                         context->line_to (i + 0.5, y);
573                                 } 
574                         } else {
575                                 for (int i = 0; i < _n_peaks; ++i) {
576                                         context->line_to (i + 0.5, position (_peaks[i].max));
577                                 }
578                         }
579                 }
580
581                 /* from final top point, move out of the clip zone */
582
583                 context->line_to (_n_peaks + 10, position (0.0));
584         
585                 /* bottom half, in reverse */
586         
587                 if (_wave_view->_shape == WaveView::Rectified) {
588                         
589                         /* lower half: drop to the bottom, then a line back to
590                          * beyond the left edge of the clip region 
591                          */
592
593                         context->line_to (_n_peaks + 10, _wave_view->_height);
594                         context->line_to (-10.0, _wave_view->_height);
595
596                 } else {
597
598                         if (_wave_view->_logscaled) {
599                                 for (int i = _n_peaks-1; i >= 0; --i) {
600                                         Coord y = _peaks[i].min;
601                                         if (y > 0.0) {
602                                                 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (y))));
603                                         } else if (y < 0.0) {
604                                                 context->line_to (i + 0.5, position (-alt_log_meter (fast_coefficient_to_dB (-y))));
605                                         } else {
606                                                 context->line_to (i + 0.5, position (0.0));
607                                         }
608                                 } 
609                         } else {
610                                 for (int i = _n_peaks-1; i >= 0; --i) {
611                                         context->line_to (i + 0.5, position (_peaks[i].min));
612                                 }
613                         }
614                 
615                         /* from final bottom point, move out of the clip zone */
616                         
617                         context->line_to (-10.0, position (0.0));
618                 }
619
620                 context->close_path ();
621
622                 if (_wave_view->gradient_depth() != 0.0) {
623                         
624                         Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
625                         
626                         double stops[3];
627                         
628                         double r, g, b, a;
629
630                         if (_wave_view->_shape == Rectified) {
631                                 stops[0] = 0.1;
632                                 stops[0] = 0.3;
633                                 stops[0] = 0.9;
634                         } else {
635                                 stops[0] = 0.1;
636                                 stops[1] = 0.5;
637                                 stops[2] = 0.9;
638                         }
639
640                         color_to_rgba (_wave_view->_fill_color, r, g, b, a);
641                         gradient->add_color_stop_rgba (stops[0], r, g, b, a);
642                         gradient->add_color_stop_rgba (stops[2], r, g, b, a);
643                         
644                         /* generate a new color for the middle of the gradient */
645                         double h, s, v;
646                         color_to_hsv (_wave_view->_fill_color, h, s, v);
647                         /* tone down the saturation */
648                         s *= 1.0 - _wave_view->gradient_depth();
649                         Color center = hsv_to_color (h, s, v, a);
650                         color_to_rgba (center, r, g, b, a);
651                         gradient->add_color_stop_rgba (stops[1], r, g, b, a);
652                         
653                         context->set_source (gradient);
654                 } else {
655                         set_source_rgba (context, _wave_view->_fill_color);
656                 }
657
658                 context->fill_preserve ();
659                 _wave_view->setup_outline_context (context);
660                 context->stroke ();
661
662                 if (_wave_view->show_zero_line()) {
663                         set_source_rgba (context, _wave_view->_zero_color);
664                         context->move_to (0, position (0.0));
665                         context->line_to (_n_peaks, position (0.0));
666                         context->stroke ();
667                 }
668         }
669
670         return _image;
671 }
672
673
674 Coord
675 WaveView::CacheEntry::position (double s) const
676 {
677         switch (_wave_view->_shape) {
678         case Rectified:
679                 return _wave_view->_height - (s * _wave_view->_height);
680         default:
681                 break;
682         }
683         return (1.0-s) * (_wave_view->_height / 2.0);
684 }
685
686 void
687 WaveView::CacheEntry::clear_image ()
688 {
689         _image.clear ();
690 }
691             
692 void
693 WaveView::set_global_gradient_depth (double depth)
694 {
695         if (_global_gradient_depth != depth) {
696                 _global_gradient_depth = depth;
697                 VisualPropertiesChanged (); /* EMIT SIGNAL */
698         }
699 }