attempt to correctly respond to changes in _amplitude_above_axis in ArdourCanvas...
[ardour.git] / libs / canvas / wave_view.cc
index 3a04961d129bee9c91c5afd7bea0e411cb8f97de..f4e56cb1d53f6fb2af75ca26a69276fdd9293efc 100644 (file)
@@ -37,6 +37,7 @@
 #include "ardour/lmath.h"
 #include "ardour/audioregion.h"
 #include "ardour/audiosource.h"
+#include "ardour/session.h"
 
 #include "canvas/canvas.h"
 #include "canvas/colors.h"
@@ -44,6 +45,8 @@
 #include "canvas/utils.h"
 #include "canvas/wave_view.h"
 
+#include "evoral/Range.hpp"
+
 #include <gdkmm/general.h>
 
 #include "gtkmm2ext/gui_thread.h"
@@ -87,7 +90,6 @@ WaveView::WaveView (Canvas* c, boost::shared_ptr<ARDOUR::AudioRegion> region)
        , _amplitude_above_axis (1.0)
        , _region_amplitude (region->scale_amplitude ())
        , _start_shift (0.0)
-       , idle_queued (false)
        , _region_start (region->start())
        , get_image_in_thread (false)
        , always_get_image_in_thread (false)
@@ -121,7 +123,6 @@ WaveView::WaveView (Item* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
        , _amplitude_above_axis (1.0)
        , _region_amplitude (region->scale_amplitude ())
        , _start_shift (0.0)
-       , idle_queued (false)
        , _region_start (region->start())
        , get_image_in_thread (false)
        , always_get_image_in_thread (false)
@@ -151,6 +152,7 @@ WaveView::debug_name() const
 void
 WaveView::image_ready ()
 {
+        DEBUG_TRACE (DEBUG::WaveView, string_compose ("queue draw for %1 at %2 (vis = %3 CR %4)\n", this, g_get_monotonic_time(), visible(), current_request));
        redraw ();
 }
 
@@ -256,6 +258,7 @@ WaveView::set_clip_level (double dB)
 void
 WaveView::invalidate_image_cache ()
 {
+        DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 invalidates image cache and cancels current request\n", this));
        cancel_my_render_request ();
        _current_image.reset ();
 }
@@ -396,7 +399,7 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
           has been scaled by scale_amplitude() already.
        */
 
-       const double clip_level = _clip_level * _region_amplitude;
+       const double clip_level = _clip_level * req->amplitude;
 
        if (_shape == WaveView::Rectified) {
 
@@ -572,7 +575,7 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                outline_context->stroke ();
 
        } else {
-               const double height_2 = _height * .5;
+               const int height_zero = floor( _height * .5);
 
                for (int i = 0; i < n_peaks; ++i) {
 
@@ -598,10 +601,11 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                                }
                        }
 
-                       /* zero line, show only if there is enough spread */
+                       /* zero line, show only if there is enough spread 
+                       or the waveform line does not cross zero line */
 
-                       if (tips[i].spread >= 5.0 && show_zero_line()) {
-                               zero_context->move_to (i, floor(height_2));
+                       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);
                        }
 
@@ -622,7 +626,11 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                                        clipped = true;
                                }
 
-                               if (!clipped) {
+                               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);
@@ -642,10 +650,15 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
                                }
 
                                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);
-                                       /* special case where outline only is drawn.
-                                        * we draw a 1px "line", pretending that the span is 1.0
-                                       */
                                        wave_context->rel_line_to (0, 1.0);
                                }
                        }
