+ ret = get_image_from_cache (start, end, full_image);
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: lookup from cache gave %2 (full %3)\n",
+ name, ret, full_image));
+
+ }
+
+
+
+ if (!ret || !full_image) {
+
+#ifndef ENABLE_THREADED_WAVEFORM_RENDERING
+ if (1)
+#else
+ if ((rendered && get_image_in_thread) || always_get_image_in_thread)
+#endif
+ {
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: generating image in caller thread\n", name));
+
+ boost::shared_ptr<WaveViewThreadRequest> req (new WaveViewThreadRequest);
+
+ req->type = WaveViewThreadRequest::Draw;
+ req->start = start;
+ req->end = end;
+ req->samples_per_pixel = _samples_per_pixel;
+ req->region = _region; /* weak ptr, to avoid storing a reference in the request queue */
+ req->channel = _channel;
+ req->height = _height;
+ req->fill_color = _fill_color;
+ req->amplitude = _region_amplitude * _amplitude_above_axis;
+ req->width = desired_image_width ();
+
+ /* draw image in this (the GUI thread) */
+
+ generate_image (req, false);
+
+ /* cache the result */
+
+ ret = cache_request_result (req);
+
+ /* reset this so that future missing images are
+ * generated in a a worker thread.
+ */
+
+ get_image_in_thread = false;
+
+ } else {
+ queue_get_image (_region, start, end);
+ }
+ }
+
+ if (ret) {
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 got an image from %2 .. %3 (full ? %4)\n", name, ret->start, ret->end, full_image));
+ } else {
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 no useful image available\n", name));
+ }
+
+ return ret;
+}
+
+boost::shared_ptr<WaveViewCache::Entry>
+WaveView::get_image_from_cache (framepos_t start, framepos_t end, bool& full) const
+{
+ if (!images) {
+ return boost::shared_ptr<WaveViewCache::Entry>();
+ }
+
+ return images->lookup_image (_region->audio_source (_channel), start, end, _channel,
+ _height, _region_amplitude * _amplitude_above_axis, _fill_color, _samples_per_pixel, full);
+}
+
+framecnt_t
+WaveView::desired_image_width () const
+{
+ /* compute how wide the image should be, in samples.
+ *
+ * We want at least 1 canvas width's worth, but if that
+ * represents less than 1/10th of a second, use 1/10th of
+ * a second instead.
+ *
+ * ..unless at high-zoom level 100ms would be more than 2^15px
+ * (cairo image limit), note that generate_image() uses twice this
+ * width (left/right of the center of the request range.
+ */
+
+ framecnt_t canvas_width_samples = _canvas->visible_area().width() * _samples_per_pixel;
+ const framecnt_t one_tenth_of_second = std::min (_region->session().frame_rate() / 10, (framecnt_t)floor (16383.0 / _samples_per_pixel));
+
+
+ if (canvas_width_samples > one_tenth_of_second) {
+ return canvas_width_samples;
+ }
+
+ return one_tenth_of_second;
+}
+
+void
+WaveView::queue_get_image (boost::shared_ptr<const ARDOUR::Region> region, framepos_t start, framepos_t end) const
+{
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: queue image from %2 .. %3\n", name, start, end));
+ boost::shared_ptr<WaveViewThreadRequest> req (new WaveViewThreadRequest);
+
+ req->type = WaveViewThreadRequest::Draw;
+ req->start = start;
+ req->end = end;
+ req->samples_per_pixel = _samples_per_pixel;
+ req->region = _region; /* weak ptr, to avoid storing a reference in the request queue */
+ req->channel = _channel;
+ req->height = _height;
+ req->fill_color = _fill_color;
+ req->amplitude = _region_amplitude * _amplitude_above_axis;
+ req->width = desired_image_width ();
+
+ if (current_request) {
+ /* this will stop rendering in progress (which might otherwise
+ be long lived) for any current request.
+ */
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+ if (current_request) {
+ current_request->cancel ();
+ }
+ }
+
+ start_drawing_thread ();
+
+ /* swap requests (protected by lock) */
+
+ {
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+ current_request = req;
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 now has current request %2\n", this, req));
+
+ if (request_queue.insert (this).second) {
+ /* this waveview was not already in the request queue, make sure we wake
+ the rendering thread in case it is asleep.
+ */
+ request_cond.signal ();
+ }
+ }
+}
+
+void
+WaveView::generate_image (boost::shared_ptr<WaveViewThreadRequest> req, bool in_render_thread) const
+{
+ if (!req->should_stop()) {
+
+ /* sample position is canonical here, and we want to generate
+ * an image that spans about 3x the canvas width. We get to that
+ * width by using an image sample count of the screen width added
+ * on each side of the desired image center.
+ */
+
+ const framepos_t center = req->start + ((req->end - req->start) / 2);
+ const framecnt_t image_samples = req->width;
+
+ /* we can request data from anywhere in the Source, between 0 and its length */
+
+ framepos_t sample_start = max (_region_start, (center - image_samples));
+ framepos_t sample_end = min (center + image_samples, region_end());
+ const int n_peaks = std::max (1LL, llrint (ceil ((sample_end - sample_start) / (req->samples_per_pixel))));
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: request %2 .. %3 width: %4; render %5 .. %6 (%7)\n", name, req->start, req->end, req->width, sample_start, sample_end, n_peaks));
+
+ assert (n_peaks > 0 && n_peaks < 32767);
+
+ boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
+
+ /* Note that Region::read_peaks() takes a start position based on an
+ offset into the Region's **SOURCE**, rather than an offset into
+ the Region itself.
+ */
+
+ framecnt_t peaks_read = _region->read_peaks (peaks.get(), n_peaks,
+ sample_start, sample_end - sample_start,
+ req->channel,
+ req->samples_per_pixel);
+
+ if (req->should_stop()) {
+ // cerr << "Request stopped after reading peaks\n";
+ return;
+ }
+
+ req->image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, req->height);
+
+ // http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create
+ // This function always returns a valid pointer, but it will return a pointer to a "nil" surface..
+ // but there's some evidence that req->image can be NULL.
+ // http://tracker.ardour.org/view.php?id=6478
+ assert (req->image);
+
+ /* make sure we record the sample positions that were actually used */
+ req->start = sample_start;
+ req->end = sample_end;
+
+ if (peaks_read > 0) {
+
+ /* region amplitude will have been used to generate the
+ * peak values already, but not the visual-only
+ * amplitude_above_axis. So apply that here before
+ * rendering.
+ */
+
+ if (_amplitude_above_axis != 1.0) {
+ for (framecnt_t i = 0; i < n_peaks; ++i) {
+ peaks[i].max *= _amplitude_above_axis;
+ peaks[i].min *= _amplitude_above_axis;
+ }
+ }
+
+ draw_image (req->image, peaks.get(), n_peaks, req);
+ } else {
+ draw_absent_image (req->image, peaks.get(), n_peaks);
+ }
+ } else {
+ // cerr << "Request stopped before image generation\n";
+ }
+
+ if (in_render_thread && !req->should_stop()) {
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("done with request for %1 at %2 CR %3 req %4 range %5 .. %6\n", this, g_get_monotonic_time(), current_request, req, req->start, req->end));
+ const_cast<WaveView*>(this)->ImageReady (); /* emit signal */
+ }
+
+ return;
+}
+
+/** Given a waveform that starts at window x-coordinate @param wave_origin
+ * and the first pixel that we will actually draw @param draw_start, return
+ * the offset into an image of the entire waveform that we will need to use.
+ *
+ * Note: most of our cached images are NOT of the entire waveform, this is just
+ * computationally useful when determining which the sample range span for
+ * the image we need.
+ */
+static inline double
+window_to_image (double wave_origin, double image_start)
+{
+ return image_start - wave_origin;
+}
+
+void
+WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
+{
+ assert (_samples_per_pixel != 0);
+
+ if (!_region) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("render %1 at %2\n", this, g_get_monotonic_time()));
+
+ /* a WaveView is intimately connected to an AudioRegion. It will
+ * display the waveform within the region, anywhere from the start of
+ * the region to its end.
+ *
+ * the area we've been aked to render may overlap with area covered
+ * by the region in any of the normal ways:
+ *
+ * - it may begin and end within the area covered by the region
+ * - it may start before and end after the area covered by region
+ * - it may start before and end within the area covered by the region
+ * - it may start within and end after the area covered by the region
+ * - it may be precisely coincident with the area covered by region.
+ *
+ * So let's start by determining the area covered by the region, in
+ * window coordinates. It begins at zero (in item coordinates for this
+ * waveview, and extends to region_length() / _samples_per_pixel.
+ */
+
+ Rect self = item_to_window (Rect (0.0, 0.0, region_length() / _samples_per_pixel, _height));
+
+ // cerr << name << " RENDER " << area << " self = " << self << endl;
+
+ /* Now lets get the intersection with the area we've been asked to draw */
+
+ Rect d = self.intersection (area);
+
+ if (!d) {
+ return;
+ }
+
+ Rect draw = d;
+
+ /* "draw" is now a rectangle that defines the rectangle we need to
+ * update/render the waveview into, in window coordinate space.
+ */
+
+ /* window coordinates - pixels where x=0 is the left edge of the canvas
+ * window. We round down in case we were asked to
+ * draw "between" pixels at the start and/or end.
+ */
+
+ double draw_start = floor (draw.x0);
+ const double draw_end = floor (draw.x1);
+
+ // cerr << "Need to draw " << draw_start << " .. " << draw_end << " vs. " << area << " and self = " << self << endl;
+
+ /* image coordnates: pixels where x=0 is the start of this waveview,
+ * wherever it may be positioned. thus image_start=N means "an image
+ * that begins N pixels after the start of region that this waveview is
+ * representing.
+ */
+
+ const framepos_t image_start = window_to_image (self.x0, draw_start);
+ const framepos_t image_end = window_to_image (self.x0, draw_end);
+
+ // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
+
+ /* sample coordinates - note, these are not subject to rounding error
+ *
+ * "sample_start = N" means "the first sample we need to represent is N
+ * samples after the first sample of the region"
+ */
+
+ framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
+ framepos_t sample_end = _region_start + (image_end * _samples_per_pixel);
+
+ // cerr << "Sample space: " << sample_start << " .. " << sample_end << " @ " << _samples_per_pixel << " rs = " << _region_start << endl;
+
+ /* sample_start and sample_end are bounded by the region
+ * limits. sample_start, because of the was just computed, must already
+ * be greater than or equal to the _region_start value.
+ */
+
+ sample_end = min (region_end(), sample_end);
+
+ // cerr << debug_name() << " will need image spanning " << sample_start << " .. " << sample_end << " region spans " << _region_start << " .. " << region_end() << endl;
+
+ double image_origin_in_self_coordinates;
+ boost::shared_ptr<WaveViewCache::Entry> image_to_draw;
+
+ Glib::Threads::Mutex::Lock lci (current_image_lock);
+ if (_current_image) {
+
+ /* check it covers the right sample range */
+
+ if (_current_image->start > sample_start || _current_image->end < sample_end) {
+ /* doesn't cover the area we need ... reset */
+ _current_image.reset ();
+ } else {
+ /* timestamp our continuing use of this image/cache entry */
+ images->use (_region->audio_source (_channel), _current_image);
+ image_to_draw = _current_image;
+ }
+ }
+
+ if (!image_to_draw) {
+
+ /* look it up */
+
+ bool full_image;
+ image_to_draw = get_image (sample_start, sample_end, full_image);
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 image to draw = %2 (full? %3)\n", name, image_to_draw, full_image));
+
+ if (!image_to_draw) {
+ /* image not currently available. A redraw will be scheduled
+ when it is ready.
+ */
+ return;
+ }
+
+ if (full_image) {
+ /* found an image that covers our entire sample range,
+ * so keep a reference to it.
+ */
+ _current_image = image_to_draw;
+ }
+ }
+
+ /* compute the first pixel of the image that should be used when we
+ * render the specified range.
+ */
+
+ image_origin_in_self_coordinates = (image_to_draw->start - _region_start) / _samples_per_pixel;
+
+ if (_start_shift && (sample_start == _region_start) && (self.x0 == draw.x0)) {
+ /* we are going to draw the first pixel for this region, but
+ we may not want this to overlap a border around the
+ waveform. If so, _start_shift will be set.
+ */
+ //cerr << name.substr (23) << " ss = " << sample_start << " rs = " << _region_start << " sf = " << _start_shift << " ds = " << draw_start << " self = " << self << " draw = " << draw << endl;
+ //draw_start += _start_shift;
+ //image_origin_in_self_coordinates += _start_shift;
+ }
+
+ /* the image may only be a best-effort ... it may not span the entire
+ * range requested, though it is guaranteed to cover the start. So
+ * determine how many pixels we can actually draw.
+ */
+
+ double draw_width;
+
+ if (image_to_draw != _current_image) {
+ lci.release ();
+
+ /* the image is guaranteed to start at or before
+ * draw_start. But if it starts before draw_start, that reduces
+ * the maximum available width we can render with.
+ *
+ * so .. clamp the draw width to the smaller of what we need to
+ * draw or the available width of the image.
+ */
+
+ draw_width = min ((double) image_to_draw->image->get_width(), (draw_end - draw_start));
+
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 draw just %2 of %3 @ %8 (iwidth %4 off %5 img @ %6 rs @ %7)\n", name, draw_width, (draw_end - draw_start),
+ image_to_draw->image->get_width(), image_origin_in_self_coordinates,
+ image_to_draw->start, _region_start, draw_start));
+ } else {
+ draw_width = draw_end - draw_start;
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("use current image, span entire render width %1..%2\n", draw_start, draw_end));
+ }
+
+ context->rectangle (draw_start, draw.y0, draw_width, draw.height());
+
+ /* round image origin position to an exact pixel in device space to
+ * avoid blurring
+ */
+
+ double x = self.x0 + image_origin_in_self_coordinates;
+ double y = self.y0;