2 Copyright (C) 2011-2013 Paul Davis
3 Copyright (C) 2017 Tim Mayberry
4 Author: Carl Hetherington <cth@carlh.net>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <boost/scoped_array.hpp>
26 #include <cairomm/cairomm.h>
28 #include <glibmm/threads.h>
29 #include <gdkmm/general.h>
31 #include "pbd/base_ui.h"
32 #include "pbd/compose.h"
33 #include "pbd/convert.h"
34 #include "pbd/signals.h"
35 #include "pbd/stacktrace.h"
37 #include "ardour/types.h"
38 #include "ardour/dB.h"
39 #include "ardour/lmath.h"
40 #include "ardour/audioregion.h"
41 #include "ardour/audiosource.h"
42 #include "ardour/session.h"
44 #include "gtkmm2ext/colors.h"
45 #include "gtkmm2ext/gui_thread.h"
46 #include "gtkmm2ext/utils.h"
48 #include "canvas/canvas.h"
49 #include "canvas/debug.h"
51 #include "waveview/wave_view.h"
52 #include "waveview/wave_view_private.h"
55 #define Rect ArdourCanvas::Rect
60 using namespace ARDOUR;
61 using namespace Gtkmm2ext;
62 using namespace ArdourCanvas;
63 using namespace ArdourWaveView;
65 double WaveView::_global_gradient_depth = 0.6;
66 bool WaveView::_global_logscaled = false;
67 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
68 bool WaveView::_global_show_waveform_clipping = true;
69 double WaveView::_global_clip_level = 0.98853;
71 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
72 PBD::Signal0<void> WaveView::ClipLevelChanged;
74 /* NO_THREAD_WAVEVIEWS is defined by the top level wscript
75 * if --no-threaded-waveviws is provided at the configure step.
78 #ifndef NO_THREADED_WAVEVIEWS
79 #define ENABLE_THREADED_WAVEFORM_RENDERING
82 WaveView::WaveView (Canvas* c, boost::shared_ptr<ARDOUR::AudioRegion> region)
85 , _props (new WaveViewProperties (region))
86 , _shape_independent (false)
87 , _logscaled_independent (false)
88 , _gradient_depth_independent (false)
89 , _draw_image_in_gui_thread (false)
90 , _always_draw_image_in_gui_thread (false)
95 WaveView::WaveView (Item* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
98 , _props (new WaveViewProperties (region))
99 , _shape_independent (false)
100 , _logscaled_independent (false)
101 , _gradient_depth_independent (false)
102 , _draw_image_in_gui_thread (false)
103 , _always_draw_image_in_gui_thread (false)
111 #ifdef ENABLE_THREADED_WAVEFORM_RENDERING
112 WaveViewThreads::initialize ();
115 _props->fill_color = _fill_color;
116 _props->outline_color = _outline_color;
118 VisualPropertiesChanged.connect_same_thread (
119 invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
120 ClipLevelChanged.connect_same_thread (invalidation_connection,
121 boost::bind (&WaveView::handle_clip_level_change, this));
124 WaveView::~WaveView ()
126 #ifdef ENABLE_THREADED_WAVEFORM_RENDERING
127 WaveViewThreads::deinitialize ();
130 reset_cache_group ();
134 WaveView::debug_name() const
136 return _region->name () + string (":") + PBD::to_string (_props->channel + 1);
140 WaveView::set_always_get_image_in_thread (bool yn)
142 _always_draw_image_in_gui_thread = yn;
146 WaveView::handle_visual_property_change ()
148 bool changed = false;
150 if (!_shape_independent && (_props->shape != global_shape())) {
151 _props->shape = global_shape();
155 if (!_logscaled_independent && (_props->logscaled != global_logscaled())) {
156 _props->logscaled = global_logscaled();
160 if (!_gradient_depth_independent && (_props->gradient_depth != global_gradient_depth())) {
161 _props->gradient_depth = global_gradient_depth();
166 begin_visual_change ();
167 end_visual_change ();
172 WaveView::handle_clip_level_change ()
174 begin_visual_change ();
175 end_visual_change ();
179 WaveView::set_fill_color (Color c)
181 if (c != _fill_color) {
182 begin_visual_change ();
183 Fill::set_fill_color (c);
184 _props->fill_color = _fill_color; // ugh
185 end_visual_change ();
190 WaveView::set_outline_color (Color c)
192 if (c != _outline_color) {
193 begin_visual_change ();
194 Outline::set_outline_color (c);
195 _props->outline_color = c;
196 end_visual_change ();
201 WaveView::set_samples_per_pixel (double samples_per_pixel)
203 if (_props->samples_per_pixel != samples_per_pixel) {
206 _props->samples_per_pixel = samples_per_pixel;
207 _bounding_box_dirty = true;
214 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
216 return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
220 alt_log_meter (float power)
222 return _log_meter (power, -192.0, 0.0, 8.0);
226 WaveView::set_clip_level (double dB)
228 const double clip_level = dB_to_coefficient (dB);
229 if (_global_clip_level != clip_level) {
230 _global_clip_level = clip_level;
235 boost::shared_ptr<WaveViewDrawRequest>
236 WaveView::create_draw_request (WaveViewProperties const& props) const
238 assert (props.is_valid());
240 boost::shared_ptr<WaveViewDrawRequest> request (new WaveViewDrawRequest);
242 request->image = boost::shared_ptr<WaveViewImage> (new WaveViewImage (_region, props));
247 WaveView::prepare_for_render (Rect const& area) const
249 if (draw_image_in_gui_thread()) {
250 // Drawing image in GUI thread in WaveView::render
257 // all in window coordinate space
258 if (!get_item_and_draw_rect_in_window_coords (area, self_rect, draw_rect)) {
262 double const image_start_pixel_offset = draw_rect.x0 - self_rect.x0;
263 double const image_end_pixel_offset = draw_rect.x1 - self_rect.x0;
265 WaveViewProperties required_props = *_props;
267 required_props.set_sample_positions_from_pixel_offsets (image_start_pixel_offset,
268 image_end_pixel_offset);
270 if (!required_props.is_valid ()) {
275 if (_image->props.is_equivalent (required_props)) {
278 // Image does not contain sample area required
282 boost::shared_ptr<WaveViewDrawRequest> request = create_draw_request (required_props);
284 queue_draw_request (request);
288 WaveView::get_item_and_draw_rect_in_window_coords (Rect const& canvas_rect, Rect& item_rect,
289 Rect& draw_rect) const
291 /* a WaveView is intimately connected to an AudioRegion. It will
292 * display the waveform within the region, anywhere from the start of
293 * the region to its end.
295 * the area we've been asked to render may overlap with area covered
296 * by the region in any of the normal ways:
298 * - it may begin and end within the area covered by the region
299 * - it may start before and end after the area covered by region
300 * - it may start before and end within the area covered by the region
301 * - it may start within and end after the area covered by the region
302 * - it may be precisely coincident with the area covered by region.
304 * So let's start by determining the area covered by the region, in
305 * window coordinates. It begins at zero (in item coordinates for this
306 * waveview, and extends to region_length() / _samples_per_pixel.
309 double const width = region_length() / _props->samples_per_pixel;
310 item_rect = item_to_window (Rect (0.0, 0.0, width, _props->height));
312 /* Now lets get the intersection with the area we've been asked to draw */
314 draw_rect = item_rect.intersection (canvas_rect);
317 // No intersection with drawing area
321 /* draw_rect now defines the rectangle we need to update/render the waveview
322 * into, in window coordinate space.
324 * We round down in case we were asked to draw "between" pixels at the start
327 draw_rect.x0 = floor (draw_rect.x0);
328 draw_rect.x1 = floor (draw_rect.x1);
334 WaveView::queue_draw_request (boost::shared_ptr<WaveViewDrawRequest> const& request) const
336 // Don't enqueue any requests without a thread to dequeue them.
337 assert (WaveViewThreads::enabled());
339 if (!request || !request->is_valid()) {
343 if (current_request) {
344 current_request->cancel ();
347 boost::shared_ptr<WaveViewImage> cached_image =
348 get_cache_group ()->lookup_image (request->image->props);
351 // The image may not be finished at this point but that is fine, great in
352 // fact as it means it should only need to be drawn once.
353 request->image = cached_image;
354 current_request = request;
356 // now we can finally set an optimal image now that we are not using the
357 // properties for comparisons.
358 request->image->props.set_width_samples (optimal_image_width_samples ());
360 current_request = request;
362 // Add it to the cache so that other WaveViews can refer to the same image
363 get_cache_group()->add_image (current_request->image);
365 WaveViewThreads::enqueue_draw_request (current_request);
370 WaveView::compute_tips (ARDOUR::PeakData const& peak, WaveView::LineTips& tips,
371 double const effective_height)
373 /* remember: canvas (and cairo) coordinate space puts the origin at the upper left.
375 So, a sample value of 1.0 (0dbFS) will be computed as:
377 (1.0 - 1.0) * 0.5 * effective_height
379 which evaluates to 0, or the top of the image.
381 A sample value of -1.0 will be computed as
383 (1.0 + 1.0) * 0.5 * effective height
385 which evaluates to effective height, or the bottom of the image.
388 const double pmax = (1.0 - peak.max) * 0.5 * effective_height;
389 const double pmin = (1.0 - peak.min) * 0.5 * effective_height;
391 /* remember that the bottom of the image (pmin) has larger y-coordinates
395 double spread = (pmin - pmax) * 0.5;
397 /* find the nearest pixel to the nominal center. */
398 const double center = round (pmin - spread);
401 /* minimum distance between line ends is 1 pixel, and we want it "centered" on a pixel,
402 as per cairo single-pixel line issues.
404 NOTE: the caller will not draw a line between these two points if the spread is
405 less than 2 pixels. So only the tips.top value matters, which is where we will
406 draw a single pixel as part of the outline.
409 tips.bot = center + 1.0;
411 /* round spread above and below center to an integer number of pixels */
412 spread = round (spread);
413 /* top and bottom are located equally either side of the center */
414 tips.top = center - spread;
415 tips.bot = center + spread;
418 tips.top = min (effective_height, max (0.0, tips.top));
419 tips.bot = min (effective_height, max (0.0, tips.bot));
424 WaveView::y_extent (double s, Shape const shape, double const height)
426 assert (shape == Rectified);
427 return floor ((1.0 - s) * height);
431 WaveView::draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* peaks, int n_peaks)
433 const double height = image->get_height();
435 Cairo::RefPtr<Cairo::ImageSurface> stripe = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
437 Cairo::RefPtr<Cairo::Context> stripe_context = Cairo::Context::create (stripe);
438 stripe_context->set_antialias (Cairo::ANTIALIAS_NONE);
440 uint32_t stripe_separation = 150;
441 double start = - floor (height / stripe_separation) * stripe_separation;
444 while (start < n_peaks) {
446 stripe_context->move_to (start, 0);
447 stripe_x = start + height;
448 stripe_context->line_to (stripe_x, height);
449 start += stripe_separation;
452 stripe_context->set_source_rgba (1.0, 1.0, 1.0, 1.0);
453 stripe_context->set_line_cap (Cairo::LINE_CAP_SQUARE);
454 stripe_context->set_line_width(50);
455 stripe_context->stroke();
457 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
459 context->set_source_rgba (1.0, 1.0, 0.0, 0.3);
460 context->mask (stripe, 0, 0);
465 Cairo::RefPtr<Cairo::ImageSurface> wave;
466 Cairo::RefPtr<Cairo::ImageSurface> outline;
467 Cairo::RefPtr<Cairo::ImageSurface> clip;
468 Cairo::RefPtr<Cairo::ImageSurface> zero;
471 wave (0), outline (0), clip (0), zero (0) {}
475 WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* peaks, int n_peaks,
476 boost::shared_ptr<WaveViewDrawRequest> req)
478 const double height = image->get_height();
482 images.wave = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
483 images.outline = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
484 images.clip = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
485 images.zero = Cairo::ImageSurface::create (Cairo::FORMAT_A8, n_peaks, height);
487 Cairo::RefPtr<Cairo::Context> wave_context = Cairo::Context::create (images.wave);
488 Cairo::RefPtr<Cairo::Context> outline_context = Cairo::Context::create (images.outline);
489 Cairo::RefPtr<Cairo::Context> clip_context = Cairo::Context::create (images.clip);
490 Cairo::RefPtr<Cairo::Context> zero_context = Cairo::Context::create (images.zero);
491 wave_context->set_antialias (Cairo::ANTIALIAS_NONE);
492 outline_context->set_antialias (Cairo::ANTIALIAS_NONE);
493 clip_context->set_antialias (Cairo::ANTIALIAS_NONE);
494 zero_context->set_antialias (Cairo::ANTIALIAS_NONE);
496 boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
498 /* Clip level nominally set to -0.9dBFS to account for inter-sample
499 interpolation possibly clipping (value may be too low).
501 We adjust by the region's own gain (but note: not by any gain
502 automation or its gain envelope) so that clip indicators are closer
503 to providing data about on-disk data. This multiplication is
504 needed because the data we get from AudioRegion::read_peaks()
505 has been scaled by scale_amplitude() already.
508 const double clip_level = _global_clip_level * req->image->props.amplitude;
510 const Shape shape = req->image->props.shape;
511 const bool logscaled = req->image->props.logscaled;
513 if (req->image->props.shape == WaveView::Rectified) {
515 /* each peak is a line from the bottom of the waveview
516 * to a point determined by max (peaks[i].max,
521 for (int i = 0; i < n_peaks; ++i) {
523 tips[i].bot = height - 1.0;
524 const double p = alt_log_meter (fast_coefficient_to_dB (max (fabs (peaks[i].max), fabs (peaks[i].min))));
525 tips[i].top = y_extent (p, shape, height);
526 tips[i].spread = p * height;
528 if (peaks[i].max >= clip_level) {
529 tips[i].clip_max = true;
532 if (-(peaks[i].min) >= clip_level) {
533 tips[i].clip_min = true;
538 for (int i = 0; i < n_peaks; ++i) {
540 tips[i].bot = height - 1.0;
541 const double p = max(fabs (peaks[i].max), fabs (peaks[i].min));
542 tips[i].top = y_extent (p, shape, height);
543 tips[i].spread = p * height;
544 if (p >= clip_level) {
545 tips[i].clip_max = true;
554 for (int i = 0; i < n_peaks; ++i) {
556 p.max = peaks[i].max;
557 p.min = peaks[i].min;
559 if (peaks[i].max >= clip_level) {
560 tips[i].clip_max = true;
562 if (-(peaks[i].min) >= clip_level) {
563 tips[i].clip_min = true;
567 p.max = alt_log_meter (fast_coefficient_to_dB (p.max));
568 } else if (p.max < 0.0) {
569 p.max =-alt_log_meter (fast_coefficient_to_dB (-p.max));
575 p.min = alt_log_meter (fast_coefficient_to_dB (p.min));
576 } else if (p.min < 0.0) {
577 p.min = -alt_log_meter (fast_coefficient_to_dB (-p.min));
582 compute_tips (p, tips[i], height);
583 tips[i].spread = tips[i].bot - tips[i].top;
587 for (int i = 0; i < n_peaks; ++i) {
588 if (peaks[i].max >= clip_level) {
589 tips[i].clip_max = true;
591 if (-(peaks[i].min) >= clip_level) {
592 tips[i].clip_min = true;
595 compute_tips (peaks[i], tips[i], height);
596 tips[i].spread = tips[i].bot - tips[i].top;
602 if (req->stopped()) {
606 Color alpha_one = rgba_to_color (0, 0, 0, 1.0);
608 set_source_rgba (wave_context, alpha_one);
609 set_source_rgba (outline_context, alpha_one);
610 set_source_rgba (clip_context, alpha_one);
611 set_source_rgba (zero_context, alpha_one);
613 /* ensure single-pixel lines */
615 wave_context->set_line_width (1.0);
616 wave_context->translate (0.5, 0.5);
618 outline_context->set_line_width (1.0);
619 outline_context->translate (0.5, 0.5);
621 clip_context->set_line_width (1.0);
622 clip_context->translate (0.5, 0.5);
624 zero_context->set_line_width (1.0);
625 zero_context->translate (0.5, 0.5);
627 /* the height of the clip-indicator should be at most 7 pixels,
628 * or 5% of the height of the waveview item.
631 const double clip_height = min (7.0, ceil (height * 0.05));
633 /* There are 3 possible components to draw at each x-axis position: the
634 waveform "line", the zero line and an outline/clip indicator. We
635 have to decide which of the 3 to draw at each position, pixel by
636 pixel. This makes the rendering less efficient but it is the only
637 way I can see to do this correctly.
639 To avoid constant source swapping and stroking, we draw the components separately
640 onto four alpha only image surfaces for use as a mask.
642 With only 1 pixel of spread between the top and bottom of the line,
643 we just draw the upper outline/clip indicator.
645 With 2 pixels of spread, we draw the upper and lower outline clip
648 With 3 pixels of spread we draw the upper and lower outline/clip
649 indicators and at least 1 pixel of the waveform line.
651 With 5 pixels of spread, we draw all components.
653 We can do rectified as two separate passes because we have a much
654 easier decision regarding whether to draw the waveform line. We
655 always draw the clip/outline indicators.
658 if (shape == WaveView::Rectified) {
660 for (int i = 0; i < n_peaks; ++i) {
664 if (tips[i].spread >= 1.0) {
665 wave_context->move_to (i, tips[i].top);
666 wave_context->line_to (i, tips[i].bot);
671 if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
672 clip_context->move_to (i, tips[i].top);
673 /* clip-indicating upper terminal line */
674 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + .5)));
676 outline_context->move_to (i, tips[i].top);
677 /* normal upper terminal dot */
678 outline_context->rel_line_to (0, -1.0);
682 wave_context->stroke ();
683 clip_context->stroke ();
684 outline_context->stroke ();
687 const int height_zero = floor(height * .5);
689 for (int i = 0; i < n_peaks; ++i) {
693 if (tips[i].spread >= 2.0) {
694 wave_context->move_to (i, tips[i].top);
695 wave_context->line_to (i, tips[i].bot);
698 /* draw square waves and other discontiguous points clearly */
700 if (tips[i-1].top + 2 < tips[i].top) {
701 wave_context->move_to (i-1, tips[i-1].top);
702 wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2);
703 wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2);
704 wave_context->line_to (i, tips[i].top);
705 } else if (tips[i-1].bot > tips[i].bot + 2) {
706 wave_context->move_to (i-1, tips[i-1].bot);
707 wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2);
708 wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2);
709 wave_context->line_to (i, tips[i].bot);
713 /* zero line, show only if there is enough spread
714 or the waveform line does not cross zero line */
715 bool const show_zero_line = req->image->props.show_zero;
717 if (show_zero_line && ((tips[i].spread >= 5.0) || (tips[i].top > height_zero ) || (tips[i].bot < height_zero)) ) {
718 zero_context->move_to (i, height_zero);
719 zero_context->rel_line_to (1.0, 0);
722 if (tips[i].spread > 1.0) {
723 bool clipped = false;
724 /* outline/clip indicators */
725 if (_global_show_waveform_clipping && tips[i].clip_max) {
726 clip_context->move_to (i, tips[i].top);
727 /* clip-indicating upper terminal line */
728 clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + 0.5)));
732 if (_global_show_waveform_clipping && tips[i].clip_min) {
733 clip_context->move_to (i, tips[i].bot);
734 /* clip-indicating lower terminal line */
735 clip_context->rel_line_to (0, - min (clip_height, ceil(tips[i].spread + 0.5)));
739 if (!clipped && tips[i].spread > 2.0) {
740 /* only draw the outline if the spread
741 implies 3 or more pixels (so that we see 1
742 white pixel in the middle).
744 outline_context->move_to (i, tips[i].bot);
745 /* normal lower terminal dot; line moves up */
746 outline_context->rel_line_to (0, -1.0);
748 outline_context->move_to (i, tips[i].top);
749 /* normal upper terminal dot, line moves down */
750 outline_context->rel_line_to (0, 1.0);
753 bool clipped = false;
754 /* outline/clip indicator */
755 if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
756 clip_context->move_to (i, tips[i].top);
757 /* clip-indicating upper / lower terminal line */
758 clip_context->rel_line_to (0, 1.0);
763 /* special case where only 1 pixel of
764 * the waveform line is drawn (and
767 * we draw a 1px "line", pretending
768 * that the span is 1.0 (whether it is
771 wave_context->move_to (i, tips[i].top);
772 wave_context->rel_line_to (0, 1.0);
777 wave_context->stroke ();
778 outline_context->stroke ();
779 clip_context->stroke ();
780 zero_context->stroke ();
783 if (req->stopped()) {
787 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
789 /* Here we set a source colour and use the various components as a mask. */
791 const Color fill_color = req->image->props.fill_color;
792 const double gradient_depth = req->image->props.gradient_depth;
794 if (gradient_depth != 0.0) {
796 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, height));
803 if (shape == Rectified) {
813 color_to_rgba (fill_color, r, g, b, a);
814 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
815 /* generate a new color for the middle of the gradient */
817 color_to_hsv (fill_color, h, s, v);
818 /* change v towards white */
819 v *= 1.0 - gradient_depth;
820 Color center = hsva_to_color (h, s, v, a);
821 color_to_rgba (center, r, g, b, a);
823 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
824 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
826 context->set_source (gradient);
828 set_source_rgba (context, fill_color);
831 if (req->stopped()) {
835 context->mask (images.wave, 0, 0);
838 set_source_rgba (context, req->image->props.outline_color);
839 context->mask (images.outline, 0, 0);
842 set_source_rgba (context, req->image->props.clip_color);
843 context->mask (images.clip, 0, 0);
846 set_source_rgba (context, req->image->props.zero_color);
847 context->mask (images.zero, 0, 0);
852 WaveView::optimal_image_width_samples () const
854 /* Compute how wide the image should be in samples.
856 * The resulting image should be wider than the canvas width so that the
857 * image does not have to be redrawn each time the canvas offset changes, but
858 * drawing too much unnecessarily, for instance when zooming into the canvas
859 * the part of the image that is outside of the visible canvas area may never
860 * be displayed and will just increase apparent render time and reduce
861 * responsiveness in non-threaded rendering and cause "flashing" waveforms in
862 * threaded rendering mode.
864 * Another thing to consider is that if there are a number of waveforms on
865 * the canvas that are the width of the canvas then we don't want to have to
866 * draw the images for them all at once as it will cause a spike in render
867 * time, or in threaded rendering mode it will mean all the draw requests will
868 * the queued during the same frame/expose event. This issue can be
869 * alleviated by using an element of randomness in selecting the image width.
871 * If the value of samples per pixel is less than 1/10th of a second, use
872 * 1/10th of a second instead.
875 framecnt_t canvas_width_samples = _canvas->visible_area().width() * _props->samples_per_pixel;
876 const framecnt_t one_tenth_of_second = _region->session().frame_rate() / 10;
878 /* If zoomed in where a canvas item interects with the canvas area but
879 * stretches for many pages either side, to avoid having draw all images when
880 * the canvas scrolls by a page width the multiplier would have to be a
881 * randomized amount centered around 3 times the visible canvas width, but
882 * for other operations like zooming or even with a stationary playhead it is
883 * a lot of extra drawing that can affect performance.
885 * So without making things too complicated with different widths for
886 * different operations, try to use a width that is a balance and will work
887 * well for scrolling(non-page width) so all the images aren't redrawn at the
888 * same time but also faster for sequential zooming operations.
890 * Canvas items that don't intersect with the edges of the visible canvas
891 * will of course only draw images that are the pixel width of the item.
893 * It is a perhaps a coincidence that these values are centered roughly
894 * around the golden ratio but they did work well in my testing.
896 const double min_multiplier = 1.4;
897 const double max_multiplier = 1.8;
900 * A combination of high resolution screens, high samplerates and high
901 * zoom levels(1 sample per pixel) can cause 1/10 of a second(in
902 * pixels) to exceed the cairo image size limit.
904 const double cairo_image_limit = 32767.0;
905 const double max_image_width = cairo_image_limit / max_multiplier;
907 framecnt_t max_width_samples = floor (max_image_width / _props->samples_per_pixel);
909 const framecnt_t one_tenth_of_second_limited = std::min (one_tenth_of_second, max_width_samples);
911 framecnt_t new_sample_count = std::max (canvas_width_samples, one_tenth_of_second_limited);
913 const double multiplier = g_random_double_range (min_multiplier, max_multiplier);
915 return new_sample_count * multiplier;
919 WaveView::set_image (boost::shared_ptr<WaveViewImage> img) const
921 get_cache_group ()->add_image (img);
926 WaveView::process_draw_request (boost::shared_ptr<WaveViewDrawRequest> req)
928 boost::shared_ptr<const ARDOUR::AudioRegion> region = req->image->region.lock();
934 if (req->stopped()) {
938 WaveViewProperties const& props = req->image->props;
940 const int n_peaks = props.get_width_pixels ();
942 assert (n_peaks > 0 && n_peaks < 32767);
944 boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
946 /* Note that Region::read_peaks() takes a start position based on an
947 offset into the Region's **SOURCE**, rather than an offset into
951 framecnt_t peaks_read =
952 region->read_peaks (peaks.get (), n_peaks, props.get_sample_start (),
953 props.get_length_samples (), props.channel, props.samples_per_pixel);
955 if (req->stopped()) {
959 Cairo::RefPtr<Cairo::ImageSurface> cairo_image =
960 Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, req->image->props.height);
962 // http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create
963 // This function always returns a valid pointer, but it will return a pointer to a "nil" surface..
964 // but there's some evidence that req->image can be NULL.
965 // http://tracker.ardour.org/view.php?id=6478
966 assert (cairo_image);
968 if (peaks_read > 0) {
970 /* region amplitude will have been used to generate the
971 * peak values already, but not the visual-only
972 * amplitude_above_axis. So apply that here before
976 const double amplitude_above_axis = props.amplitude_above_axis;
978 if (amplitude_above_axis != 1.0) {
979 for (framecnt_t i = 0; i < n_peaks; ++i) {
980 peaks[i].max *= amplitude_above_axis;
981 peaks[i].min *= amplitude_above_axis;
985 draw_image (cairo_image, peaks.get(), n_peaks, req);
988 draw_absent_image (cairo_image, peaks.get(), n_peaks);
991 if (req->stopped ()) {
995 // Assign now that we are sure all drawing is complete as that is what
996 // determines whether a request was finished.
997 req->image->cairo_image = cairo_image;
1001 WaveView::draw_image_in_gui_thread () const
1003 return _draw_image_in_gui_thread || _always_draw_image_in_gui_thread || !rendered () ||
1004 !WaveViewThreads::enabled ();
1008 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
1010 assert (_props->samples_per_pixel != 0);
1012 if (!_region) { // assert?
1019 if (!get_item_and_draw_rect_in_window_coords (area, self, draw)) {
1024 double const image_start_pixel_offset = draw.x0 - self.x0;
1025 double const image_end_pixel_offset = draw.x1 - self.x0;
1027 if (image_start_pixel_offset == image_end_pixel_offset) {
1028 // this may happen if zoomed very far out with a small region
1032 WaveViewProperties required_props = *_props;
1034 required_props.set_sample_positions_from_pixel_offsets (image_start_pixel_offset,
1035 image_end_pixel_offset);
1037 assert (required_props.is_valid());
1039 boost::shared_ptr<WaveViewImage> image_to_draw;
1041 if (current_request) {
1042 if (!current_request->image->props.is_equivalent (required_props)) {
1043 // The WaveView properties may have been updated during recording between
1044 // prepare_for_render and render calls and the new required props have
1045 // different end sample value.
1046 current_request->cancel ();
1047 current_request.reset ();
1048 } else if (current_request->finished ()) {
1049 image_to_draw = current_request->image;
1050 current_request.reset ();
1053 // No current Request
1056 if (!image_to_draw && _image) {
1057 if (_image->props.is_equivalent (required_props)) {
1058 // Image contains required properties
1059 image_to_draw = _image;
1061 // Image does not contain properties required
1065 if (!image_to_draw) {
1066 image_to_draw = get_cache_group ()->lookup_image (required_props);
1067 if (image_to_draw && !image_to_draw->finished ()) {
1068 // Found equivalent but unfinished Image in cache
1069 image_to_draw.reset ();
1073 if (!image_to_draw) {
1074 // No existing image to draw
1076 boost::shared_ptr<WaveViewDrawRequest> const request = create_draw_request (required_props);
1078 if (draw_image_in_gui_thread ()) {
1079 // now that we have to draw something, draw more than required.
1080 request->image->props.set_width_samples (optimal_image_width_samples ());
1082 process_draw_request (request);
1084 image_to_draw = request->image;
1086 } else if (current_request) {
1087 if (current_request->finished ()) {
1088 // There is a chance the request is now finished since checking above
1089 image_to_draw = current_request->image;
1090 current_request.reset ();
1091 } else if (_canvas->get_microseconds_since_render_start () < 15000) {
1092 current_request->cancel ();
1093 current_request.reset ();
1095 // Drawing image in GUI thread as we have time
1097 // now that we have to draw something, draw more than required.
1098 request->image->props.set_width_samples (optimal_image_width_samples ());
1100 process_draw_request (request);
1102 image_to_draw = request->image;
1104 // Waiting for current request to finish
1109 // Defer the rendering to another thread or perhaps render pass if
1110 // a thread cannot generate it in time.
1111 queue_draw_request (request);
1117 /* reset this so that future missing images can be generated in a worker thread. */
1118 _draw_image_in_gui_thread = false;
1120 assert (image_to_draw);
1122 /* compute the first pixel of the image that should be used when we
1123 * render the specified range.
1126 double image_origin_in_self_coordinates =
1127 (image_to_draw->props.get_sample_start () - _props->region_start) / _props->samples_per_pixel;
1129 /* the image may only be a best-effort ... it may not span the entire
1130 * range requested, though it is guaranteed to cover the start. So
1131 * determine how many pixels we can actually draw.
1134 const double draw_start_pixel = draw.x0;
1135 const double draw_end_pixel = draw.x1;
1137 double draw_width_pixels = draw_end_pixel - draw_start_pixel;
1139 if (image_to_draw != _image) {
1141 /* the image is guaranteed to start at or before
1142 * draw_start. But if it starts before draw_start, that reduces
1143 * the maximum available width we can render with.
1145 * so .. clamp the draw width to the smaller of what we need to
1146 * draw or the available width of the image.
1148 draw_width_pixels = min ((double)image_to_draw->cairo_image->get_width (), draw_width_pixels);
1150 set_image (image_to_draw);
1153 context->rectangle (draw_start_pixel, draw.y0, draw_width_pixels, draw.height());
1155 /* round image origin position to an exact pixel in device space to
1159 double x = self.x0 + image_origin_in_self_coordinates;
1161 context->user_to_device (x, y);
1164 context->device_to_user (x, y);
1166 /* the coordinates specify where in "user coordinates" (i.e. what we
1167 * generally call "canvas coordinates" in this code) the image origin
1168 * will appear. So specifying (10,10) will put the upper left corner of
1169 * the image at (10,10) in user space.
1172 context->set_source (image_to_draw->cairo_image, x, y);
1177 WaveView::compute_bounding_box () const
1180 _bounding_box = Rect (0.0, 0.0, region_length() / _props->samples_per_pixel, _props->height);
1182 _bounding_box = Rect ();
1185 _bounding_box_dirty = false;
1189 WaveView::set_height (Distance height)
1191 if (_props->height != height) {
1194 _props->height = height;
1195 _draw_image_in_gui_thread = true;
1197 _bounding_box_dirty = true;
1203 WaveView::set_channel (int channel)
1205 if (_props->channel != channel) {
1207 _props->channel = channel;
1208 reset_cache_group ();
1209 _bounding_box_dirty = true;
1215 WaveView::set_logscaled (bool yn)
1217 if (_props->logscaled != yn) {
1218 begin_visual_change ();
1219 _props->logscaled = yn;
1220 end_visual_change ();
1225 WaveView::set_gradient_depth (double)
1231 WaveView::gradient_depth () const
1233 return _props->gradient_depth;
1237 WaveView::gain_changed ()
1239 begin_visual_change ();
1240 _props->amplitude = _region->scale_amplitude ();
1241 _draw_image_in_gui_thread = true;
1242 end_visual_change ();
1246 WaveView::set_zero_color (Color c)
1248 if (_props->zero_color != c) {
1249 begin_visual_change ();
1250 _props->zero_color = c;
1251 end_visual_change ();
1256 WaveView::set_clip_color (Color c)
1258 if (_props->clip_color != c) {
1259 begin_visual_change ();
1260 _props->clip_color = c;
1261 end_visual_change ();
1266 WaveView::set_show_zero_line (bool yn)
1268 if (_props->show_zero != yn) {
1269 begin_visual_change ();
1270 _props->show_zero = yn;
1271 end_visual_change ();
1276 WaveView::show_zero_line () const
1278 return _props->show_zero;
1282 WaveView::set_shape (Shape s)
1284 if (_props->shape != s) {
1285 begin_visual_change ();
1287 end_visual_change ();
1292 WaveView::set_amplitude_above_axis (double a)
1294 if (fabs (_props->amplitude_above_axis - a) > 0.01) {
1295 begin_visual_change ();
1296 _props->amplitude_above_axis = a;
1297 _draw_image_in_gui_thread = true;
1298 end_visual_change ();
1303 WaveView::amplitude_above_axis () const
1305 return _props->amplitude_above_axis;
1309 WaveView::set_global_shape (Shape s)
1311 if (_global_shape != s) {
1313 WaveViewCache::get_instance()->clear_cache ();
1314 VisualPropertiesChanged (); /* EMIT SIGNAL */
1319 WaveView::set_global_logscaled (bool yn)
1321 if (_global_logscaled != yn) {
1322 _global_logscaled = yn;
1323 WaveViewCache::get_instance()->clear_cache ();
1324 VisualPropertiesChanged (); /* EMIT SIGNAL */
1329 WaveView::region_length() const
1331 return _region->length() - (_props->region_start - _region->start());
1335 WaveView::region_end() const
1337 return _props->region_start + region_length();
1341 WaveView::set_region_start (frameoffset_t start)
1347 if (_props->region_start == start) {
1352 _props->region_start = start;
1353 _bounding_box_dirty = true;
1358 WaveView::region_resized ()
1360 /* Called when the region start or end (thus length) has changed.
1368 _props->region_start = _region->start();
1369 _props->region_end = _region->start() + _region->length();
1370 _bounding_box_dirty = true;
1375 WaveView::set_global_gradient_depth (double depth)
1377 if (_global_gradient_depth != depth) {
1378 _global_gradient_depth = depth;
1379 VisualPropertiesChanged (); /* EMIT SIGNAL */
1384 WaveView::set_global_show_waveform_clipping (bool yn)
1386 if (_global_show_waveform_clipping != yn) {
1387 _global_show_waveform_clipping = yn;
1388 ClipLevelChanged ();
1393 WaveView::set_start_shift (double pixels)
1399 begin_visual_change ();
1400 //_start_shift = pixels;
1401 end_visual_change ();
1405 WaveView::set_image_cache_size (uint64_t sz)
1407 WaveViewCache::get_instance()->set_image_cache_threshold (sz);
1410 boost::shared_ptr<WaveViewCacheGroup>
1411 WaveView::get_cache_group () const
1414 return _cache_group;
1417 boost::shared_ptr<AudioSource> source = _region->audio_source (_props->channel);
1420 _cache_group = WaveViewCache::get_instance ()->get_cache_group (source);
1422 return _cache_group;
1426 WaveView::reset_cache_group ()
1428 WaveViewCache::get_instance()->reset_cache_group (_cache_group);