2 Copyright (C) 2011-2013 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
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.
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.
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.
22 #include <cairomm/cairomm.h>
24 #include "gtkmm2ext/utils.h"
26 #include "pbd/compose.h"
27 #include "pbd/signals.h"
28 #include "pbd/stacktrace.h"
30 #include "ardour/types.h"
31 #include "ardour/dB.h"
32 #include "ardour/audioregion.h"
34 #include "canvas/wave_view.h"
35 #include "canvas/utils.h"
36 #include "canvas/canvas.h"
38 #include <gdkmm/general.h>
41 using namespace ARDOUR;
42 using namespace ArdourCanvas;
44 double WaveView::_global_gradient_depth = 0.6;
45 bool WaveView::_global_logscaled = false;
46 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
48 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
50 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
56 , _samples_per_pixel (0)
58 , _wave_color (0xffffffff)
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())
71 VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
75 WaveView::handle_visual_property_change ()
79 if (!_shape_independent && (_shape != global_shape())) {
80 _shape = global_shape();
84 if (!_logscaled_independent && (_logscaled != global_logscaled())) {
85 _logscaled = global_logscaled();
89 if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
90 _gradient_depth = global_gradient_depth();
95 invalidate_image_cache ();
100 WaveView::set_fill_color (Color c)
102 if (c != _fill_color) {
103 invalidate_image_cache ();
104 Fill::set_fill_color (c);
109 WaveView::set_outline_color (Color c)
111 if (c != _outline_color) {
112 invalidate_image_cache ();
113 Outline::set_outline_color (c);
118 WaveView::set_samples_per_pixel (double samples_per_pixel)
120 if (samples_per_pixel != _samples_per_pixel) {
123 _samples_per_pixel = samples_per_pixel;
125 _bounding_box_dirty = true;
129 invalidate_whole_cache ();
134 to_src_sample_offset (frameoffset_t src_sample_start, double pixel_offset, double spp)
136 return llrintf (src_sample_start + (pixel_offset * spp));
140 to_pixel_offset (frameoffset_t src_sample_start, double sample_offset, double spp)
142 return llrintf ((sample_offset - src_sample_start) / spp);
146 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
148 assert (_samples_per_pixel != 0);
154 Rect self = item_to_window (Rect (0.0, 0.0, floor (_region->length() / _samples_per_pixel), _height));
155 boost::optional<Rect> d = self.intersection (area);
163 /* pixel coordinates */
165 double start = floor (draw.x0);
166 double const end = ceil (draw.x1);
168 list<CacheEntry*>::iterator cache = _cache.begin ();
170 cache = _cache.begin ();
172 while ((end - start) > 1.0) {
174 /* Step through cache entries that end at or before our current position */
176 for (; cache != _cache.end(); ++cache) {
177 if ((*cache)->pixel_start() <= start) {
184 1. we have run out of cache entries
186 2. we have found a cache entry that starts after start
187 create a new cache entry to "fill in" before the one we have found.
189 3. we have found a cache entry that starts at or before
190 start, but finishes before end: create a new cache entry
191 to extend the cache further along the timeline.
193 Set up a pointer to the cache entry that we will use on this iteration.
196 CacheEntry* image = 0;
197 const double BIG_IMAGE_SIZE = 32767.0;
199 if (cache == _cache.end ()) {
201 /* Case 1: we have run out of cache entries, so make a new one for
202 the whole required area and put it in the list.
204 We would like to avoid lots of little images in the
205 cache, so when we create a new one, make it as wide
206 as possible, within the limits inherent in Cairo.
208 However, we don't want to try to make it larger than
209 the region actually is, so clamp with that too.
212 double const rend = floor (_region->length() / _samples_per_pixel);
213 double const end_pixel = min (rend, max (end, BIG_IMAGE_SIZE));
215 if ((end_pixel - start) < 1.0) {
216 /* nothing more to draw */
220 cerr << "Create new cache entry to grow cache,"
221 << " range is " << start << " .. " << end_pixel
224 CacheEntry* c = new CacheEntry (this, start, end_pixel);
226 _cache.push_back (c);
229 } else if ((*cache)->pixel_start() > start) {
231 /* Case 2: we have a cache entry, but it begins after
232 * start, so we need another one for the missing section.
234 * Create a new cached image that extends as far as the
235 * next cached image's start, or the end of the region,
236 * or the end of a BIG_IMAGE, whichever comes first.
241 if (end < (*cache)->pixel_start()) {
242 double const rend = floor (_region->length() / _samples_per_pixel);
243 end_pixel = min (rend, max (end, BIG_IMAGE_SIZE));
245 end_pixel = (*cache)->pixel_start();
248 cerr << "Create new cache entry to reach " << (*cache)->pixel_start()
249 << " range is " << start << " .. " << end_pixel
252 CacheEntry* c = new CacheEntry (this, start, end_pixel);
254 cache = _cache.insert (cache, c);
260 /* Case 3: we have a cache entry that will do at least some of what
261 we have left, so render it.
268 double this_end = min (end, image->pixel_end ());
269 double const image_origin = image->pixel_start ();
271 cerr << "\t\tDraw image between "
275 << " using image spanning "
276 << image->pixel_start()
278 << image->pixel_end ()
282 context->rectangle (start, draw.y0, this_end - start, _height);
283 context->set_source (image->image(), self.x0 - image_origin, self.y0);
292 WaveView::compute_bounding_box () const
295 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
297 _bounding_box = boost::optional<Rect> ();
300 _bounding_box_dirty = false;
304 WaveView::set_height (Distance height)
306 if (height != _height) {
311 _bounding_box_dirty = true;
314 invalidate_image_cache ();
319 WaveView::set_channel (int channel)
321 if (channel != _channel) {
326 _bounding_box_dirty = true;
329 invalidate_whole_cache ();
334 WaveView::invalidate_whole_cache ()
336 begin_visual_change ();
338 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
344 end_visual_change ();
348 WaveView::invalidate_image_cache ()
350 begin_visual_change ();
352 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
353 (*i)->clear_image ();
356 end_visual_change ();
360 WaveView::set_logscaled (bool yn)
362 if (_logscaled != yn) {
364 invalidate_image_cache ();
369 WaveView::gain_changed ()
371 invalidate_whole_cache ();
375 WaveView::set_zero_color (Color c)
377 if (_zero_color != c) {
379 invalidate_image_cache ();
384 WaveView::set_clip_color (Color c)
386 if (_clip_color != c) {
388 invalidate_image_cache ();
393 WaveView::set_show_zero_line (bool yn)
395 if (_show_zero != yn) {
397 invalidate_image_cache ();
402 WaveView::set_shape (Shape s)
406 invalidate_image_cache ();
411 WaveView::set_amplitude_above_axis (double a)
413 if (_amplitude_above_axis != a) {
414 _amplitude_above_axis = a;
415 invalidate_image_cache ();
420 WaveView::set_global_shape (Shape s)
422 if (_global_shape != s) {
424 VisualPropertiesChanged (); /* EMIT SIGNAL */
429 WaveView::set_global_logscaled (bool yn)
431 if (_global_logscaled != yn) {
432 _global_logscaled = yn;
433 VisualPropertiesChanged (); /* EMIT SIGNAL */
439 WaveView::region_resized ()
445 /* special: do not use _region->length() here to compute
446 bounding box because it will already have changed.
448 if we have a bounding box, use it.
451 _pre_change_bounding_box = _bounding_box;
453 frameoffset_t s = _region->start();
455 if (s != _region_start) {
456 /* if the region start changes, the information we have
457 in the image cache is out of date and not useful
458 since it will fragmented into little pieces. invalidate
461 _region_start = _region->start();
462 invalidate_whole_cache ();
465 _bounding_box_dirty = true;
466 compute_bounding_box ();
471 WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end)
472 : _wave_view (wave_view)
473 , _pixel_start (pixel_start)
474 , _pixel_end (pixel_end)
475 , _n_peaks (_pixel_end - _pixel_start)
477 _peaks.reset (new PeakData[_n_peaks]);
479 _sample_start = pixel_start * _wave_view->_samples_per_pixel;
480 _sample_end = pixel_end * _wave_view->_samples_per_pixel;
482 _wave_view->_region->read_peaks (_peaks.get(), _n_peaks,
483 _sample_start, _sample_end,
484 _wave_view->_channel,
485 _wave_view->_samples_per_pixel);
488 WaveView::CacheEntry::~CacheEntry ()
493 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
495 return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
499 alt_log_meter (float power)
501 return _log_meter (power, -192.0, 0.0, 8.0);
504 Cairo::RefPtr<Cairo::ImageSurface>
505 WaveView::CacheEntry::image ()
509 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
510 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
512 /* Draw the edge of the waveform, top half first, the loop back
513 * for the bottom half to create a clockwise path
516 context->begin_new_path();
518 if (_wave_view->_shape == WaveView::Rectified) {
520 /* top edge of waveform is based on max (fabs (peak_min, peak_max))
523 if (_wave_view->_logscaled) {
524 for (int i = 0; i < _n_peaks; ++i) {
525 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (
526 max (fabs (_peaks[i].max), fabs (_peaks[i].min))))));
529 for (int i = 0; i < _n_peaks; ++i) {
530 context->line_to (i + 0.5, position (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
535 if (_wave_view->_logscaled) {
536 for (int i = 0; i < _n_peaks; ++i) {
537 Coord y = _peaks[i].max;
540 y = position (alt_log_meter (fast_coefficient_to_dB (y)));
541 } else if (y < 0.0) {
542 y = position (-alt_log_meter (fast_coefficient_to_dB (-y)));
546 context->line_to (i + 0.5, y);
549 for (int i = 0; i < _n_peaks; ++i) {
550 context->line_to (i + 0.5, position (_peaks[i].max));
555 /* from final top point, move out of the clip zone */
557 context->line_to (_n_peaks + 10, position (0.0));
559 /* bottom half, in reverse */
561 if (_wave_view->_shape == WaveView::Rectified) {
563 /* lower half: drop to the bottom, then a line back to
564 * beyond the left edge of the clip region
567 context->line_to (_n_peaks + 10, _wave_view->_height);
568 context->line_to (-10.0, _wave_view->_height);
572 if (_wave_view->_logscaled) {
573 for (int i = _n_peaks-1; i >= 0; --i) {
574 Coord y = _peaks[i].min;
576 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (y))));
577 } else if (y < 0.0) {
578 context->line_to (i + 0.5, position (-alt_log_meter (fast_coefficient_to_dB (-y))));
580 context->line_to (i + 0.5, position (0.0));
584 for (int i = _n_peaks-1; i >= 0; --i) {
585 context->line_to (i + 0.5, position (_peaks[i].min));
589 /* from final bottom point, move out of the clip zone */
591 context->line_to (-10.0, position (0.0));
594 context->close_path ();
596 if (_wave_view->gradient_depth() != 0.0) {
598 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
604 if (_wave_view->_shape == Rectified) {
614 color_to_rgba (_wave_view->_fill_color, r, g, b, a);
615 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
616 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
618 /* generate a new color for the middle of the gradient */
620 color_to_hsv (_wave_view->_fill_color, h, s, v);
621 /* tone down the saturation */
622 s *= 1.0 - _wave_view->gradient_depth();
623 Color center = hsv_to_color (h, s, v, a);
624 color_to_rgba (center, r, g, b, a);
625 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
627 context->set_source (gradient);
629 set_source_rgba (context, _wave_view->_fill_color);
632 context->fill_preserve ();
633 _wave_view->setup_outline_context (context);
636 if (_wave_view->show_zero_line()) {
637 set_source_rgba (context, _wave_view->_zero_color);
638 context->move_to (0, position (0.0));
639 context->line_to (_n_peaks, position (0.0));
649 WaveView::CacheEntry::position (double s) const
651 switch (_wave_view->_shape) {
653 return _wave_view->_height - (s * _wave_view->_height);
657 return (1.0-s) * (_wave_view->_height / 2.0);
661 WaveView::CacheEntry::clear_image ()
667 WaveView::set_global_gradient_depth (double depth)
669 if (_global_gradient_depth != depth) {
670 _global_gradient_depth = depth;
671 VisualPropertiesChanged (); /* EMIT SIGNAL */