+ } else {
+ const int height_zero = floor( _height * .5);
+
+ for (int i = 0; i < n_peaks; ++i) {
+
+ /* waveform line */
+
+ if (tips[i].spread >= 2.0) {
+ wave_context->move_to (i, tips[i].top);
+ wave_context->line_to (i, tips[i].bot);
+ }
+
+ /* draw square waves and other discontiguous points clearly */
+ if (i > 0) {
+ if (tips[i-1].top + 2 < tips[i].top) {
+ wave_context->move_to (i-1, tips[i-1].top);
+ wave_context->line_to (i-1, (tips[i].bot + tips[i-1].top)/2);
+ wave_context->move_to (i, (tips[i].bot + tips[i-1].top)/2);
+ wave_context->line_to (i, tips[i].top);
+ } else if (tips[i-1].bot > tips[i].bot + 2) {
+ wave_context->move_to (i-1, tips[i-1].bot);
+ wave_context->line_to (i-1, (tips[i].top + tips[i-1].bot)/2);
+ wave_context->move_to (i, (tips[i].top + tips[i-1].bot)/2);
+ wave_context->line_to (i, tips[i].bot);
+ }
+ }
+
+ /* zero line, show only if there is enough spread
+ or the waveform line does not cross zero line */
+
+ if (show_zero_line() && ((tips[i].spread >= 5.0) || (tips[i].top > height_zero ) || (tips[i].bot < height_zero)) ) {
+ zero_context->move_to (i, height_zero);
+ zero_context->rel_line_to (1.0, 0);
+ }
+
+ if (tips[i].spread > 1.0) {
+ bool clipped = false;
+ /* outline/clip indicators */
+ if (_global_show_waveform_clipping && tips[i].clip_max) {
+ clip_context->move_to (i, tips[i].top);
+ /* clip-indicating upper terminal line */
+ clip_context->rel_line_to (0, min (clip_height, ceil(tips[i].spread + 0.5)));
+ clipped = true;
+ }
+
+ if (_global_show_waveform_clipping && tips[i].clip_min) {
+ clip_context->move_to (i, tips[i].bot);
+ /* clip-indicating lower terminal line */
+ clip_context->rel_line_to (0, - min (clip_height, ceil(tips[i].spread + 0.5)));
+ clipped = true;
+ }
+
+ if (!clipped && tips[i].spread > 2.0) {
+ /* only draw the outline if the spread
+ implies 3 or more pixels (so that we see 1
+ white pixel in the middle).
+ */
+ outline_context->move_to (i, tips[i].bot);
+ /* normal lower terminal dot; line moves up */
+ outline_context->rel_line_to (0, -1.0);
+
+ outline_context->move_to (i, tips[i].top);
+ /* normal upper terminal dot, line moves down */
+ outline_context->rel_line_to (0, 1.0);
+ }
+ } else {
+ bool clipped = false;
+ /* outline/clip indicator */
+ if (_global_show_waveform_clipping && (tips[i].clip_max || tips[i].clip_min)) {
+ clip_context->move_to (i, tips[i].top);
+ /* clip-indicating upper / lower terminal line */
+ clip_context->rel_line_to (0, 1.0);
+ clipped = true;
+ }
+
+ if (!clipped) {
+ /* special case where only 1 pixel of
+ * the waveform line is drawn (and
+ * nothing else).
+ *
+ * we draw a 1px "line", pretending
+ * that the span is 1.0 (whether it is
+ * zero or 1.0)
+ */
+ wave_context->move_to (i, tips[i].top);
+ wave_context->rel_line_to (0, 1.0);
+ }
+ }
+ }
+
+ wave_context->stroke ();
+ outline_context->stroke ();
+ clip_context->stroke ();
+ zero_context->stroke ();
+ }
+
+ if (req->should_stop()) {
+ return;
+ }
+
+ Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
+
+ /* Here we set a source colour and use the various components as a mask. */
+
+ if (gradient_depth() != 0.0) {
+
+ Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _height));
+
+ double stops[3];
+
+ double r, g, b, a;
+
+ if (_shape == Rectified) {
+ stops[0] = 0.1;
+ stops[1] = 0.3;
+ stops[2] = 0.9;
+ } else {
+ stops[0] = 0.1;
+ stops[1] = 0.5;
+ stops[2] = 0.9;
+ }
+
+ color_to_rgba (_fill_color, r, g, b, a);
+ gradient->add_color_stop_rgba (stops[1], r, g, b, a);
+ /* generate a new color for the middle of the gradient */
+ double h, s, v;
+ color_to_hsv (_fill_color, h, s, v);
+ /* change v towards white */
+ v *= 1.0 - gradient_depth();
+ Color center = hsva_to_color (h, s, v, a);
+ color_to_rgba (center, r, g, b, a);
+
+ gradient->add_color_stop_rgba (stops[0], r, g, b, a);
+ gradient->add_color_stop_rgba (stops[2], r, g, b, a);
+
+ context->set_source (gradient);
+ } else {
+ set_source_rgba (context, _fill_color);
+ }
+
+ if (req->should_stop()) {
+ return;
+ }
+
+ context->mask (images.wave, 0, 0);
+ context->fill ();
+
+ set_source_rgba (context, _outline_color);
+ context->mask (images.outline, 0, 0);
+ context->fill ();
+
+ set_source_rgba (context, _clip_color);
+ context->mask (images.clip, 0, 0);
+ context->fill ();
+
+ set_source_rgba (context, _zero_color);
+ context->mask (images.zero, 0, 0);
+ context->fill ();
+}
+
+boost::shared_ptr<WaveViewCache::Entry>
+WaveView::cache_request_result (boost::shared_ptr<WaveViewThreadRequest> req) const
+{
+ if (!req->image) {
+ cerr << "asked to cache null image!!!\n";
+ return boost::shared_ptr<WaveViewCache::Entry> ();
+ }
+
+ boost::shared_ptr<WaveViewCache::Entry> ret (new WaveViewCache::Entry (req->channel,
+ req->height,
+ req->amplitude,
+ req->fill_color,
+ req->samples_per_pixel,
+ req->start,
+ req->end,
+ req->image));
+ images->add (_region->audio_source (_channel), ret);
+
+ /* consolidate cache first (removes fully-contained
+ * duplicate images)
+ */
+
+ images->consolidate_image_cache (_region->audio_source (_channel),
+ req->channel, req->height, req->amplitude,
+ req->fill_color, req->samples_per_pixel);
+
+ return ret;
+}
+
+boost::shared_ptr<WaveViewCache::Entry>
+WaveView::get_image (framepos_t start, framepos_t end, bool& full_image) const
+{
+ boost::shared_ptr<WaveViewCache::Entry> ret;
+
+ full_image = true;
+
+ /* this is called from a ::render() call, when we need an image to
+ draw with.
+ */
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 needs image from %2 .. %3\n", name, start, end));
+
+
+ {
+ Glib::Threads::Mutex::Lock lmq (request_queue_lock);
+
+ /* if there's a draw request outstanding, check to see if we
+ * have an image there. if so, use it (and put it in the cache
+ * while we're here.
+ */
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 CR %2 stop? %3 image %4\n", this, current_request,
+ (current_request ? current_request->should_stop() : false),
+ (current_request ? current_request->image : 0)));
+
+ if (current_request && !current_request->should_stop() && current_request->image) {
+
+ /* put the image into the cache so that other
+ * WaveViews can use it if it is useful
+ */
+
+ if (current_request->start <= start && current_request->end >= end) {
+
+ ret.reset (new WaveViewCache::Entry (current_request->channel,
+ current_request->height,
+ current_request->amplitude,
+ current_request->fill_color,
+ current_request->samples_per_pixel,
+ current_request->start,
+ current_request->end,
+ current_request->image));
+
+ cache_request_result (current_request);
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: got image from completed request, spans %2..%3\n",
+ name, current_request->start, current_request->end));
+ }
+
+ /* drop our handle on the current request */
+ current_request.reset ();
+ }
+ }
+
+ if (!ret) {
+
+ /* no current image draw request, so look in the cache */
+
+ 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.
+ */
+
+ framecnt_t canvas_width_samples = _canvas->visible_area().width() * _samples_per_pixel;
+ const framecnt_t one_tenth_of_second = _region->session().frame_rate() / 10;
+
+ 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
+{
+ 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.
+ */
+ 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 = llrintf ((sample_end - sample_start)/ (req->samples_per_pixel));
+
+ 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);
+
+ req->image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, n_peaks, req->height);
+ /* 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;