fix clamping of line and rect coordinates to avoid issues with cairo when drawing...
[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
29 #include "ardour/types.h"
30 #include "ardour/dB.h"
31 #include "ardour/audioregion.h"
32
33 #include "canvas/wave_view.h"
34 #include "canvas/utils.h"
35 #include "canvas/canvas.h"
36
37 #include <gdkmm/general.h>
38
39 using namespace std;
40 using namespace ARDOUR;
41 using namespace ArdourCanvas;
42
43 double WaveView::_global_gradient_depth = 0.6;
44 bool WaveView::_global_logscaled = false;
45 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
46
47 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
48
49 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
50         : Item (parent)
51         , Outline (parent)
52         , Fill (parent)
53         , _region (region)
54         , _channel (0)
55         , _samples_per_pixel (0)
56         , _height (64)
57         , _wave_color (0xffffffff)
58         , _show_zero (true)
59         , _zero_color (0xff0000ff)
60         , _clip_color (0xff0000ff)
61         , _logscaled (_global_logscaled)
62         , _shape (_global_shape)
63         , _gradient_depth (_global_gradient_depth)
64         , _amplitude (1.0)
65         , _shape_independent (false)
66         , _logscaled_independent (false)
67         , _gradient_depth_independent (false)
68         , _region_start (0)
69 {
70         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
71 }
72
73 void
74 WaveView::handle_visual_property_change ()
75 {
76         bool changed = false;
77
78         if (!_shape_independent && (_shape != global_shape())) {
79                 _shape = global_shape();
80                 changed = true;
81         }
82
83         if (!_logscaled_independent && (_logscaled != global_logscaled())) {
84                 _logscaled = global_logscaled();
85                 changed = true;
86         }
87
88         if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
89                 _gradient_depth = global_gradient_depth();
90                 changed = true;
91         }
92         
93         if (changed) {
94                 invalidate_image_cache ();
95         }
96 }
97
98 void
99 WaveView::set_fill_color (Color c)
100 {
101         invalidate_image_cache ();
102         Fill::set_fill_color (c);
103 }
104
105 void
106 WaveView::set_outline_color (Color c)
107 {
108         invalidate_image_cache ();
109         Outline::set_outline_color (c);
110 }
111
112 void
113 WaveView::set_samples_per_pixel (double samples_per_pixel)
114 {
115         begin_change ();
116         
117         _samples_per_pixel = samples_per_pixel;
118
119         _bounding_box_dirty = true;
120         end_change ();
121
122         invalidate_whole_cache ();
123 }
124
125 void
126 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
127 {
128         assert (_samples_per_pixel != 0);
129
130         if (!_region) {
131                 return;
132         }
133
134         /* p, start and end are offsets from the start of the source.
135            area is relative to the position of the region.
136          */
137         
138         int const start = rint (area.x0 + _region_start / _samples_per_pixel);
139         int const end   = rint (area.x1 + _region_start / _samples_per_pixel);
140
141         int p = start;
142         list<CacheEntry*>::iterator cache = _cache.begin ();
143
144         while (p < end) {
145
146                 /* Step through cache entries that end at or before our current position, p */
147                 while (cache != _cache.end() && (*cache)->end() <= p) {
148                         ++cache;
149                 }
150
151                 /* Now either:
152                    1. we have run out of cache entries
153                    2. the one we are looking at finishes after p but also starts after p.
154                    3. the one we are looking at finishes after p and starts before p.
155
156                    Set up a pointer to the cache entry that we will use on this iteration.
157                 */
158
159                 CacheEntry* render = 0;
160
161                 if (cache == _cache.end ()) {
162
163                         /* Case 1: we have run out of cache entries, so make a new one for
164                            the whole required area and put it in the list.
165                         */
166                         
167                         CacheEntry* c = new CacheEntry (this, p, end);
168                         _cache.push_back (c);
169                         render = c;
170
171                 } else if ((*cache)->start() > p) {
172
173                         /* Case 2: we have a cache entry, but it starts after p, so we
174                            need another one for the missing bit.
175                         */
176
177                         CacheEntry* c = new CacheEntry (this, p, (*cache)->start());
178                         cache = _cache.insert (cache, c);
179                         ++cache;
180                         render = c;
181
182                 } else {
183
184                         /* Case 3: we have a cache entry that will do at least some of what
185                            we have left, so render it.
186                         */
187
188                         render = *cache;
189                         ++cache;
190
191                 }
192
193                 int const this_end = min (end, render->end ());
194                 
195                 Coord const left  =        p - _region_start / _samples_per_pixel;
196                 Coord const right = this_end - _region_start / _samples_per_pixel;
197                 
198                 context->save ();
199                 
200                 context->rectangle (left, area.y0, right, area.height());
201                 context->clip ();
202                 
203                 context->translate (left, 0);
204
205                 context->set_source (render->image(), render->start() - p, 0);
206                 context->paint ();
207                 
208                 context->restore ();
209
210                 p = min (end, render->end ());
211         }
212 }
213
214 void
215 WaveView::compute_bounding_box () const
216 {
217         if (_region) {
218                 _bounding_box = Rect (0, 0, _region->length() / _samples_per_pixel, _height);
219         } else {
220                 _bounding_box = boost::optional<Rect> ();
221         }
222         
223         _bounding_box_dirty = false;
224 }
225         
226 void
227 WaveView::set_height (Distance height)
228 {
229         begin_change ();
230
231         _height = height;
232
233         _bounding_box_dirty = true;
234         end_change ();
235
236         invalidate_image_cache ();
237 }
238
239 void
240 WaveView::set_channel (int channel)
241 {
242         begin_change ();
243         
244         _channel = channel;
245
246         _bounding_box_dirty = true;
247         end_change ();
248
249         invalidate_whole_cache ();
250 }
251
252 void
253 WaveView::invalidate_whole_cache ()
254 {
255         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
256                 delete *i;
257         }
258
259         _cache.clear ();
260         _canvas->item_visual_property_changed (this);
261 }
262
263 void
264 WaveView::invalidate_image_cache ()
265 {
266         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
267                 (*i)->clear_image ();
268         }
269         _canvas->item_visual_property_changed (this);
270 }
271
272 void
273 WaveView::region_resized ()
274 {
275         _bounding_box_dirty = true;
276 }
277
278 void
279 WaveView::set_logscaled (bool yn)
280 {
281         if (_logscaled != yn) {
282                 _logscaled = yn;
283                 invalidate_image_cache ();
284         }
285 }
286
287 void
288 WaveView::set_amplitude (double a)
289 {
290         if (_amplitude != a) {
291                 _amplitude = a;
292                 invalidate_image_cache ();
293         }
294 }
295
296 void
297 WaveView::set_zero_color (Color c)
298 {
299         if (_zero_color != c) {
300                 _zero_color = c;
301                 invalidate_image_cache ();
302         }
303 }
304
305 void
306 WaveView::set_clip_color (Color c)
307 {
308         if (_clip_color != c) {
309                 _clip_color = c;
310                 invalidate_image_cache ();
311         }
312 }
313
314 void
315 WaveView::set_show_zero_line (bool yn)
316 {
317         if (_show_zero != yn) {
318                 _show_zero = yn;
319                 invalidate_image_cache ();
320         }
321 }
322
323 void
324 WaveView::set_shape (Shape s)
325 {
326         if (_shape != s) {
327                 _shape = s;
328                 invalidate_image_cache ();
329         }
330 }
331
332 void
333 WaveView::set_global_shape (Shape s)
334 {
335         if (_global_shape != s) {
336                 _global_shape = s;
337                 VisualPropertiesChanged (); /* EMIT SIGNAL */
338         }
339 }
340
341 void
342 WaveView::set_global_logscaled (bool yn)
343 {
344         if (_global_logscaled != yn) {
345                 _global_logscaled = yn;
346                 VisualPropertiesChanged (); /* EMIT SIGNAL */
347                 
348         }
349 }
350
351 void
352 WaveView::set_region_start (frameoffset_t start)
353 {
354         _region_start = start;
355         _bounding_box_dirty = true;
356 }
357
358 /** Construct a new CacheEntry with peak data between two offsets
359  *  in the source.
360  */
361 WaveView::CacheEntry::CacheEntry (
362         WaveView const * wave_view,
363         int start,
364         int end
365         )
366         : _wave_view (wave_view)
367         , _start (start)
368         , _end (end)
369 {
370         _n_peaks = _end - _start;
371         _peaks.reset (new PeakData[_n_peaks]);
372
373         _wave_view->_region->read_peaks (
374                 _peaks.get(),
375                 _n_peaks,
376                 _start * _wave_view->_samples_per_pixel,
377                 (_end - _start) * _wave_view->_samples_per_pixel,
378                 _wave_view->_channel,
379                 _wave_view->_samples_per_pixel
380                 );
381 }
382
383 WaveView::CacheEntry::~CacheEntry ()
384 {
385 }
386
387 static inline float
388 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
389 {
390         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
391 }
392
393 static inline float
394 alt_log_meter (float power)
395 {
396         return _log_meter (power, -192.0, 0.0, 8.0);
397 }
398
399 Cairo::RefPtr<Cairo::ImageSurface>
400 WaveView::CacheEntry::image ()
401 {
402         if (!_image) {
403
404                 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
405                 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
406
407                 /* Draw the edge of the waveform, top half first, the loop back
408                  * for the bottom half to create a clockwise path
409                  */
410
411                 context->begin_new_path();
412
413
414                 if (_wave_view->_shape == WaveView::Rectified) {
415
416                         /* top edge of waveform is based on max (fabs (peak_min, peak_max))
417                          */
418
419                         if (_wave_view->_logscaled) {
420                                 for (int i = 0; i < _n_peaks; ++i) {
421                                         context->line_to (i + 0.5, position (_wave_view->amplitude() * 
422                                                                              alt_log_meter (fast_coefficient_to_dB (
423                                                                                                     max (fabs (_peaks[i].max), fabs (_peaks[i].min))))));
424                                 }
425                         } else {
426                                 for (int i = 0; i < _n_peaks; ++i) {
427                                         context->line_to (i + 0.5, position (_wave_view->amplitude() * max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
428                                 }
429                         }
430
431                 } else {
432                         if (_wave_view->_logscaled) {
433                                 for (int i = 0; i < _n_peaks; ++i) {
434                                         Coord y = _peaks[i].max;
435                                         if (y > 0.0) {
436                                                 context->line_to (i + 0.5, position (_wave_view->amplitude() * alt_log_meter (fast_coefficient_to_dB (y))));
437                                         } else if (y < 0.0) {
438                                                 context->line_to (i + 0.5, position (_wave_view->amplitude() * -alt_log_meter (fast_coefficient_to_dB (-y))));
439                                         } else {
440                                                 context->line_to (i + 0.5, position (0.0));
441                                         }
442                                 } 
443                         } else {
444                                 for (int i = 0; i < _n_peaks; ++i) {
445                                         context->line_to (i + 0.5, position (_wave_view->amplitude() * _peaks[i].max));
446                                 }
447                         }
448                 }
449
450                 /* from final top point, move out of the clip zone */
451
452                 context->line_to (_n_peaks + 10, position (0.0));
453
454         
455                 /* bottom half, in reverse */
456         
457                 if (_wave_view->_shape == WaveView::Rectified) {
458                         
459                         /* lower half: drop to the bottom, then a line back to
460                          * beyond the left edge of the clip region 
461                          */
462
463                         context->line_to (_n_peaks + 10, _wave_view->_height);
464                         context->line_to (-10.0, _wave_view->_height);
465
466                 } else {
467
468                         if (_wave_view->_logscaled) {
469                                 for (int i = _n_peaks-1; i >= 0; --i) {
470                                         Coord y = _peaks[i].min;
471                                         if (y > 0.0) {
472                                                 context->line_to (i + 0.5, position (_wave_view->amplitude() * alt_log_meter (fast_coefficient_to_dB (y))));
473                                         } else if (y < 0.0) {
474                                                 context->line_to (i + 0.5, position (_wave_view->amplitude() * -alt_log_meter (fast_coefficient_to_dB (-y))));
475                                         } else {
476                                                 context->line_to (i + 0.5, position (0.0));
477                                         }
478                                 } 
479                         } else {
480                                 for (int i = _n_peaks-1; i >= 0; --i) {
481                                         context->line_to (i + 0.5, position (_wave_view->amplitude() * _peaks[i].min));
482                                 }
483                         }
484                 
485                         /* from final bottom point, move out of the clip zone */
486                         
487                         context->line_to (-10.0, position (0.0));
488                 }
489
490                 context->close_path ();
491
492                 if (_wave_view->gradient_depth() != 0.0) {
493                         
494                         Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
495                         
496                         double stops[3];
497                         
498                         double r, g, b, a;
499
500                         if (_wave_view->_shape == Rectified) {
501                                 stops[0] = 0.1;
502                                 stops[0] = 0.3;
503                                 stops[0] = 0.9;
504                         } else {
505                                 stops[0] = 0.1;
506                                 stops[1] = 0.5;
507                                 stops[2] = 0.9;
508                         }
509
510                         color_to_rgba (_wave_view->_fill_color, r, g, b, a);
511                         gradient->add_color_stop_rgba (stops[0], r, g, b, a);
512                         gradient->add_color_stop_rgba (stops[2], r, g, b, a);
513                         
514                         /* generate a new color for the middle of the gradient */
515                         double h, s, v;
516                         color_to_hsv (_wave_view->_fill_color, h, s, v);
517                         /* tone down the saturation */
518                         s *= 1.0 - _wave_view->gradient_depth();
519                         Color center = hsv_to_color (h, s, v, a);
520                         color_to_rgba (center, r, g, b, a);
521                         gradient->add_color_stop_rgba (stops[1], r, g, b, a);
522                         
523                         context->set_source (gradient);
524                 } else {
525                         set_source_rgba (context, _wave_view->_fill_color);
526                 }
527
528                 context->fill_preserve ();
529                 _wave_view->setup_outline_context (context);
530                 context->stroke ();
531
532                 if (_wave_view->show_zero_line()) {
533                         set_source_rgba (context, _wave_view->_zero_color);
534                         context->move_to (0, position (0.0));
535                         context->line_to (_n_peaks, position (0.0));
536                         context->stroke ();
537                 }
538         }
539
540         return _image;
541 }
542
543
544 Coord
545 WaveView::CacheEntry::position (double s) const
546 {
547         switch (_wave_view->_shape) {
548         case Rectified:
549                 return _wave_view->_height - (s * _wave_view->_height);
550         default:
551                 break;
552         }
553         return (s+1.0) * (_wave_view->_height / 2.0);
554 }
555
556 void
557 WaveView::CacheEntry::clear_image ()
558 {
559         _image.clear ();
560 }
561             
562 void
563 WaveView::set_global_gradient_depth (double depth)
564 {
565         if (_global_gradient_depth != depth) {
566                 _global_gradient_depth = depth;
567                 VisualPropertiesChanged (); /* EMIT SIGNAL */
568         }
569 }