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())
72 VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
75 WaveView::~WaveView ()
82 WaveView::handle_visual_property_change ()
86 if (!_shape_independent && (_shape != global_shape())) {
87 _shape = global_shape();
91 if (!_logscaled_independent && (_logscaled != global_logscaled())) {
92 _logscaled = global_logscaled();
96 if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
97 _gradient_depth = global_gradient_depth();
102 invalidate_image_cache ();
107 WaveView::set_fill_color (Color c)
109 if (c != _fill_color) {
110 invalidate_image_cache ();
111 Fill::set_fill_color (c);
116 WaveView::set_outline_color (Color c)
118 if (c != _outline_color) {
119 invalidate_image_cache ();
120 Outline::set_outline_color (c);
125 WaveView::set_samples_per_pixel (double samples_per_pixel)
127 if (samples_per_pixel != _samples_per_pixel) {
130 _samples_per_pixel = samples_per_pixel;
132 _bounding_box_dirty = true;
136 invalidate_whole_cache ();
141 image_to_window (double wave_origin, double image_start)
143 return wave_origin + image_start;
147 window_to_image (double wave_origin, double image_start)
149 return image_start - wave_origin;
153 WaveView::ensure_cache (framecnt_t start, framecnt_t end,
154 framepos_t sample_start, framepos_t sample_end) const
156 if (_cache && _cache->sample_start() <= sample_start && _cache->sample_end() >= sample_end) {
157 /* cache already covers required range, do nothing */
166 /* sample position is canonical here, and we want to generate
167 * an image that spans about twice the canvas width
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);
173 /* we can request data from anywhere in the Source, between 0 and its length
176 sample_start = max ((framepos_t) 0, (center - canvas_samples));
177 sample_end = min (center + canvas_samples, _region->source_length (0));
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;
190 start = floor (sample_start / (double) _samples_per_pixel);
191 end = ceil (sample_end / (double) _samples_per_pixel);
193 assert (end > start);
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);
200 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
202 assert (_samples_per_pixel != 0);
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);
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.
222 const double draw_start = floor (draw.x0);
223 const double draw_end = ceil (draw.x1);
225 // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
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
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);
236 // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
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);
242 // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
244 ensure_cache (image_start, image_end, sample_start, sample_end);
246 // cerr << "Cache contains " << _cache->pixel_start() << " .. " << _cache->pixel_end() << " / "
247 // << _cache->sample_start() << " .. " << _cache->sample_end()
250 double image_offset = (_cache->sample_start() - _region->start()) / _samples_per_pixel;
252 // cerr << "Offset into image to place at zero: " << image_offset << endl;
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);
260 WaveView::compute_bounding_box () const
263 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
265 _bounding_box = boost::optional<Rect> ();
268 _bounding_box_dirty = false;
272 WaveView::set_height (Distance height)
274 if (height != _height) {
279 _bounding_box_dirty = true;
282 invalidate_image_cache ();
287 WaveView::set_channel (int channel)
289 if (channel != _channel) {
294 _bounding_box_dirty = true;
297 invalidate_whole_cache ();
302 WaveView::invalidate_whole_cache ()
304 begin_visual_change ();
307 end_visual_change ();
311 WaveView::invalidate_image_cache ()
313 invalidate_whole_cache ();
317 WaveView::set_logscaled (bool yn)
319 if (_logscaled != yn) {
321 invalidate_image_cache ();
326 WaveView::gain_changed ()
328 invalidate_whole_cache ();
332 WaveView::set_zero_color (Color c)
334 if (_zero_color != c) {
336 invalidate_image_cache ();
341 WaveView::set_clip_color (Color c)
343 if (_clip_color != c) {
345 invalidate_image_cache ();
350 WaveView::set_show_zero_line (bool yn)
352 if (_show_zero != yn) {
354 invalidate_image_cache ();
359 WaveView::set_shape (Shape s)
363 invalidate_image_cache ();
368 WaveView::set_amplitude_above_axis (double a)
370 if (_amplitude_above_axis != a) {
371 _amplitude_above_axis = a;
372 invalidate_image_cache ();
377 WaveView::set_global_shape (Shape s)
379 if (_global_shape != s) {
381 VisualPropertiesChanged (); /* EMIT SIGNAL */
386 WaveView::set_global_logscaled (bool yn)
388 if (_global_logscaled != yn) {
389 _global_logscaled = yn;
390 VisualPropertiesChanged (); /* EMIT SIGNAL */
396 WaveView::region_resized ()
402 /* special: do not use _region->length() here to compute
403 bounding box because it will already have changed.
405 if we have a bounding box, use it.
408 _pre_change_bounding_box = _bounding_box;
410 frameoffset_t s = _region->start();
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
418 _region_start = _region->start();
419 invalidate_whole_cache ();
422 _bounding_box_dirty = true;
423 compute_bounding_box ();
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)
437 _peaks.reset (new PeakData[_n_peaks]);
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);
445 WaveView::CacheEntry::~CacheEntry ()
450 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
452 return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
456 alt_log_meter (float power)
458 return _log_meter (power, -192.0, 0.0, 8.0);
461 Cairo::RefPtr<Cairo::ImageSurface>
462 WaveView::CacheEntry::image ()
466 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
467 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
469 #ifdef AREA_DRAW_AND_FILL
471 /* Draw the edge of the waveform, top half first, the loop back
472 * for the bottom half to create a clockwise path
475 context->begin_new_path();
477 if (_wave_view->_shape == WaveView::Rectified) {
479 /* top edge of waveform is based on max (fabs (peak_min, peak_max))
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))))));
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))));
494 if (_wave_view->_logscaled) {
495 for (int i = 0; i < _n_peaks; ++i) {
496 Coord y = _peaks[i].max;
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)));
505 context->line_to (i + 0.5, y);
508 for (int i = 0; i < _n_peaks; ++i) {
509 context->line_to (i + 0.5, position (_peaks[i].max));
514 /* from final top point, move out of the clip zone */
516 context->line_to (_n_peaks + 10, position (0.0));
518 /* bottom half, in reverse */
520 if (_wave_view->_shape == WaveView::Rectified) {
522 /* lower half: drop to the bottom, then a line back to
523 * beyond the left edge of the clip region
526 context->line_to (_n_peaks + 10, _wave_view->_height);
527 context->line_to (-10.0, _wave_view->_height);
531 if (_wave_view->_logscaled) {
532 for (int i = _n_peaks-1; i >= 0; --i) {
533 Coord y = _peaks[i].min;
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))));
539 context->line_to (i + 0.5, position (0.0));
543 for (int i = _n_peaks-1; i >= 0; --i) {
544 context->line_to (i + 0.5, position (_peaks[i].min));
548 /* from final bottom point, move out of the clip zone */
550 context->line_to (-10.0, position (0.0));
553 context->close_path ();
555 if (_wave_view->gradient_depth() != 0.0) {
557 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
563 if (_wave_view->_shape == Rectified) {
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);
577 /* generate a new color for the middle of the gradient */
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);
586 context->set_source (gradient);
588 set_source_rgba (context, _wave_view->_fill_color);
591 context->fill_preserve ();
592 _wave_view->setup_outline_context (context);
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));
603 set_source_rgba (context, _wave_view->_fill_color);
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);
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);
617 context->rectangle (i + 0.5, ceil (position (_peaks[i].max)), 0.5, 0.5);
628 WaveView::CacheEntry::position (double s) const
630 switch (_wave_view->_shape) {
632 return _wave_view->_height - (s * _wave_view->_height);
636 return (1.0-s) * (_wave_view->_height / 2.0);
640 WaveView::CacheEntry::clear_image ()
646 WaveView::set_global_gradient_depth (double depth)
648 if (_global_gradient_depth != depth) {
649 _global_gradient_depth = depth;
650 VisualPropertiesChanged (); /* EMIT SIGNAL */