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, _region->length() / _samples_per_pixel, _height));
155 boost::optional<Rect> draw = self.intersection (area);
157 context->rectangle (self.x0, self.y0, self.width(), self.height());
158 context->set_source_rgb (1.0, 0.0, 0.0);
165 /* pixel coordinates */
167 double start = draw->x0;
168 double const end = draw->x1;
170 list<CacheEntry*>::iterator cache = _cache.begin ();
172 cerr << name << " draw " << area << "self = " << self << "\n\twill use " << draw.get() << endl;
174 cerr << " Cache contains " << _cache.size() << endl;
175 while (cache != _cache.end()) {
176 cerr << "\tsample span " << (*cache)->start() << " .. " << (*cache)->end()
178 << to_pixel_offset (_region_start, (*cache)->start(), _samples_per_pixel)
180 << to_pixel_offset (_region_start, (*cache)->end(), _samples_per_pixel)
184 cache = _cache.begin();
187 while ((end - start) > 1.0) {
189 cerr << "***** RANGE = " << start << " .. " << end << " = " << end - start << endl;
191 frameoffset_t start_sample_offset = to_src_sample_offset (_region_start, start, _samples_per_pixel);
193 /* Step through cache entries that end at or before our current position */
195 while (cache != _cache.end() && (*cache)->end() <= start_sample_offset) {
200 1. we have run out of cache entries
201 2. the one we are looking at finishes after start(_sample_offset) but also starts after start(_sample_offset).
202 3. the one we are looking at finishes after start(_sample_offset) and starts before start(_sample_offset).
204 Set up a pointer to the cache entry that we will use on this iteration.
207 CacheEntry* image = 0;
209 if (cache == _cache.end ()) {
211 cerr << "Nothing in cache spans\n";
213 /* Case 1: we have run out of cache entries, so make a new one for
214 the whole required area and put it in the list.
216 We would like to avoid lots of little images in the
217 cache, so when we create a new one, make it as wide
218 as possible, within a sensible limit (here, the
219 visible width of the canvas we're on).
221 However, we don't want to try to make it larger than
222 the region actually is, so clamp with that too.
225 double const rend = _region->length() / _samples_per_pixel;
226 double const endpoint = min (rend, max (end, start + _canvas->visible_area().width()));
228 cerr << "New cache entry for " << start_sample_offset << " .. " << to_src_sample_offset (_region_start, endpoint, _samples_per_pixel)
231 CacheEntry* c = new CacheEntry (this,
233 to_src_sample_offset (_region_start, endpoint, _samples_per_pixel),
235 _cache.push_back (c);
238 } else if ((*cache)->start() > start_sample_offset) {
240 /* Case 2: we have a cache entry, but it starts after
241 * start(_sample_offset), so we need another one for
244 * Create a new cached image that extends as far as the
245 * next cached image's start, or the end of the region,
246 * or the end of the render area, whichever comes first.
250 double end_sample_offset;
253 if (end_sample_offset < (*cache)->start()) {
254 double const rend = _region->length() / _samples_per_pixel;
255 end_sample_offset = to_src_sample_offset (_region_start, end_pixel, _samples_per_pixel);
256 end_pixel = min (rend, end);
258 end_sample_offset = (*cache)->start();
259 end_pixel = to_pixel_offset (_region_start, end_sample_offset, _samples_per_pixel);
262 npeaks = end_pixel - start;
265 cerr << "New fill-in cache entry for " << start_sample_offset << " .. " << end_sample_offset << endl;
267 CacheEntry* c = new CacheEntry (this,
272 cache = _cache.insert (cache, c);
278 /* Case 3: we have a cache entry that will do at least some of what
279 we have left, so render it.
282 cerr << "found suitable cache entry\n";
291 double this_end = min (end, to_pixel_offset (_region_start, image->end (), _samples_per_pixel));
292 double const image_origin = to_pixel_offset (_region_start, image->start(), _samples_per_pixel);
294 cerr << "\t\tDraw image between "
298 << " using image spanning "
303 << to_pixel_offset (_region_start, image->start(), _samples_per_pixel)
305 << to_pixel_offset (_region_start, image->end(), _samples_per_pixel)
309 cerr << "Fill rect " << draw->x0 << ", " << self.y0 << ' ' << draw->width() << " x " << draw->height() << endl;
311 context->rectangle (start, draw->y0, this_end - start, _height);
312 // context->set_source (image->image(), image_origin, self.y0 - draw->y0);
313 context->set_source_rgb (0.0, 0.0, 1.0);
321 WaveView::compute_bounding_box () const
324 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
326 _bounding_box = boost::optional<Rect> ();
329 _bounding_box_dirty = false;
333 WaveView::set_height (Distance height)
335 if (height != _height) {
340 _bounding_box_dirty = true;
343 invalidate_image_cache ();
348 WaveView::set_channel (int channel)
350 if (channel != _channel) {
355 _bounding_box_dirty = true;
358 invalidate_whole_cache ();
363 WaveView::invalidate_whole_cache ()
365 begin_visual_change ();
367 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
373 end_visual_change ();
377 WaveView::invalidate_image_cache ()
379 begin_visual_change ();
381 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
382 (*i)->clear_image ();
385 end_visual_change ();
389 WaveView::set_logscaled (bool yn)
391 if (_logscaled != yn) {
393 invalidate_image_cache ();
398 WaveView::gain_changed ()
400 invalidate_whole_cache ();
404 WaveView::set_zero_color (Color c)
406 if (_zero_color != c) {
408 invalidate_image_cache ();
413 WaveView::set_clip_color (Color c)
415 if (_clip_color != c) {
417 invalidate_image_cache ();
422 WaveView::set_show_zero_line (bool yn)
424 if (_show_zero != yn) {
426 invalidate_image_cache ();
431 WaveView::set_shape (Shape s)
435 invalidate_image_cache ();
440 WaveView::set_amplitude_above_axis (double a)
442 if (_amplitude_above_axis != a) {
443 _amplitude_above_axis = a;
444 invalidate_image_cache ();
449 WaveView::set_global_shape (Shape s)
451 if (_global_shape != s) {
453 VisualPropertiesChanged (); /* EMIT SIGNAL */
458 WaveView::set_global_logscaled (bool yn)
460 if (_global_logscaled != yn) {
461 _global_logscaled = yn;
462 VisualPropertiesChanged (); /* EMIT SIGNAL */
468 WaveView::region_resized ()
474 /* special: do not use _region->length() here to compute
475 bounding box because it will already have changed.
477 if we have a bounding box, use it.
480 _pre_change_bounding_box = _bounding_box;
482 frameoffset_t s = _region->start();
484 if (s != _region_start) {
485 /* if the region start changes, the information we have
486 in the image cache is out of date and not useful
487 since it will fragmented into little pieces. invalidate
490 _region_start = _region->start();
491 invalidate_whole_cache ();
494 _bounding_box_dirty = true;
495 compute_bounding_box ();
500 WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double start, double end, int npeaks)
501 : _wave_view (wave_view)
506 _peaks.reset (new PeakData[_n_peaks]);
508 _wave_view->_region->read_peaks (_peaks.get(), _n_peaks,
509 (framecnt_t) floor (_start),
510 (framecnt_t) ceil (_end - _start),
511 _wave_view->_channel, _wave_view->_samples_per_pixel);
514 WaveView::CacheEntry::~CacheEntry ()
519 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
521 return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
525 alt_log_meter (float power)
527 return _log_meter (power, -192.0, 0.0, 8.0);
530 Cairo::RefPtr<Cairo::ImageSurface>
531 WaveView::CacheEntry::image ()
535 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
536 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
538 /* Draw the edge of the waveform, top half first, the loop back
539 * for the bottom half to create a clockwise path
542 context->begin_new_path();
544 if (_wave_view->_shape == WaveView::Rectified) {
546 /* top edge of waveform is based on max (fabs (peak_min, peak_max))
549 if (_wave_view->_logscaled) {
550 for (int i = 0; i < _n_peaks; ++i) {
551 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (
552 max (fabs (_peaks[i].max), fabs (_peaks[i].min))))));
555 for (int i = 0; i < _n_peaks; ++i) {
556 context->line_to (i + 0.5, position (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
561 if (_wave_view->_logscaled) {
562 for (int i = 0; i < _n_peaks; ++i) {
563 Coord y = _peaks[i].max;
566 y = position (alt_log_meter (fast_coefficient_to_dB (y)));
567 } else if (y < 0.0) {
568 y = position (-alt_log_meter (fast_coefficient_to_dB (-y)));
572 context->line_to (i + 0.5, y);
575 for (int i = 0; i < _n_peaks; ++i) {
576 context->line_to (i + 0.5, position (_peaks[i].max));
581 /* from final top point, move out of the clip zone */
583 context->line_to (_n_peaks + 10, position (0.0));
585 /* bottom half, in reverse */
587 if (_wave_view->_shape == WaveView::Rectified) {
589 /* lower half: drop to the bottom, then a line back to
590 * beyond the left edge of the clip region
593 context->line_to (_n_peaks + 10, _wave_view->_height);
594 context->line_to (-10.0, _wave_view->_height);
598 if (_wave_view->_logscaled) {
599 for (int i = _n_peaks-1; i >= 0; --i) {
600 Coord y = _peaks[i].min;
602 context->line_to (i + 0.5, position (alt_log_meter (fast_coefficient_to_dB (y))));
603 } else if (y < 0.0) {
604 context->line_to (i + 0.5, position (-alt_log_meter (fast_coefficient_to_dB (-y))));
606 context->line_to (i + 0.5, position (0.0));
610 for (int i = _n_peaks-1; i >= 0; --i) {
611 context->line_to (i + 0.5, position (_peaks[i].min));
615 /* from final bottom point, move out of the clip zone */
617 context->line_to (-10.0, position (0.0));
620 context->close_path ();
622 if (_wave_view->gradient_depth() != 0.0) {
624 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
630 if (_wave_view->_shape == Rectified) {
640 color_to_rgba (_wave_view->_fill_color, r, g, b, a);
641 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
642 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
644 /* generate a new color for the middle of the gradient */
646 color_to_hsv (_wave_view->_fill_color, h, s, v);
647 /* tone down the saturation */
648 s *= 1.0 - _wave_view->gradient_depth();
649 Color center = hsv_to_color (h, s, v, a);
650 color_to_rgba (center, r, g, b, a);
651 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
653 context->set_source (gradient);
655 set_source_rgba (context, _wave_view->_fill_color);
658 context->fill_preserve ();
659 _wave_view->setup_outline_context (context);
662 if (_wave_view->show_zero_line()) {
663 set_source_rgba (context, _wave_view->_zero_color);
664 context->move_to (0, position (0.0));
665 context->line_to (_n_peaks, position (0.0));
675 WaveView::CacheEntry::position (double s) const
677 switch (_wave_view->_shape) {
679 return _wave_view->_height - (s * _wave_view->_height);
683 return (1.0-s) * (_wave_view->_height / 2.0);
687 WaveView::CacheEntry::clear_image ()
693 WaveView::set_global_gradient_depth (double depth)
695 if (_global_gradient_depth != depth) {
696 _global_gradient_depth = depth;
697 VisualPropertiesChanged (); /* EMIT SIGNAL */