/* Copyright (C) 2011-2013 Paul Davis Author: Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __CANVAS_WAVE_VIEW_H__ #define __CANVAS_WAVE_VIEW_H__ #include #include #include #include "pbd/properties.h" #include "ardour/types.h" #include #include "canvas/visibility.h" #include "canvas/item.h" #include "canvas/fill.h" #include "canvas/outline.h" namespace ARDOUR { class AudioRegion; } namespace Gdk { class Pixbuf; } class WaveViewTest; namespace ArdourCanvas { struct LIBCANVAS_API WaveViewThreadRequest { public: enum RequestType { Quit, Cancel, Draw }; WaveViewThreadRequest () : stop (0) {} bool should_stop () const { return (bool) g_atomic_int_get (const_cast(&stop)); } void cancel() { g_atomic_int_set (&stop, 1); } RequestType type; framepos_t start; framepos_t end; double width; double height; double samples_per_pixel; uint16_t channel; double amplitude; Color fill_color; boost::weak_ptr region; /* resulting image, after request has been satisfied */ Cairo::RefPtr image; private: gint stop; /* intended for atomic access */ }; class LIBCANVAS_API WaveView; class LIBCANVAS_API WaveViewCache { public: WaveViewCache(); ~WaveViewCache(); struct Entry { /* these properties define the cache entry as unique. If an image is in use by a WaveView and any of these properties are modified on the WaveView, the image can no longer be used (or may no longer be usable for start/end parameters). It will remain in the cache until flushed for some reason (typically the cache is full). */ int channel; Coord height; float amplitude; Color fill_color; double samples_per_pixel; framepos_t start; framepos_t end; /* the actual image referred to by the cache entry */ Cairo::RefPtr image; /* last time the cache entry was used */ uint64_t timestamp; Entry (int chan, Coord hght, float amp, Color fcl, double spp, framepos_t strt, framepos_t ed, Cairo::RefPtr img) : channel (chan) , height (hght) , amplitude (amp) , fill_color (fcl) , samples_per_pixel (spp) , start (strt) , end (ed) , image (img) {} }; uint64_t image_cache_threshold () const { return _image_cache_threshold; } void set_image_cache_threshold (uint64_t); void clear_cache (); void add (boost::shared_ptr, boost::shared_ptr); void use (boost::shared_ptr, boost::shared_ptr); void consolidate_image_cache (boost::shared_ptr, int channel, Coord height, float amplitude, Color fill_color, double samples_per_pixel); boost::shared_ptr lookup_image (boost::shared_ptr, framepos_t start, framepos_t end, int _channel, Coord height, float amplitude, Color fill_color, double samples_per_pixel, bool& full_image); private: /* an unsorted, unindexd collection of cache entries associated with a particular AudioSource. All cache Entries in the collection share the AudioSource in common, but represent different parameter settings (e.g. height, color, samples per pixel etc.) */ typedef std::vector > CacheLine; /* Indexed, non-sortable structure used to lookup images associated * with a particular AudioSource */ typedef std::map ,CacheLine> ImageCache; ImageCache cache_map; /* Linear, sortable structure used when we need to do a timestamp-based * flush of entries from the cache. */ typedef std::pair,boost::shared_ptr > ListEntry; typedef std::vector CacheList; struct SortByTimestamp { bool operator() (const WaveViewCache::ListEntry& a, const WaveViewCache::ListEntry& b) { return a.second->timestamp < b.second->timestamp; } }; friend struct SortByTimestamp; uint64_t image_cache_size; uint64_t _image_cache_threshold; uint64_t compute_image_cache_size (); void cache_flush (); bool cache_full (); }; class LIBCANVAS_API WaveView : public Item, public sigc::trackable { public: enum Shape { Normal, Rectified }; std::string debug_name() const; /* final ImageSurface rendered with colours */ Cairo::RefPtr _image; PBD::Signal0 ImageReady; /* Displays a single channel of waveform data for the given Region. x = 0 in the waveview corresponds to the first waveform datum taken from region->start() samples into the source data. x = N in the waveview corresponds to the (N * spp)'th sample measured from region->start() into the source data. when drawing, we will map the zeroth-pixel of the waveview into a window. The waveview itself contains a set of pre-rendered Cairo::ImageSurfaces that cache sections of the display. This is filled on-demand and never cleared until something explicitly marks the cache invalid (such as a change in samples_per_pixel, the log scaling, rectified or other view parameters). */ WaveView (Canvas *, boost::shared_ptr); WaveView (Item*, boost::shared_ptr); ~WaveView (); void render (Rect const & area, Cairo::RefPtr) const; void compute_bounding_box () const; void set_samples_per_pixel (double); void set_height (Distance); void set_channel (int); void set_region_start (ARDOUR::frameoffset_t); /** Change the first position drawn by @param pixels. * @param pixels must be positive. This is used by * AudioRegionViews in Ardour to avoid drawing the * first pixel of a waveform, and exists in case * there are uses for WaveView where we do not * want this behaviour. */ void set_start_shift (double pixels); void set_fill_color (Color); void set_outline_color (Color); void region_resized (); void gain_changed (); void set_show_zero_line (bool); bool show_zero_line() const { return _show_zero; } void set_zero_color (Color); void set_clip_color (Color); void set_logscaled (bool); void set_gradient_depth (double); double gradient_depth() const { return _gradient_depth; } void set_shape (Shape); void set_always_get_image_in_thread (bool yn); /* currently missing because we don't need them (yet): set_shape_independent(); set_logscaled_independent() */ static void set_global_gradient_depth (double); static void set_global_logscaled (bool); static void set_global_shape (Shape); static void set_global_show_waveform_clipping (bool); static double global_gradient_depth() { return _global_gradient_depth; } static bool global_logscaled() { return _global_logscaled; } static Shape global_shape() { return _global_shape; } void set_amplitude_above_axis (double v); double amplitude_above_axis () const { return _amplitude_above_axis; } static void set_clip_level (double dB); static PBD::Signal0 ClipLevelChanged; static void start_drawing_thread (); static void stop_drawing_thread (); static void set_image_cache_size (uint64_t); #ifdef CANVAS_COMPATIBILITY void*& property_gain_src () { return _foo_void; } void*& property_gain_function () { return _foo_void; } private: void* _foo_void; #endif private: friend class ::WaveViewTest; friend class WaveViewThreadClient; void invalidate_image_cache (); boost::shared_ptr _region; int _channel; double _samples_per_pixel; Coord _height; bool _show_zero; Color _zero_color; Color _clip_color; bool _logscaled; Shape _shape; double _gradient_depth; bool _shape_independent; bool _logscaled_independent; bool _gradient_depth_independent; double _amplitude_above_axis; float _region_amplitude; double _start_shift; /** The `start' value to use for the region; we can't use the region's * value as the crossfade editor needs to alter it. */ ARDOUR::frameoffset_t _region_start; /** Under almost conditions, this is going to return _region->length(), * but if _region_start has been reset, then we need * to use this modified computation. */ ARDOUR::framecnt_t region_length() const; /** Under almost conditions, this is going to return _region->start() + * _region->length(), but if _region_start has been reset, then we need * to use this modified computation. */ ARDOUR::framepos_t region_end() const; /** If true, calls to get_image() will render a missing wave image in the calling thread. Generally set to false, but true after a call to set_height(). */ mutable bool get_image_in_thread; /** If true, calls to get_image() will render a missing wave image in the calling thread. Set true for waveviews we expect to keep updating (e.g. while recording) */ bool always_get_image_in_thread; /** Set to true by render(). Used so that we know if the wave view * has actually been displayed on screen. ::set_height() when this * is true does not use get_image_in_thread, because it implies * that the height is being set BEFORE the waveview is drawn. */ mutable bool rendered; PBD::ScopedConnectionList invalidation_connection; PBD::ScopedConnection image_ready_connection; static double _global_gradient_depth; static bool _global_logscaled; static Shape _global_shape; static bool _global_show_waveform_clipping; static double _clip_level; static PBD::Signal0 VisualPropertiesChanged; void handle_visual_property_change (); void handle_clip_level_change (); boost::shared_ptr get_image (framepos_t start, framepos_t end, bool& full_image) const; boost::shared_ptr get_image_from_cache (framepos_t start, framepos_t end, bool& full_image) const; struct LineTips { double top; double bot; double spread; bool clip_max; bool clip_min; LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {} }; ArdourCanvas::Coord y_extent (double) const; void compute_tips (ARDOUR::PeakData const & peak, LineTips& tips) const; ARDOUR::framecnt_t desired_image_width () const; void draw_image (Cairo::RefPtr&, ARDOUR::PeakData*, int n_peaks, boost::shared_ptr) const; void draw_absent_image (Cairo::RefPtr&, ARDOUR::PeakData*, int) const; void cancel_my_render_request () const; void queue_get_image (boost::shared_ptr region, framepos_t start, framepos_t end) const; void generate_image (boost::shared_ptr, bool in_render_thread) const; boost::shared_ptr cache_request_result (boost::shared_ptr req) const; void image_ready (); mutable boost::shared_ptr _current_image; mutable boost::shared_ptr current_request; static WaveViewCache* images; static void drawing_thread (); static gint drawing_thread_should_quit; static Glib::Threads::Mutex request_queue_lock; static Glib::Threads::Mutex current_image_lock; static Glib::Threads::Cond request_cond; static Glib::Threads::Thread* _drawing_thread; typedef std::set DrawingRequestQueue; static DrawingRequestQueue request_queue; }; } // namespace ArdourCanvas #endif // __CANVAS_WAVE_VIEW_H__