@@ -724,6 +737,7 @@ WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peak
 boost::shared_ptr<WaveViewCache::Entry>
 WaveView::cache_request_result (boost::shared_ptr<WaveViewThreadRequest> req) const
 {
+       
        boost::shared_ptr<WaveViewCache::Entry> ret (new WaveViewCache::Entry (req->channel,
                                                                               req->height,
                                                                               req->amplitude,
@@ -766,6 +780,10 @@ WaveView::get_image (framepos_t start, framepos_t end, bool& full_image) const
                 * 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) {
 
@@ -809,7 +827,7 @@ WaveView::get_image (framepos_t start, framepos_t end, bool& full_image) const
        if (!ret || !full_image) {
 
                if ((rendered && get_image_in_thread) || always_get_image_in_thread) {
-
+                        
                        DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1: generating image in caller thread\n", name));
                        
                        boost::shared_ptr<WaveViewThreadRequest> req (new WaveViewThreadRequest);
@@ -820,10 +838,10 @@ WaveView::get_image (framepos_t start, framepos_t end, bool& full_image) const
                        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->width = _canvas->visible_area().width();
                        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) */
                        
@@ -864,6 +882,26 @@ WaveView::get_image_from_cache (framepos_t start, framepos_t end, bool& full) co
                                     _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
 {
@@ -875,10 +913,10 @@ WaveView::queue_get_image (boost::shared_ptr<const ARDOUR::Region> region, frame
        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->width = _canvas->visible_area().width();
        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
@@ -894,44 +932,31 @@ WaveView::queue_get_image (boost::shared_ptr<const ARDOUR::Region> region, frame
        {
                Glib::Threads::Mutex::Lock lm (request_queue_lock);
                current_request = req;
-       }
 
-       /* this is always called from the GUI thread (there is only one), and
-        * the same thread runs the idle callback chain. thus we do not need
-        * any locks to protect idle_queued - it is only ever set or read in
-        * the unitary GUI thread.
-        */
-       
-       if (!idle_queued) {
-               Glib::signal_idle().connect (sigc::mem_fun (*this, &WaveView::idle_send_request));
-               idle_queued = true;
-       }
-}
+                DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 now has current request %2\n", this, req));
 
-bool
-WaveView::idle_send_request () const
-{
-       Glib::Threads::Mutex::Lock lm (request_queue_lock);
-
-       request_queue.insert (this);
-       request_cond.signal (); /* wake thread - must be done while holding lock */
-       idle_queued = false;
-       
-       return false; /* do not call from idle again */
+                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 twice the canvas width
+                * 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 * req->samples_per_pixel; /* one canvas width */
+               const framecnt_t image_samples = req->width;
                
                /* we can request data from anywhere in the Source, between 0 and its length
                 */
@@ -959,6 +984,12 @@ WaveView::generate_image (boost::shared_ptr<WaveViewThreadRequest> req, bool in_
                
                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;
@@ -973,6 +1004,7 @@ WaveView::generate_image (boost::shared_ptr<WaveViewThreadRequest> req, bool in_
        }
        
        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 */
        }
        
@@ -1002,6 +1034,8 @@ WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) cons
                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.
@@ -1428,6 +1462,8 @@ WaveView::cancel_my_render_request () const
        
        request_queue.erase (this);
        current_request.reset ();
+        DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 now has no request %2\n", this));
+
 }
 
 void
@@ -1488,6 +1524,8 @@ WaveView::drawing_thread ()
                requestor = *(request_queue.begin());
                request_queue.erase (request_queue.begin());
 
+                DEBUG_TRACE (DEBUG::WaveView, string_compose ("start request for %1 at %2\n", requestor, g_get_monotonic_time()));
+
                boost::shared_ptr<WaveViewThreadRequest> req = requestor->current_request;
 
                if (!req) {
@@ -1565,25 +1603,25 @@ WaveViewCache::lookup_image (boost::shared_ptr<ARDOUR::AudioSource> src,
                        continue;
                }
 
-               if (end <= e->end && start >= e->start) {
-                       /* found an image that covers the range we need */
+                switch (Evoral::coverage (start, end, e->start, e->end)) {
+                case Evoral::OverlapExternal:  /* required range is inside image range */
                        DEBUG_TRACE (DEBUG::WaveView, string_compose ("found image spanning %1..%2 covers %3..%4\n",
                                                                      e->start, e->end, start, end));
                        use (src, e);
-                       full_coverage = true;
-                       return e;
-               }
-
-               if (start >= e->start) {
-                       /* found an image that covers the start, but not the
-                        * end. See if it is longer than any other similar
-                        * partial image that we've found so far.
-                        */
-
-                       if ((e->end - e->start) > max_coverage) {
-                               best_partial = e;
-                               max_coverage = e->end - e->start;
-                       }
+                        full_coverage = true;
+                        return e;
+
+                case Evoral::OverlapStart: /* required range start is covered by image range */
+                        if ((e->end - start) > max_coverage) {
+                                best_partial = e;
+                                max_coverage = e->end - start;
+                        }
+                        break;
+
+                case Evoral::OverlapNone:
+                case Evoral::OverlapEnd:
+                case Evoral::OverlapInternal:
+                        break;
                }
        }