diff options
author | Robin Gareus <robin@gareus.org> | 2017-07-17 20:12:33 +0200 |
---|---|---|
committer | Robin Gareus <robin@gareus.org> | 2017-07-17 21:06:04 +0200 |
commit | beb73edf553dadbbef7a02727c04f51294b0f4e6 (patch) | |
tree | c706e536fb07e69bb2f050f9541784a41d65366c /libs/waveview/waveview | |
parent | 601c317d70a03190257577bd867cefc2c70d3275 (diff) |
Purify libcanvas, remove libardour dependency
A canvas is just a canvas. Move WaveView into its own library.
Diffstat (limited to 'libs/waveview/waveview')
-rw-r--r-- | libs/waveview/waveview/debug.h | 35 | ||||
-rw-r--r-- | libs/waveview/waveview/visibility.h | 45 | ||||
-rw-r--r-- | libs/waveview/waveview/wave_view.h | 268 | ||||
-rw-r--r-- | libs/waveview/waveview/wave_view_private.h | 365 |
4 files changed, 713 insertions, 0 deletions
diff --git a/libs/waveview/waveview/debug.h b/libs/waveview/waveview/debug.h new file mode 100644 index 0000000000..df93085721 --- /dev/null +++ b/libs/waveview/waveview/debug.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2011-2013 Paul Davis + Author: Carl Hetherington <cth@carlh.net> + + 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 _WAVEVIEW_DEBUG_H_ +#define _WAVEVIEW_DEBUG_H_ + +#include <sys/time.h> +#include <map> +#include "pbd/debug.h" + +#include "waveview/visibility.h" + +namespace PBD { + namespace DEBUG { + LIBWAVEVIEW_API extern DebugBits WaveView; + } +} + +#endif diff --git a/libs/waveview/waveview/visibility.h b/libs/waveview/waveview/visibility.h new file mode 100644 index 0000000000..827ffd5f0e --- /dev/null +++ b/libs/waveview/waveview/visibility.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 __libwaveview_visibility_h__ +#define __libwaveview_visibility_h__ + +#if defined(COMPILER_MSVC) + #define LIBWAVEVIEW_DLL_IMPORT __declspec(dllimport) + #define LIBWAVEVIEW_DLL_EXPORT __declspec(dllexport) + #define LIBWAVEVIEW_DLL_LOCAL +#else + #define LIBWAVEVIEW_DLL_IMPORT __attribute__ ((visibility ("default"))) + #define LIBWAVEVIEW_DLL_EXPORT __attribute__ ((visibility ("default"))) + #define LIBWAVEVIEW_DLL_LOCAL __attribute__ ((visibility ("hidden"))) +#endif + +#ifdef LIBWAVEVIEW_STATIC // libcanvas is not a DLL +#define LIBWAVEVIEW_API +#define LIBWAVEVIEW_LOCAL +#else + #ifdef LIBWAVEVIEW_DLL_EXPORTS // defined if we are building the libcanvas DLL (instead of using it) + #define LIBWAVEVIEW_API LIBWAVEVIEW_DLL_EXPORT + #else + #define LIBWAVEVIEW_API LIBWAVEVIEW_DLL_IMPORT + #endif + #define LIBWAVEVIEW_LOCAL LIBWAVEVIEW_DLL_LOCAL +#endif + +#endif /* __libwaveview_visibility_h__ */ diff --git a/libs/waveview/waveview/wave_view.h b/libs/waveview/waveview/wave_view.h new file mode 100644 index 0000000000..4b1c8eb42b --- /dev/null +++ b/libs/waveview/waveview/wave_view.h @@ -0,0 +1,268 @@ +/* + Copyright (C) 2011-2013 Paul Davis + Copyright (C) 2017 Tim Mayberry + Author: Carl Hetherington <cth@carlh.net> + + 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 _WAVEVIEW_WAVE_VIEW_H_ +#define _WAVEVIEW_WAVE_VIEW_H_ + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> + +#include <glibmm/refptr.h> + +#include "ardour/types.h" +#include "canvas/item.h" +#include "waveview/visibility.h" + +namespace ARDOUR { + class AudioRegion; +} + +namespace Gdk { + class Pixbuf; +} + +namespace ArdourWaveView { + +class WaveViewCacheGroup; +class WaveViewDrawRequest; +class WaveViewDrawRequestQueue; +class WaveViewImage; +class WaveViewProperties; +class WaveViewDrawingThread; + +class LIBWAVEVIEW_API WaveView : public ArdourCanvas::Item, public sigc::trackable +{ +public: + enum Shape { Normal, Rectified }; + + std::string debug_name () const; + + /* 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 (ArdourCanvas::Canvas*, boost::shared_ptr<ARDOUR::AudioRegion>); + WaveView (Item*, boost::shared_ptr<ARDOUR::AudioRegion>); + ~WaveView (); + + virtual void prepare_for_render (ArdourCanvas::Rect const& window_area) const; + + virtual void render (ArdourCanvas::Rect const & area, Cairo::RefPtr<Cairo::Context>) const; + + void compute_bounding_box () const; + + void set_samples_per_pixel (double); + void set_height (ArdourCanvas::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 (Gtkmm2ext::Color); + void set_outline_color (Gtkmm2ext::Color); + + void region_resized (); + void gain_changed (); + + void set_show_zero_line (bool); + bool show_zero_line () const; + + void set_zero_color (Gtkmm2ext::Color); + void set_clip_color (Gtkmm2ext::Color); + void set_logscaled (bool); + + void set_gradient_depth (double); + double gradient_depth () const; + + 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; + + static void set_clip_level (double dB); + static PBD::Signal0<void> 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 WaveViewThreadClient; + friend class WaveViewDrawingThread; + + boost::shared_ptr<ARDOUR::AudioRegion> _region; + + boost::scoped_ptr<WaveViewProperties> _props; + + mutable boost::shared_ptr<WaveViewImage> _image; + + mutable boost::shared_ptr<WaveViewCacheGroup> _cache_group; + + bool _shape_independent; + bool _logscaled_independent; + bool _gradient_depth_independent; + + /** 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; + + /** + * _image stays non-null after the first time it is set + */ + bool rendered () const { return _image.get(); } + + bool draw_image_in_gui_thread () const; + + /** If true, calls to render() will render a missing wave image in the GUI + * thread. Generally set to false, but true after a call to set_height(). + */ + mutable bool _draw_image_in_gui_thread; + + /** If true, calls to render() will render a missing wave image in the GUI + * thread. Set true for waveviews we expect to keep updating (e.g. while + * recording) + */ + bool _always_draw_image_in_gui_thread; + + void init(); + + mutable boost::shared_ptr<WaveViewDrawRequest> current_request; + + PBD::ScopedConnectionList invalidation_connection; + + static double _global_gradient_depth; + static bool _global_logscaled; + static Shape _global_shape; + static bool _global_show_waveform_clipping; + static double _global_clip_level; + + static PBD::Signal0<void> VisualPropertiesChanged; + + void handle_visual_property_change (); + void handle_clip_level_change (); + + 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) {} + }; + + static ArdourCanvas::Coord y_extent (double, Shape const, double const height); + + static void compute_tips (ARDOUR::PeakData const& peak, LineTips& tips, double const effective_height); + + static void draw_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int n_peaks, + boost::shared_ptr<WaveViewDrawRequest>); + static void draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int); + + ARDOUR::framecnt_t optimal_image_width_samples () const; + + void set_image (boost::shared_ptr<WaveViewImage> img) const; + + // @return true if item area intersects with draw area + bool get_item_and_draw_rect_in_window_coords (ArdourCanvas::Rect const& canvas_rect, + ArdourCanvas::Rect& item_area, + ArdourCanvas::Rect& draw_rect) const; + + boost::shared_ptr<WaveViewDrawRequest> create_draw_request (WaveViewProperties const&) const; + + void queue_draw_request (boost::shared_ptr<WaveViewDrawRequest> const&) const; + + static void process_draw_request (boost::shared_ptr<WaveViewDrawRequest>); + + boost::shared_ptr<WaveViewCacheGroup> get_cache_group () const; + + /** + * Notify the Cache that we are dropping our reference to the + * CacheGroup so it can also do so if it is the only reference holder + * of the cache group. + */ + void reset_cache_group (); +}; + +} /* namespace */ + +#endif diff --git a/libs/waveview/waveview/wave_view_private.h b/libs/waveview/waveview/wave_view_private.h new file mode 100644 index 0000000000..b1e5ace844 --- /dev/null +++ b/libs/waveview/waveview/wave_view_private.h @@ -0,0 +1,365 @@ +/* + Copyright (C) 2017 Tim Mayberry + + 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 _WAVEVIEW_WAVE_VIEW_PRIVATE_H_ +#define _WAVEVIEW_WAVE_VIEW_PRIVATE_H_ + +#include <deque> + +#include "waveview/wave_view.h" + +namespace ARDOUR { + class AudioRegion; +} + +namespace ArdourWaveView { + +struct WaveViewProperties +{ +public: // ctors + WaveViewProperties (boost::shared_ptr<ARDOUR::AudioRegion> region); + + // WaveViewProperties (WaveViewProperties const& other) = default; + + // WaveViewProperties& operator=(WaveViewProperties const& other) = default; + +public: // member variables + + framepos_t region_start; + framepos_t region_end; + uint16_t channel; + double height; + double samples_per_pixel; + double amplitude; + double amplitude_above_axis; + Gtkmm2ext::Color fill_color; + Gtkmm2ext::Color outline_color; + Gtkmm2ext::Color zero_color; + Gtkmm2ext::Color clip_color; + bool show_zero; + bool logscaled; + WaveView::Shape shape; + double gradient_depth; + double start_shift; + +private: // member variables + + framepos_t sample_start; + framepos_t sample_end; + +public: // methods + + bool is_valid () const + { + return (sample_end != 0 && samples_per_pixel != 0); + } + + void set_width_samples (ARDOUR::framecnt_t const width_samples) + { + assert (is_valid()); + assert (width_samples != 0); + ARDOUR::framecnt_t half_width = width_samples / 2; + framepos_t new_sample_start = std::max (region_start, get_center_sample () - half_width); + framepos_t new_sample_end = std::min (get_center_sample () + half_width, region_end); + assert (new_sample_start <= new_sample_end); + sample_start = new_sample_start; + sample_end = new_sample_end; + } + + uint64_t get_width_pixels () const + { + return (uint64_t)std::max (1LL, llrint (ceil (get_length_samples () / samples_per_pixel))); + } + + + void set_sample_offsets (framepos_t const start, framepos_t const end) + { + assert (start <= end); + + // sample_start and sample_end are bounded by the region limits. + if (start < region_start) { + sample_start = region_start; + } else if (start > region_end) { + sample_start = region_end; + } else { + sample_start = start; + } + + if (end > region_end) { + sample_end = region_end; + } else if (end < region_start) { + sample_end = region_start; + } else { + sample_end = end; + } + + assert (sample_start <= sample_end); + } + + framepos_t get_sample_start () const + { + return sample_start; + } + + framepos_t get_sample_end () const + { + return sample_end; + } + + void set_sample_positions_from_pixel_offsets (double start_pixel, double end_pixel) + { + assert (start_pixel <= end_pixel); + /** + * It is possible for the new sample positions to be past the region_end, + * so we have to do bounds checking/adjustment for this in set_sample_offsets. + */ + framepos_t new_sample_start = region_start + (start_pixel * samples_per_pixel); + framepos_t new_sample_end = region_start + (end_pixel * samples_per_pixel); + set_sample_offsets (new_sample_start, new_sample_end); + } + + ARDOUR::framecnt_t get_length_samples () const + { + assert (sample_start <= sample_end); + return sample_end - sample_start; + } + + framepos_t get_center_sample () + { + return sample_start + (get_length_samples() / 2); + } + + bool is_equivalent (WaveViewProperties const& other) + { + return (samples_per_pixel == other.samples_per_pixel && + contains (other.sample_start, other.sample_end) && channel == other.channel && + height == other.height && amplitude == other.amplitude && + amplitude_above_axis == other.amplitude_above_axis && fill_color == other.fill_color && + outline_color == other.outline_color && zero_color == other.zero_color && + clip_color == other.clip_color && show_zero == other.show_zero && + logscaled == other.logscaled && shape == other.shape && + gradient_depth == other.gradient_depth); + // region_start && start_shift?? + } + + bool contains (framepos_t start, framepos_t end) + { + return (sample_start <= start && end <= sample_end); + } +}; + +struct WaveViewImage { +public: // ctors + WaveViewImage (boost::shared_ptr<const ARDOUR::AudioRegion> const& region_ptr, + WaveViewProperties const& properties); + + ~WaveViewImage (); + +public: // member variables + boost::weak_ptr<const ARDOUR::AudioRegion> region; + WaveViewProperties props; + Cairo::RefPtr<Cairo::ImageSurface> cairo_image; + uint64_t timestamp; + +public: // methods + bool finished() { return static_cast<bool>(cairo_image); } + + bool + contains_image_with_properties (WaveViewProperties const& other_props) + { + return cairo_image && props.is_equivalent (other_props); + } + + bool is_valid () { + return props.is_valid (); + } + + size_t size_in_bytes () + { + // 4 = bytes per FORMAT_ARGB32 pixel + return props.height * props.get_width_pixels() * 4; + } +}; + +struct WaveViewDrawRequest +{ +public: + WaveViewDrawRequest (); + ~WaveViewDrawRequest (); + + bool stopped() const { return (bool) g_atomic_int_get (const_cast<gint*>(&stop)); } + void cancel() { g_atomic_int_set (&stop, 1); } + bool finished() { return image->finished(); } + + boost::shared_ptr<WaveViewImage> image; + + bool is_valid () { + return (image && image->is_valid()); + } + +private: + gint stop; /* intended for atomic access */ +}; + +class WaveViewCache; + +class WaveViewCacheGroup +{ +public: + WaveViewCacheGroup (WaveViewCache& parent_cache); + + ~WaveViewCacheGroup (); + +public: + + // @return image with matching properties or null + boost::shared_ptr<WaveViewImage> lookup_image (WaveViewProperties const&); + + void add_image (boost::shared_ptr<WaveViewImage>); + + bool full () const { return _cached_images.size() > max_size(); } + + static uint32_t max_size () { return 16; } + + void clear_cache (); + +private: + + /** + * At time of writing we don't strictly need a reference to the parent cache + * as there is only a single global cache but if the image cache ever becomes + * a per canvas cache then a using a reference is handy. + */ + WaveViewCache& _parent_cache; + + typedef std::list<boost::shared_ptr<WaveViewImage> > ImageCache; + ImageCache _cached_images; +}; + +class WaveViewCache +{ +public: + static WaveViewCache* get_instance (); + + uint64_t image_cache_threshold () const { return _image_cache_threshold; } + void set_image_cache_threshold (uint64_t); + + void clear_cache (); + + boost::shared_ptr<WaveViewCacheGroup> get_cache_group (boost::shared_ptr<ARDOUR::AudioSource>); + + void reset_cache_group (boost::shared_ptr<WaveViewCacheGroup>&); + +private: + WaveViewCache(); + ~WaveViewCache(); + +private: + typedef std::map<boost::shared_ptr<ARDOUR::AudioSource>, boost::shared_ptr<WaveViewCacheGroup> > + CacheGroups; + + CacheGroups cache_group_map; + + uint64_t image_cache_size; + uint64_t _image_cache_threshold; + +private: + friend class WaveViewCacheGroup; + + void increase_size (uint64_t bytes); + void decrease_size (uint64_t bytes); + + bool full () { return image_cache_size > _image_cache_threshold; } +}; + +class WaveViewDrawRequestQueue +{ +public: + + void enqueue (boost::shared_ptr<WaveViewDrawRequest>&); + + // @return valid request or null if non-blocking or no request is available + boost::shared_ptr<WaveViewDrawRequest> dequeue (bool block); + + void wake_up (); + +private: + + mutable Glib::Threads::Mutex _queue_mutex; + Glib::Threads::Cond _cond; + + typedef std::deque<boost::shared_ptr<WaveViewDrawRequest> > DrawRequestQueueType; + DrawRequestQueueType _queue; +}; + +class WaveViewDrawingThread +{ +public: + WaveViewDrawingThread (); + ~WaveViewDrawingThread (); + +private: + void start (); + void quit (); + void run (); + +private: + Glib::Threads::Thread* _thread; + gint _quit; +}; + +class WaveViewThreads { +private: + WaveViewThreads (); + ~WaveViewThreads (); + +public: + static void initialize (); + static void deinitialize (); + + static bool enabled () { return (instance); } + + static void enqueue_draw_request (boost::shared_ptr<WaveViewDrawRequest>&); + +private: + friend class WaveViewDrawingThread; + + static void wake_up (); + + // will block until a request is available + static boost::shared_ptr<WaveViewDrawRequest> dequeue_draw_request (); + + void start_threads (); + void stop_threads (); + +private: + static uint32_t init_count; + static WaveViewThreads* instance; + + // TODO use std::unique_ptr when possible + typedef std::vector<boost::shared_ptr<WaveViewDrawingThread> > WaveViewThreadList; + + WaveViewThreadList _threads; + WaveViewDrawRequestQueue _request_queue; +}; + + +} /* namespace */ + +#endif |