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