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