0248b9ddaabb090b1ee514f48be60d4a6cbb08de
[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         , _sample_start (-1)
71         , _sample_end (-1)
72 {
73         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
74 }
75
76 WaveView::~WaveView ()
77 {
78 }
79
80 void
81 WaveView::handle_visual_property_change ()
82 {
83         bool changed = false;
84
85         if (!_shape_independent && (_shape != global_shape())) {
86                 _shape = global_shape();
87                 changed = true;
88         }
89
90         if (!_logscaled_independent && (_logscaled != global_logscaled())) {
91                 _logscaled = global_logscaled();
92                 changed = true;
93         }
94
95         if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
96                 _gradient_depth = global_gradient_depth();
97                 changed = true;
98         }
99         
100         if (changed) {
101                 invalidate_image ();
102         }
103 }
104
105 void
106 WaveView::set_fill_color (Color c)
107 {
108         if (c != _fill_color) {
109                 invalidate_image ();
110                 Fill::set_fill_color (c);
111         }
112 }
113
114 void
115 WaveView::set_outline_color (Color c)
116 {
117         if (c != _outline_color) {
118                 invalidate_image ();
119                 Outline::set_outline_color (c);
120         }
121 }
122
123 void
124 WaveView::set_samples_per_pixel (double samples_per_pixel)
125 {
126         if (samples_per_pixel != _samples_per_pixel) {
127                 begin_change ();
128
129                 _samples_per_pixel = samples_per_pixel;
130                 
131                 _bounding_box_dirty = true;
132                 
133                 end_change ();
134                 
135                 invalidate_image ();
136         }
137 }
138
139 static inline double
140 image_to_window (double wave_origin, double image_start)
141 {
142         return wave_origin + image_start;
143 }
144
145 static inline double
146 window_to_image (double wave_origin, double image_start)
147 {
148         return image_start - wave_origin;
149 }
150
151 static inline float
152 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
153 {
154         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
155 }
156
157 static inline float
158 alt_log_meter (float power)
159 {
160         return _log_meter (power, -192.0, 0.0, 8.0);
161 }
162
163 struct LineTips {
164     double top;
165     double bot;
166     bool clipped;
167     
168     LineTips() : top (0.0), bot (0.0), clipped (false) {}
169 };
170
171 void
172 WaveView::draw_image (PeakData* _peaks, int n_peaks) const
173 {
174         _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, _height);
175
176         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
177
178         boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
179
180         if (_shape == WaveView::Rectified) {
181
182                 /* each peak is a line from the bottom of the waveview
183                  * to a point determined by max (_peaks[i].max,
184                  * _peaks[i].min)
185                  */
186
187                 if (_logscaled) {
188                         for (int i = 0; i < n_peaks; ++i) {
189                                 tips[i].bot = height();
190                                 tips[i].top = position (alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min)))));
191                         }
192                 } else {for (int i = 0; i < n_peaks; ++i) {
193                                 tips[i].bot = height();
194                                 tips[i].top = position (max (fabs (_peaks[i].max), fabs (_peaks[i].min)));
195                         }
196                 }
197
198         } else {
199
200                 if (_logscaled) {
201                         for (int i = 0; i < n_peaks; ++i) {
202                                 Coord top = _peaks[i].min;
203                                 Coord bot = _peaks[i].max;
204
205                                 if (top > 0.0) {
206                                         top = position (alt_log_meter (fast_coefficient_to_dB (top)));
207                                 } else if (top < 0.0) {
208                                         top = position (-alt_log_meter (fast_coefficient_to_dB (-top)));
209                                 } else {
210                                         top = position (0.0);
211                                 }
212
213                                 if (bot > 0.0) {
214                                         bot = position (alt_log_meter (fast_coefficient_to_dB (bot)));
215                                 } else if (bot < 0.0) {
216                                         bot = position (-alt_log_meter (fast_coefficient_to_dB (-bot)));
217                                 } else {
218                                         bot = position (0.0);
219                                 }
220
221                                 tips[i].top = top;
222                                 tips[i].bot = bot;
223
224                         } 
225
226                 } else {
227                         for (int i = 0; i < n_peaks; ++i) {
228                                 tips[i].top = position (_peaks[i].min);
229                                 tips[i].bot = position (_peaks[i].max);
230                         }
231                 }
232         }
233
234         if (gradient_depth() != 0.0) {
235                         
236                 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _height));
237                         
238                 double stops[3];
239                         
240                 double r, g, b, a;
241
242                 if (_shape == Rectified) {
243                         stops[0] = 0.1;
244                         stops[0] = 0.3;
245                         stops[0] = 0.9;
246                 } else {
247                         stops[0] = 0.1;
248                         stops[1] = 0.5;
249                         stops[2] = 0.9;
250                 }
251
252                 color_to_rgba (_fill_color, r, g, b, a);
253                 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
254                 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
255
256                 /* generate a new color for the middle of the gradient */
257                 double h, s, v;
258                 color_to_hsv (_fill_color, h, s, v);
259                 /* change v towards white */
260                 v *= 1.0 - gradient_depth();
261                 Color center = hsv_to_color (h, s, v, a);
262                 color_to_rgba (center, r, g, b, a);
263                 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
264                         
265                 context->set_source (gradient);
266         } else {
267                 set_source_rgba (context, _fill_color);
268         }
269
270         /* ensure single-pixel lines */
271                 
272         context->set_line_width (0.5);
273         context->translate (0.5, 0.0);
274
275         /* draw the lines */
276
277         if (_shape == WaveView::Rectified) {
278                 for (int i = 0; i < n_peaks; ++i) {
279                         context->move_to (i, tips[i].top); /* down 1 pixel */
280                         context->line_to (i, tips[i].bot);
281                         context->stroke ();
282                 }
283         } else {
284                 for (int i = 0; i < n_peaks; ++i) {
285                         context->move_to (i, tips[i].top);
286                         context->line_to (i, tips[i].bot);
287                         context->stroke ();
288                 }
289         }
290
291         /* now add dots to the top and bottom of each line (this is
292          * modelled on pyramix, except that we add clipping indicators.
293          */
294
295         context->set_source_rgba (0, 0, 0, 1.0);
296
297         for (int i = 0; i < n_peaks; ++i) {
298                 context->move_to (i, tips[i].top);
299                 context->rel_line_to (0, 1.0);
300                 context->stroke ();
301                 if (_shape != WaveView::Rectified) {
302                         context->move_to (i, tips[i].bot);
303                         context->rel_line_to (0, -1.0);
304                         context->stroke ();
305                 }
306         }
307
308         if (show_zero_line()) {
309                 set_source_rgba (context, _zero_color);
310                 context->move_to (0, position (0.0));
311                 context->line_to (n_peaks, position (0.0));
312                 context->stroke ();
313         }
314 }
315
316 void
317 WaveView::ensure_cache (framepos_t start, framepos_t end) const
318 {
319         if (_image && _sample_start <= start && _sample_end >= end) {
320                 /* cache already covers required range, do nothing */
321                 return;
322         }
323
324         /* sample position is canonical here, and we want to generate
325          * an image that spans about twice the canvas width 
326          */
327         
328         const framepos_t center = start + ((end - start) / 2);
329         const framecnt_t canvas_samples = 2 * (_canvas->visible_area().width() * _samples_per_pixel);
330
331         /* we can request data from anywhere in the Source, between 0 and its length
332          */
333
334         _sample_start = max ((framepos_t) 0, (center - canvas_samples));
335         _sample_end = min (center + canvas_samples, _region->source_length (0));
336
337         double pixel_start = floor (_sample_start / (double) _samples_per_pixel);
338         double pixel_end = ceil (_sample_end / (double) _samples_per_pixel);
339         int n_peaks = llrintf (pixel_end - pixel_start);
340
341         boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
342
343         _region->read_peaks (peaks.get(), n_peaks, 
344                              _sample_start, _sample_end - _sample_start,
345                              _channel, 
346                              _samples_per_pixel);
347
348         draw_image (peaks.get(), n_peaks);
349 }
350
351 void
352 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
353 {
354         assert (_samples_per_pixel != 0);
355
356         if (!_region) {
357                 return;
358         }
359
360         Rect self = item_to_window (Rect (0.0, 0.0, floor (_region->length() / _samples_per_pixel), _height));
361         boost::optional<Rect> d = self.intersection (area);
362
363         if (!d) {
364                 return;
365         }
366         
367         Rect draw = d.get();
368
369         /* window coordinates - pixels where x=0 is the left edge of the canvas
370          * window. We round up and down in case we were asked to
371          * draw "between" pixels at the start and/or end.
372          */
373
374         const double draw_start = floor (draw.x0);
375         const double draw_end = ceil (draw.x1);
376
377         // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
378         
379         /* image coordnates: pixels where x=0 is the start of this waveview,
380          * wherever it may be positioned. thus image_start=N means "an image
381          * that beings N pixels after the start of region that this waveview is
382          * representing. 
383          */
384
385         const framepos_t image_start = window_to_image (self.x0, draw_start);
386         const framepos_t image_end = window_to_image (self.x0, draw_end);
387
388         // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
389
390         /* sample coordinates - note, these are not subject to rounding error */
391         framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
392         framepos_t sample_end   = _region_start + (image_end * _samples_per_pixel);
393
394         // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
395
396         ensure_cache (sample_start, sample_end);
397
398         // cerr << "Cache contains " << _cache->pixel_start() << " .. " << _cache->pixel_end() << " / " 
399         // << _cache->sample_start() << " .. " << _cache->sample_end()
400         // << endl;
401
402         double image_offset = (_sample_start - _region->start()) / _samples_per_pixel;
403
404         // cerr << "Offset into image to place at zero: " << image_offset << endl;
405
406         context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
407
408         /* round image origin position to an exact pixel in device space to
409          * avoid blurring
410          */
411
412         double x  = self.x0 + image_offset;
413         double y  = self.y0;
414         context->user_to_device (x, y);
415         x = round (x);
416         y = round (y);
417         context->device_to_user (x, y);
418
419         context->set_source (_image, x, y);
420         context->fill ();
421 }
422
423 void
424 WaveView::compute_bounding_box () const
425 {
426         if (_region) {
427                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
428         } else {
429                 _bounding_box = boost::optional<Rect> ();
430         }
431         
432         _bounding_box_dirty = false;
433 }
434         
435 void
436 WaveView::set_height (Distance height)
437 {
438         if (height != _height) {
439                 begin_change ();
440                 
441                 _height = height;
442                 
443                 _bounding_box_dirty = true;
444                 end_change ();
445                 
446                 invalidate_image ();
447         }
448 }
449
450 void
451 WaveView::set_channel (int channel)
452 {
453         if (channel != _channel) {
454                 begin_change ();
455                 
456                 _channel = channel;
457                 
458                 _bounding_box_dirty = true;
459                 end_change ();
460                 
461                 invalidate_image ();
462         }
463 }
464
465 void
466 WaveView::invalidate_image ()
467 {
468         begin_visual_change ();
469
470         _image.clear ();
471         _sample_start  = -1;
472         _sample_end = -1;
473         
474         end_visual_change ();
475 }
476
477
478 void
479 WaveView::set_logscaled (bool yn)
480 {
481         if (_logscaled != yn) {
482                 _logscaled = yn;
483                 invalidate_image ();
484         }
485 }
486
487 void
488 WaveView::gain_changed ()
489 {
490         invalidate_image ();
491 }
492
493 void
494 WaveView::set_zero_color (Color c)
495 {
496         if (_zero_color != c) {
497                 _zero_color = c;
498                 invalidate_image ();
499         }
500 }
501
502 void
503 WaveView::set_clip_color (Color c)
504 {
505         if (_clip_color != c) {
506                 _clip_color = c;
507                 invalidate_image ();
508         }
509 }
510
511 void
512 WaveView::set_show_zero_line (bool yn)
513 {
514         if (_show_zero != yn) {
515                 _show_zero = yn;
516                 invalidate_image ();
517         }
518 }
519
520 void
521 WaveView::set_shape (Shape s)
522 {
523         if (_shape != s) {
524                 _shape = s;
525                 invalidate_image ();
526         }
527 }
528
529 void
530 WaveView::set_amplitude_above_axis (double a)
531 {
532         if (_amplitude_above_axis != a) {
533                 _amplitude_above_axis = a;
534                 invalidate_image ();
535         }
536 }
537
538 void
539 WaveView::set_global_shape (Shape s)
540 {
541         if (_global_shape != s) {
542                 _global_shape = s;
543                 VisualPropertiesChanged (); /* EMIT SIGNAL */
544         }
545 }
546
547 void
548 WaveView::set_global_logscaled (bool yn)
549 {
550         if (_global_logscaled != yn) {
551                 _global_logscaled = yn;
552                 VisualPropertiesChanged (); /* EMIT SIGNAL */
553         }
554 }
555
556 void
557 WaveView::region_resized ()
558 {
559         if (!_region) {
560                 return;
561         }
562
563         /* special: do not use _region->length() here to compute
564            bounding box because it will already have changed.
565            
566            if we have a bounding box, use it.
567         */
568
569         _pre_change_bounding_box = _bounding_box;
570
571         _bounding_box_dirty = true;
572         compute_bounding_box ();
573
574         end_change ();
575 }
576
577 Coord
578 WaveView::position (double s) const
579 {
580         /* it is important that this returns an integral value, so that we 
581            can ensure correct single pixel behaviour.
582          */
583
584         switch (_shape) {
585         case Rectified:
586                 return floor (_height - (s * _height));
587         default:
588                 break;
589         }
590         return floor ((1.0-s) * (_height / 2.0));
591 }
592
593 void
594 WaveView::set_global_gradient_depth (double depth)
595 {
596         if (_global_gradient_depth != depth) {
597                 _global_gradient_depth = depth;
598                 VisualPropertiesChanged (); /* EMIT SIGNAL */
599         }
600 }