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/lmath.h"
33 #include "ardour/audioregion.h"
35 #include "canvas/wave_view.h"
36 #include "canvas/utils.h"
37 #include "canvas/canvas.h"
38 #include "canvas/colors.h"
40 #include <gdkmm/general.h>
42 #include "gtkmm2ext/gui_thread.h"
45 using namespace ARDOUR;
46 using namespace ArdourCanvas;
48 #define CACHE_HIGH_WATER (2)
50 std::map <boost::shared_ptr<AudioSource>, std::vector<WaveView::CacheEntry> > WaveView::_image_cache;
51 double WaveView::_global_gradient_depth = 0.6;
52 bool WaveView::_global_logscaled = false;
53 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
54 bool WaveView::_global_show_waveform_clipping = true;
55 double WaveView::_clip_level = 0.98853;
57 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
58 PBD::Signal0<void> WaveView::ClipLevelChanged;
60 WaveView::WaveView (Canvas* c, boost::shared_ptr<ARDOUR::AudioRegion> region)
64 , _samples_per_pixel (0)
67 , _zero_color (0xff0000ff)
68 , _clip_color (0xff0000ff)
69 , _logscaled (_global_logscaled)
70 , _shape (_global_shape)
71 , _gradient_depth (_global_gradient_depth)
72 , _shape_independent (false)
73 , _logscaled_independent (false)
74 , _gradient_depth_independent (false)
75 , _amplitude_above_axis (1.0)
76 , _region_amplitude (_region->scale_amplitude ())
78 , _region_start (region->start())
80 _region->DropReferences.connect (_source_invalidated_connection, MISSING_INVALIDATOR,
81 boost::bind (&ArdourCanvas::WaveView::invalidate_source,
82 this, boost::weak_ptr<AudioSource>(_region->audio_source())), gui_context());
84 VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
85 ClipLevelChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_clip_level_change, this));
88 WaveView::WaveView (Item* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
92 , _samples_per_pixel (0)
95 , _zero_color (0xff0000ff)
96 , _clip_color (0xff0000ff)
97 , _logscaled (_global_logscaled)
98 , _shape (_global_shape)
99 , _gradient_depth (_global_gradient_depth)
100 , _shape_independent (false)
101 , _logscaled_independent (false)
102 , _gradient_depth_independent (false)
103 , _amplitude_above_axis (1.0)
104 , _region_amplitude (_region->scale_amplitude ())
105 , _region_start (region->start())
107 _region->DropReferences.connect (_source_invalidated_connection, MISSING_INVALIDATOR,
108 boost::bind (&ArdourCanvas::WaveView::invalidate_source,
109 this, boost::weak_ptr<AudioSource>(_region->audio_source())), gui_context());
111 VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
112 ClipLevelChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_clip_level_change, this));
115 WaveView::~WaveView ()
117 _source_invalidated_connection.disconnect();
118 invalidate_image_cache ();
122 WaveView::handle_visual_property_change ()
124 bool changed = false;
126 if (!_shape_independent && (_shape != global_shape())) {
127 _shape = global_shape();
131 if (!_logscaled_independent && (_logscaled != global_logscaled())) {
132 _logscaled = global_logscaled();
136 if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
137 _gradient_depth = global_gradient_depth();
142 begin_visual_change ();
143 invalidate_image_cache ();
144 end_visual_change ();
149 WaveView::handle_clip_level_change ()
151 begin_visual_change ();
152 invalidate_image_cache ();
153 end_visual_change ();
157 WaveView::set_fill_color (Color c)
159 if (c != _fill_color) {
160 begin_visual_change ();
161 invalidate_image_cache ();
162 Fill::set_fill_color (c);
163 end_visual_change ();
168 WaveView::set_outline_color (Color c)
170 if (c != _outline_color) {
171 begin_visual_change ();
172 invalidate_image_cache ();
173 Outline::set_outline_color (c);
174 end_visual_change ();
179 WaveView::set_samples_per_pixel (double samples_per_pixel)
181 if (samples_per_pixel != _samples_per_pixel) {
184 invalidate_image_cache ();
185 _samples_per_pixel = samples_per_pixel;
186 _bounding_box_dirty = true;
193 window_to_image (double wave_origin, double image_start)
195 return image_start - wave_origin;
199 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
201 return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
205 alt_log_meter (float power)
207 return _log_meter (power, -192.0, 0.0, 8.0);
211 WaveView::set_clip_level (double dB)
213 const double clip_level = dB_to_coefficient (dB);
214 if (clip_level != _clip_level) {
215 _clip_level = clip_level;
221 WaveView::invalidate_source (boost::weak_ptr<AudioSource> src)
223 if (boost::shared_ptr<AudioSource> source = src.lock()) {
225 std::map <boost::shared_ptr<ARDOUR::AudioSource>, std::vector <CacheEntry> >::iterator i;
226 for (i = _image_cache.begin (); i != _image_cache.end (); ++i) {
227 if (i->first == source) {
228 for (uint32_t n = 0; n < i->second.size (); ++n) {
229 i->second[n].image.clear ();
232 _image_cache.erase (i->first);
239 WaveView::invalidate_image_cache ()
241 vector <uint32_t> deletion_list;
242 vector <CacheEntry> caches;
244 /* The source may have disappeared.*/
246 if (_region->n_channels() == 0) {
250 if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
251 caches = _image_cache.find (_region->audio_source ())->second;
256 for (uint32_t i = 0; i < caches.size (); ++i) {
258 if (_channel != caches[i].channel
259 || _height != caches[i].height
260 || _region_amplitude != caches[i].amplitude
261 || _fill_color != caches[i].fill_color) {
266 deletion_list.push_back (i);
269 while (deletion_list.size() > 0) {
270 caches[deletion_list.back ()].image.clear ();
271 caches.erase (caches.begin() + deletion_list.back());
272 deletion_list.pop_back();
275 if (caches.size () == 0) {
276 _image_cache.erase(_region->audio_source ());
278 _image_cache[_region->audio_source ()] = caches;
283 WaveView::consolidate_image_cache () const
285 list <uint32_t> deletion_list;
286 vector <CacheEntry> caches;
287 uint32_t other_entries = 0;
289 if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
290 caches = _image_cache.find (_region->audio_source ())->second;
293 for (uint32_t i = 0; i < caches.size (); ++i) {
295 if (_channel != caches[i].channel
296 || _height != caches[i].height
297 || _region_amplitude != caches[i].amplitude
298 || _fill_color != caches[i].fill_color) {
304 framepos_t segment_start = caches[i].start;
305 framepos_t segment_end = caches[i].end;
307 for (uint32_t j = i; j < caches.size (); ++j) {
309 if (i == j || _channel != caches[j].channel
310 || _height != caches[i].height
311 || _region_amplitude != caches[i].amplitude
312 || _fill_color != caches[i].fill_color) {
317 if (caches[j].start >= segment_start && caches[j].end <= segment_end) {
319 deletion_list.push_back (j);
324 deletion_list.sort ();
325 deletion_list.unique ();
327 while (deletion_list.size() > 0) {
328 caches[deletion_list.back ()].image.clear ();
329 caches.erase (caches.begin() + deletion_list.back ());
330 deletion_list.pop_back();
333 /* We don't care if this channel/height/amplitude has anything in the cache - just drop the Last Added entries
334 until we reach a size where there is a maximum of CACHE_HIGH_WATER + other entries.
337 while (caches.size() > CACHE_HIGH_WATER + other_entries) {
338 caches.front ().image.clear ();
339 caches.erase(caches.begin ());
342 if (caches.size () == 0) {
343 _image_cache.erase (_region->audio_source ());
345 _image_cache[_region->audio_source ()] = caches;
350 WaveView::y_extent (double s, bool /*round_to_lower_edge*/) const
352 /* it is important that this returns an integral value, so that we
353 * can ensure correct single pixel behaviour.
355 * we need (_height - max(wave_line_width))
356 * wave_line_width == 1 IFF top==bottom (1 sample per pixel or flat line)
357 * wave_line_width == 2 otherwise
358 * then round away from the zero line, towards peak
360 if (_shape == Rectified) {
361 // we only ever have 1 point and align to the bottom (not center)
362 return floor ((1.0 - s) * (_height - 2.0));
364 /* currently canvas rectangle is off-by-one and we
365 * cannot draw a pixel at 0 (-.5 .. +.5) without it being
366 * clipped. A value 1.0 (ideally one point at y=0) ends
367 * up a pixel down. and a value of -1.0 (ideally y = _height-1)
368 * currently is on the bottom separator line :(
369 * So to make the complete waveform appear centered in
370 * a region, we translate by +1.5 (instead of -.5)
371 * and scale to height - 2.5 (if we scale to height - 2.0
372 * then the bottom most pixel may bleed into the selection rect
377 pos = floor ((1.0 - s) * .5 * (_height - 2.5));
379 return min (_height - 2.5, (max (0.0, pos)));
384 WaveView::draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peaks, int n_peaks) const
386 Cairo::RefPtr<Cairo::ImageSurface> stripe = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
388 Cairo::RefPtr<Cairo::Context> stripe_context = Cairo::Context::create (stripe);
389 stripe_context->set_antialias (Cairo::ANTIALIAS_NONE);
391 uint32_t stripe_separation = 150;
392 double start = - floor (_height / stripe_separation) * stripe_separation;
395 while (start < n_peaks) {
397 stripe_context->move_to (start, 0);
398 stripe_x = start + _height;
399 stripe_context->line_to (stripe_x, _height);
400 start += stripe_separation;
403 stripe_context->set_source_rgba (1.0, 1.0, 1.0, 1.0);
404 stripe_context->set_line_cap (Cairo::LINE_CAP_SQUARE);
405 stripe_context->set_line_width(50);
406 stripe_context->stroke();
408 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
410 context->set_source_rgba (1.0, 1.0, 0.0, 0.3);
411 context->mask (stripe, 0, 0);
422 LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
426 Cairo::RefPtr<Cairo::ImageSurface> wave;
427 Cairo::RefPtr<Cairo::ImageSurface> outline;
428 Cairo::RefPtr<Cairo::ImageSurface> clip;
429 Cairo::RefPtr<Cairo::ImageSurface> zero;
432 wave (0), outline (0), clip (0), zero (0) {}
436 WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peaks, int n_peaks) const
441 images.wave = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
442 images.outline = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
443 images.clip = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
444 images.zero = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, _height);
446 Cairo::RefPtr<Cairo::Context> wave_context = Cairo::Context::create (images.wave);
447 Cairo::RefPtr<Cairo::Context> outline_context = Cairo::Context::create (images.outline);
448 Cairo::RefPtr<Cairo::Context> clip_context = Cairo::Context::create (images.clip);
449 Cairo::RefPtr<Cairo::Context> zero_context = Cairo::Context::create (images.zero);
450 wave_context->set_antialias (Cairo::ANTIALIAS_NONE);
451 outline_context->set_antialias (Cairo::ANTIALIAS_NONE);
452 clip_context->set_antialias (Cairo::ANTIALIAS_NONE);
453 zero_context->set_antialias (Cairo::ANTIALIAS_NONE);
455 boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
457 /* Clip level nominally set to -0.9dBFS to account for inter-sample
458 interpolation possibly clipping (value may be too low).
460 We adjust by the region's own gain (but note: not by any gain
461 automation or its gain envelope) so that clip indicators are closer
462 to providing data about on-disk data. This multiplication is
463 needed because the data we get from AudioRegion::read_peaks()
464 has been scaled by scale_amplitude() already.
467 const double clip_level = _clip_level * _region_amplitude;
469 if (_shape == WaveView::Rectified) {
471 /* each peak is a line from the bottom of the waveview
472 * to a point determined by max (_peaks[i].max,
477 for (int i = 0; i < n_peaks; ++i) {
479 tips[i].bot = height() - 1.0;
480 const double p = alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min))));
481 tips[i].top = y_extent (p, false);
482 tips[i].spread = p * (_height - 1.0);
484 if (_peaks[i].max >= clip_level) {
485 tips[i].clip_max = true;
488 if (-(_peaks[i].min) >= clip_level) {
489 tips[i].clip_min = true;
494 for (int i = 0; i < n_peaks; ++i) {
496 tips[i].bot = height() - 1.0;
497 const double p = max(fabs (_peaks[i].max), fabs (_peaks[i].min));
498 tips[i].top = y_extent (p, false);
499 tips[i].spread = p * (_height - 2.0);
500 if (p >= clip_level) {
501 tips[i].clip_max = true;
510 for (int i = 0; i < n_peaks; ++i) {
511 double top = _peaks[i].max;
512 double bot = _peaks[i].min;
514 if (_peaks[i].max >= clip_level) {
515 tips[i].clip_max = true;
517 if (-(_peaks[i].min) >= clip_level) {
518 tips[i].clip_min = true;
522 top = alt_log_meter (fast_coefficient_to_dB (top));
523 } else if (top < 0.0) {
524 top =-alt_log_meter (fast_coefficient_to_dB (-top));
530 bot = alt_log_meter (fast_coefficient_to_dB (bot));
531 } else if (bot < 0.0) {
532 bot = -alt_log_meter (fast_coefficient_to_dB (-bot));
537 tips[i].top = y_extent (top, false);
538 tips[i].bot = y_extent (bot, true);
539 tips[i].spread = tips[i].bot - tips[i].top;
543 for (int i = 0; i < n_peaks; ++i) {
544 if (_peaks[i].max >= clip_level) {
545 tips[i].clip_max = true;
547 if (-(_peaks[i].min) >= clip_level) {
548 tips[i].clip_min = true;
551 tips[i].top = y_extent (_peaks[i].max, false);
552 tips[i].bot = y_extent (_peaks[i].min, true);
553 tips[i].spread = tips[i].bot - tips[i].top;
558 Color alpha_one = rgba_to_color (0, 0, 0, 1.0);
560 set_source_rgba (wave_context, alpha_one);
561 set_source_rgba (outline_context, alpha_one);
562 set_source_rgba (clip_context, alpha_one);
563 set_source_rgba (zero_context, alpha_one);
565 /* ensure single-pixel lines */
567 wave_context->set_line_width (1.0);
568 wave_context->translate (0.5, +1.5);
570 outline_context->set_line_width (1.0);
571 outline_context->translate (0.5, +1.5);
573 clip_context->set_line_width (1.0);
574 clip_context->translate (0.5, +1.5);
576 zero_context->set_line_width (1.0);
577 zero_context->translate (0.5, +1.5);
579 /* the height of the clip-indicator should be at most 7 pixels,
580 * or 5% of the height of the waveview item.
583 const double clip_height = min (7.0, ceil (_height * 0.05));
585 /* There are 3 possible components to draw at each x-axis position: the
586 waveform "line", the zero line and an outline/clip indicator. We
587 have to decide which of the 3 to draw at each position, pixel by
588 pixel. This makes the rendering less efficient but it is the only
589 way I can see to do this correctly.
591 To avoid constant source swapping and stroking, we draw the components separately
592 onto four alpha only image surfaces for use as a mask.
594 With only 1 pixel of spread between the top and bottom of the line,
595 we just draw the upper outline/clip indicator.
597 With 2 pixels of spread, we draw the upper and lower outline clip
600 With 3 pixels of spread we draw the upper and lower outline/clip
601 indicators and at least 1 pixel of the waveform line.
603 With 5 pixels of spread, we draw all components.
605 We can do rectified as two separate passes because we have a much
606 easier decision regarding whether to draw the waveform line. We
607 always draw the clip/outline indicators.
610 if (_shape == WaveView::Rectified) {
612 for (int i = 0; i < n_peaks; ++i) {
616 if (tips[i].spread >= 1.0) {
617 wave_context->move_to (i, tips[i].top);
618 wave_context->line_to (i, tips[i].bot);
622 bool clipped = false;
623 if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
624 clip_context->move_to (i, tips[i].top);
625 /* clip-indicating upper terminal line */
626 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + .5)));
631 outline_context->move_to (i, tips[i].top);
632 /* normal upper terminal dot */
633 outline_context->rel_line_to (0, -1.0);
637 wave_context->stroke ();
638 clip_context->stroke ();
639 outline_context->stroke ();
642 const double height_2 = (_height - 2.5) * .5;
644 for (int i = 0; i < n_peaks; ++i) {
648 if (tips[i].spread >= 2.0) {
649 wave_context->move_to (i, tips[i].top);
650 wave_context->line_to (i, tips[i].bot);
652 /* draw square waves and other discontiguous points clearly */
654 if (tips[i-1].top + 2 < tips[i].top) {
655 wave_context->move_to (i-1, tips[i-1].top);
656 wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2);
657 wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2);
658 wave_context->line_to (i, tips[i].top);
659 } else if (tips[i-1].bot > tips[i].bot + 2) {
660 wave_context->move_to (i-1, tips[i-1].bot);
661 wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2);
662 wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2);
663 wave_context->line_to (i, tips[i].bot);
669 if (tips[i].spread >= 5.0 && show_zero_line()) {
670 zero_context->move_to (i, floor(height_2));
671 zero_context->rel_line_to (1.0, 0);
674 if (tips[i].spread > 1.0) {
675 bool clipped = false;
676 /* outline/clip indicators */
677 if (_global_show_waveform_clipping && tips[i].clip_max) {
678 clip_context->move_to (i, tips[i].top);
679 /* clip-indicating upper terminal line */
680 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + 0.5)));
684 if (_global_show_waveform_clipping && tips[i].clip_min) {
685 clip_context->move_to (i, tips[i].bot);
686 /* clip-indicating lower terminal line */
687 clip_context->rel_line_to (0, - min (clip_height, ceil(tips[i].spread + 0.5)));
692 outline_context->move_to (i, tips[i].bot + 1.0);
693 /* normal lower terminal dot */
694 outline_context->rel_line_to (0, -1.0);
696 outline_context->move_to (i, tips[i].top - 1.0);
697 /* normal upper terminal dot */
698 outline_context->rel_line_to (0, 1.0);
701 bool clipped = false;
702 /* outline/clip indicator */
703 if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
704 clip_context->move_to (i, tips[i].top);
705 /* clip-indicating upper / lower terminal line */
706 clip_context->rel_line_to (0, 1.0);
711 wave_context->move_to (i, tips[i].top);
712 /* special case where outline only is drawn.
713 * we draw a 1px "line", pretending that the span is 1.0
715 wave_context->rel_line_to (0, 1.0);
720 wave_context->stroke ();
721 outline_context->stroke ();
722 clip_context->stroke ();
723 zero_context->stroke ();
726 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
728 /* Here we set a source colour and use the various components as a mask. */
730 if (gradient_depth() != 0.0) {
732 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _height));
738 if (_shape == Rectified) {
748 color_to_rgba (_fill_color, r, g, b, a);
749 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
750 /* generate a new color for the middle of the gradient */
752 color_to_hsv (_fill_color, h, s, v);
753 /* change v towards white */
754 v *= 1.0 - gradient_depth();
755 Color center = hsva_to_color (h, s, v, a);
756 color_to_rgba (center, r, g, b, a);
758 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
759 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
761 context->set_source (gradient);
763 set_source_rgba (context, _fill_color);
766 context->mask (images.wave, 0, 0);
769 set_source_rgba (context, _outline_color);
770 context->mask (images.outline, 0, 0);
773 set_source_rgba (context, _clip_color);
774 context->mask (images.clip, 0, 0);
777 set_source_rgba (context, _zero_color);
778 context->mask (images.zero, 0, 0);
784 WaveView::get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const
786 vector <CacheEntry> caches;
788 if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
790 caches = _image_cache.find (_region->audio_source ())->second;
793 /* Find a suitable ImageSurface.
795 for (uint32_t i = 0; i < caches.size (); ++i) {
797 if (_channel != caches[i].channel
798 || _height != caches[i].height
799 || _region_amplitude != caches[i].amplitude
800 || _fill_color != caches[i].fill_color) {
805 framepos_t segment_start = caches[i].start;
806 framepos_t segment_end = caches[i].end;
808 if (end <= segment_end && start >= segment_start) {
809 image_offset = (segment_start - _region_start) / _samples_per_pixel;
810 image = caches[i].image;
816 consolidate_image_cache ();
818 /* sample position is canonical here, and we want to generate
819 * an image that spans about twice the canvas width
822 const framepos_t center = start + ((end - start) / 2);
823 const framecnt_t canvas_samples = _canvas->visible_area().width() * _samples_per_pixel; /* one canvas width */
825 /* we can request data from anywhere in the Source, between 0 and its length
828 framepos_t sample_start = max ((framepos_t) 0, (center - canvas_samples));
829 framepos_t sample_end = min (center + canvas_samples, _region->source_length (0));
831 const int n_peaks = llrintf ((sample_end - sample_start)/ (double) _samples_per_pixel);
833 boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
835 framecnt_t peaks_read;
836 peaks_read = _region->read_peaks (peaks.get(), n_peaks,
837 sample_start, sample_end - sample_start,
841 image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, _height);
843 if (peaks_read > 0) {
844 draw_image (image, peaks.get(), n_peaks);
846 draw_absent_image (image, peaks.get(), n_peaks);
849 _image_cache[_region->audio_source ()].push_back (CacheEntry (_channel, _height, _region_amplitude, _fill_color, sample_start, sample_end, image));
851 image_offset = (sample_start - _region->start()) / _samples_per_pixel;
853 //cerr << "_image_cache size is : " << _image_cache.size() << " entries for this audiosource : " << _image_cache.find (_region->audio_source ())->second.size() << endl;
859 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
861 assert (_samples_per_pixel != 0);
867 Rect self = item_to_window (Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height));
868 boost::optional<Rect> d = self.intersection (area);
876 /* window coordinates - pixels where x=0 is the left edge of the canvas
877 * window. We round down in case we were asked to
878 * draw "between" pixels at the start and/or end.
881 double draw_start = floor (draw.x0);
882 const double draw_end = floor (draw.x1);
884 // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
886 /* image coordnates: pixels where x=0 is the start of this waveview,
887 * wherever it may be positioned. thus image_start=N means "an image
888 * that beings N pixels after the start of region that this waveview is
892 const framepos_t image_start = window_to_image (self.x0, draw_start);
893 const framepos_t image_end = window_to_image (self.x0, draw_end);
895 // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
897 /* sample coordinates - note, these are not subject to rounding error */
898 framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
899 framepos_t sample_end = _region_start + (image_end * _samples_per_pixel);
901 // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
903 Cairo::RefPtr<Cairo::ImageSurface> image;
904 double image_offset = 0;
906 get_image (image, sample_start, sample_end, image_offset);
908 // cerr << "Offset into image to place at zero: " << image_offset << endl;
910 if (_start_shift && (sample_start == _region_start) && (self.x0 == draw.x0)) {
911 /* we are going to draw the first pixel for this region, but
912 we may not want this to overlap a border around the
913 waveform. If so, _start_shift will be set.
915 //cerr << name.substr (23) << " ss = " << sample_start << " rs = " << _region_start << " sf = " << _start_shift << " ds = " << draw_start << " self = " << self << " draw = " << draw << endl;
916 //draw_start += _start_shift;
917 //image_offset += _start_shift;
920 context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
922 /* round image origin position to an exact pixel in device space to
926 double x = self.x0 + image_offset;
928 context->user_to_device (x, y);
931 context->device_to_user (x, y);
933 context->set_source (image, x, y);
939 WaveView::compute_bounding_box () const
942 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
944 _bounding_box = boost::optional<Rect> ();
947 _bounding_box_dirty = false;
951 WaveView::set_height (Distance height)
953 if (height != _height) {
956 invalidate_image_cache ();
959 _bounding_box_dirty = true;
965 WaveView::set_channel (int channel)
967 if (channel != _channel) {
970 invalidate_image_cache ();
973 _bounding_box_dirty = true;
979 WaveView::set_logscaled (bool yn)
981 if (_logscaled != yn) {
982 begin_visual_change ();
983 invalidate_image_cache ();
985 end_visual_change ();
990 WaveView::gain_changed ()
992 begin_visual_change ();
993 invalidate_image_cache ();
994 _region_amplitude = _region->scale_amplitude ();
995 end_visual_change ();
999 WaveView::set_zero_color (Color c)
1001 if (_zero_color != c) {
1002 begin_visual_change ();
1003 invalidate_image_cache ();
1005 end_visual_change ();
1010 WaveView::set_clip_color (Color c)
1012 if (_clip_color != c) {
1013 begin_visual_change ();
1014 invalidate_image_cache ();
1016 end_visual_change ();
1021 WaveView::set_show_zero_line (bool yn)
1023 if (_show_zero != yn) {
1024 begin_visual_change ();
1025 invalidate_image_cache ();
1027 end_visual_change ();
1032 WaveView::set_shape (Shape s)
1035 begin_visual_change ();
1036 invalidate_image_cache ();
1038 end_visual_change ();
1043 WaveView::set_amplitude_above_axis (double a)
1045 if (_amplitude_above_axis != a) {
1046 begin_visual_change ();
1047 invalidate_image_cache ();
1048 _amplitude_above_axis = a;
1049 end_visual_change ();
1054 WaveView::set_global_shape (Shape s)
1056 if (_global_shape != s) {
1058 VisualPropertiesChanged (); /* EMIT SIGNAL */
1063 WaveView::set_global_logscaled (bool yn)
1065 if (_global_logscaled != yn) {
1066 _global_logscaled = yn;
1067 VisualPropertiesChanged (); /* EMIT SIGNAL */
1072 WaveView::set_region_start (frameoffset_t start)
1078 if (_region_start == start) {
1083 _region_start = start;
1084 _bounding_box_dirty = true;
1089 WaveView::region_resized ()
1091 /* Called when the region start or end (thus length) has changed.
1099 _region_start = _region->start();
1100 _bounding_box_dirty = true;
1105 WaveView::set_global_gradient_depth (double depth)
1107 if (_global_gradient_depth != depth) {
1108 _global_gradient_depth = depth;
1109 VisualPropertiesChanged (); /* EMIT SIGNAL */
1114 WaveView::set_global_show_waveform_clipping (bool yn)
1116 if (_global_show_waveform_clipping != yn) {
1117 _global_show_waveform_clipping = yn;
1118 ClipLevelChanged ();
1123 WaveView::set_start_shift (double pixels)
1129 begin_visual_change ();
1130 _start_shift = pixels;
1131 end_visual_change ();