2013-04-10 15:27:55 -04:00
/*
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 0213 9 , USA .
*/
2013-04-16 15:38:18 -04:00
# include <cmath>
2013-04-04 00:32:52 -04:00
# include <cairomm/cairomm.h>
2015-06-01 14:45:32 -04:00
# include <glibmm/threads.h>
2013-04-04 00:32:52 -04:00
# include "gtkmm2ext/utils.h"
2015-06-01 14:45:32 -04:00
# include "gtkmm2ext/gui_thread.h"
2013-04-04 00:32:52 -04:00
2015-06-01 14:45:32 -04:00
# include "pbd/base_ui.h"
2013-04-04 00:32:52 -04:00
# include "pbd/compose.h"
2015-06-01 14:45:32 -04:00
# include "pbd/convert.h"
2013-04-04 00:32:52 -04:00
# include "pbd/signals.h"
2013-04-24 15:42:14 -04:00
# include "pbd/stacktrace.h"
2013-04-04 00:32:52 -04:00
# include "ardour/types.h"
2013-04-16 15:38:18 -04:00
# include "ardour/dB.h"
2015-03-31 10:05:15 -04:00
# include "ardour/lmath.h"
2013-04-04 00:32:52 -04:00
# include "ardour/audioregion.h"
2015-06-01 14:45:32 -04:00
# include "ardour/audiosource.h"
2015-06-23 14:26:10 -04:00
# include "ardour/session.h"
2013-04-04 00:32:52 -04:00
2013-04-16 10:07:52 -04:00
# include "canvas/canvas.h"
2014-11-07 14:24:27 -05:00
# include "canvas/colors.h"
2015-06-10 18:48:01 -04:00
# include "canvas/debug.h"
# include "canvas/utils.h"
# include "canvas/wave_view.h"
2013-04-04 00:32:52 -04:00
2015-06-23 14:26:10 -04:00
# include "evoral/Range.hpp"
2013-04-04 00:32:52 -04:00
# include <gdkmm/general.h>
2015-03-29 04:53:02 -04:00
# include "gtkmm2ext/gui_thread.h"
2015-03-28 11:13:40 -04:00
2013-04-04 00:32:52 -04:00
using namespace std ;
using namespace ARDOUR ;
2015-06-10 18:48:01 -04:00
using namespace PBD ;
2013-04-04 00:32:52 -04:00
using namespace ArdourCanvas ;
2013-04-16 21:23:50 -04:00
double WaveView : : _global_gradient_depth = 0.6 ;
2013-04-16 18:02:12 -04:00
bool WaveView : : _global_logscaled = false ;
WaveView : : Shape WaveView : : _global_shape = WaveView : : Normal ;
2013-06-28 20:21:30 -04:00
bool WaveView : : _global_show_waveform_clipping = true ;
2013-12-30 15:46:44 -05:00
double WaveView : : _clip_level = 0.98853 ;
2013-04-16 18:02:12 -04:00
2015-06-01 14:45:32 -04:00
WaveViewCache * WaveView : : images = 0 ;
gint WaveView : : drawing_thread_should_quit = 0 ;
Glib : : Threads : : Mutex WaveView : : request_queue_lock ;
2015-09-02 08:28:26 -04:00
Glib : : Threads : : Mutex WaveView : : current_image_lock ;
2015-06-01 14:45:32 -04:00
Glib : : Threads : : Cond WaveView : : request_cond ;
Glib : : Threads : : Thread * WaveView : : _drawing_thread = 0 ;
WaveView : : DrawingRequestQueue WaveView : : request_queue ;
2013-04-16 18:02:12 -04:00
PBD : : Signal0 < void > WaveView : : VisualPropertiesChanged ;
2013-12-30 15:54:09 -05:00
PBD : : Signal0 < void > WaveView : : ClipLevelChanged ;
2013-04-16 14:04:59 -04:00
2016-02-21 12:34:10 -05:00
/* NO_THREAD_WAVEVIEWS is defined by the top level wscript
* if - - no - threaded - waveviws is provided at the configure step .
*/
# ifndef NO_THREADED_WAVEVIEWS
2016-02-19 15:13:29 -05:00
# define ENABLE_THREADED_WAVEFORM_RENDERING
2016-02-21 12:34:10 -05:00
# endif
2015-12-16 10:28:17 -05:00
2014-06-12 14:53:44 -04:00
WaveView : : WaveView ( Canvas * c , boost : : shared_ptr < ARDOUR : : AudioRegion > region )
: Item ( c )
, _region ( region )
, _channel ( 0 )
, _samples_per_pixel ( 0 )
, _height ( 64 )
, _show_zero ( false )
, _zero_color ( 0xff0000ff )
, _clip_color ( 0xff0000ff )
, _logscaled ( _global_logscaled )
, _shape ( _global_shape )
, _gradient_depth ( _global_gradient_depth )
, _shape_independent ( false )
, _logscaled_independent ( false )
, _gradient_depth_independent ( false )
, _amplitude_above_axis ( 1.0 )
2015-06-01 14:45:32 -04:00
, _region_amplitude ( region - > scale_amplitude ( ) )
2015-02-09 17:55:05 -05:00
, _start_shift ( 0.0 )
2014-06-12 14:53:44 -04:00
, _region_start ( region - > start ( ) )
2015-06-01 14:45:32 -04:00
, get_image_in_thread ( false )
2015-06-04 17:45:58 -04:00
, always_get_image_in_thread ( false )
2015-06-01 14:45:32 -04:00
, rendered ( false )
2014-06-12 14:53:44 -04:00
{
2015-06-23 08:17:48 -04:00
if ( ! images ) {
images = new WaveViewCache ;
}
2014-06-12 14:53:44 -04:00
VisualPropertiesChanged . connect_same_thread ( invalidation_connection , boost : : bind ( & WaveView : : handle_visual_property_change , this ) ) ;
ClipLevelChanged . connect_same_thread ( invalidation_connection , boost : : bind ( & WaveView : : handle_clip_level_change , this ) ) ;
2015-06-01 14:45:32 -04:00
2015-06-10 22:26:58 -04:00
ImageReady . connect ( image_ready_connection , invalidator ( * this ) , boost : : bind ( & WaveView : : image_ready , this ) , gui_context ( ) ) ;
2014-06-12 14:53:44 -04:00
}
2014-06-21 11:43:42 -04:00
WaveView : : WaveView ( Item * parent , boost : : shared_ptr < ARDOUR : : AudioRegion > region )
: Item ( parent )
2013-04-04 00:32:52 -04:00
, _region ( region )
, _channel ( 0 )
2013-04-12 11:31:17 -04:00
, _samples_per_pixel ( 0 )
2013-04-04 00:32:52 -04:00
, _height ( 64 )
2014-01-10 12:08:58 -05:00
, _show_zero ( false )
2013-04-16 10:07:52 -04:00
, _zero_color ( 0xff0000ff )
, _clip_color ( 0xff0000ff )
2013-04-16 18:02:12 -04:00
, _logscaled ( _global_logscaled )
, _shape ( _global_shape )
2013-04-16 21:23:50 -04:00
, _gradient_depth ( _global_gradient_depth )
2013-04-16 18:02:12 -04:00
, _shape_independent ( false )
, _logscaled_independent ( false )
2013-04-16 21:23:50 -04:00
, _gradient_depth_independent ( false )
2013-04-17 15:22:09 -04:00
, _amplitude_above_axis ( 1.0 )
2015-06-01 14:45:32 -04:00
, _region_amplitude ( region - > scale_amplitude ( ) )
2015-06-11 13:34:46 -04:00
, _start_shift ( 0.0 )
2013-04-24 15:42:14 -04:00
, _region_start ( region - > start ( ) )
2015-06-01 14:45:32 -04:00
, get_image_in_thread ( false )
2015-06-04 17:45:58 -04:00
, always_get_image_in_thread ( false )
2015-06-01 14:45:32 -04:00
, rendered ( false )
2013-04-04 00:32:52 -04:00
{
2015-06-23 08:17:48 -04:00
if ( ! images ) {
images = new WaveViewCache ;
}
2013-04-16 18:02:12 -04:00
VisualPropertiesChanged . connect_same_thread ( invalidation_connection , boost : : bind ( & WaveView : : handle_visual_property_change , this ) ) ;
2014-05-26 18:33:15 -04:00
ClipLevelChanged . connect_same_thread ( invalidation_connection , boost : : bind ( & WaveView : : handle_clip_level_change , this ) ) ;
2015-06-01 14:45:32 -04:00
2015-06-10 22:26:58 -04:00
ImageReady . connect ( image_ready_connection , invalidator ( * this ) , boost : : bind ( & WaveView : : image_ready , this ) , gui_context ( ) ) ;
2013-04-16 14:04:59 -04:00
}
2013-06-20 14:37:31 -04:00
WaveView : : ~ WaveView ( )
{
2014-06-07 11:47:38 -04:00
invalidate_image_cache ( ) ;
2015-08-20 16:27:35 -04:00
if ( images ) {
images - > clear_cache ( ) ;
}
2013-06-20 14:37:31 -04:00
}
2015-06-01 14:45:32 -04:00
string
WaveView : : debug_name ( ) const
{
return _region - > name ( ) + string ( " : " ) + PBD : : to_string ( _channel + 1 , std : : dec ) ;
}
void
WaveView : : image_ready ( )
{
2016-03-01 08:43:44 -05:00
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 ) ) ;
2015-06-01 14:45:32 -04:00
redraw ( ) ;
}
2015-06-04 17:45:58 -04:00
void
WaveView : : set_always_get_image_in_thread ( bool yn )
{
always_get_image_in_thread = yn ;
}
2013-04-16 14:04:59 -04:00
void
2013-04-16 18:02:12 -04:00
WaveView : : handle_visual_property_change ( )
2013-04-16 14:04:59 -04:00
{
2013-04-16 18:02:12 -04:00
bool changed = false ;
if ( ! _shape_independent & & ( _shape ! = global_shape ( ) ) ) {
_shape = global_shape ( ) ;
changed = true ;
}
if ( ! _logscaled_independent & & ( _logscaled ! = global_logscaled ( ) ) ) {
_logscaled = global_logscaled ( ) ;
changed = true ;
}
2013-04-16 21:23:50 -04:00
if ( ! _gradient_depth_independent & & ( _gradient_depth ! = global_gradient_depth ( ) ) ) {
_gradient_depth = global_gradient_depth ( ) ;
changed = true ;
}
2014-09-12 19:44:05 -04:00
2013-04-16 18:02:12 -04:00
if ( changed ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
end_visual_change ( ) ;
2013-04-16 18:02:12 -04:00
}
2013-04-04 00:32:52 -04:00
}
2014-05-26 18:33:15 -04:00
void
WaveView : : handle_clip_level_change ( )
{
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
end_visual_change ( ) ;
2014-05-26 18:33:15 -04:00
}
2013-04-16 20:38:10 -04:00
void
WaveView : : set_fill_color ( Color c )
{
2013-04-24 15:42:14 -04:00
if ( c ! = _fill_color ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-24 15:42:14 -04:00
Fill : : set_fill_color ( c ) ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-24 15:42:14 -04:00
}
2013-04-16 20:38:10 -04:00
}
void
WaveView : : set_outline_color ( Color c )
{
2013-04-24 15:42:14 -04:00
if ( c ! = _outline_color ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-24 15:42:14 -04:00
Outline : : set_outline_color ( c ) ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-24 15:42:14 -04:00
}
2013-04-16 20:38:10 -04:00
}
2013-04-04 00:32:52 -04:00
void
2013-04-12 11:31:17 -04:00
WaveView : : set_samples_per_pixel ( double samples_per_pixel )
2013-04-04 00:32:52 -04:00
{
2013-04-24 15:42:14 -04:00
if ( samples_per_pixel ! = _samples_per_pixel ) {
begin_change ( ) ;
2013-04-26 11:10:19 -04:00
2014-06-07 11:47:38 -04:00
invalidate_image_cache ( ) ;
2013-04-24 15:42:14 -04:00
_samples_per_pixel = samples_per_pixel ;
_bounding_box_dirty = true ;
2014-09-12 19:44:05 -04:00
2013-04-24 15:42:14 -04:00
end_change ( ) ;
}
}
2013-04-04 00:32:52 -04:00
2013-06-27 17:23:34 -04:00
static inline float
_log_meter ( float power , double lower_db , double upper_db , double non_linearity )
{
return ( power < lower_db ? 0.0 : pow ( ( power - lower_db ) / ( upper_db - lower_db ) , non_linearity ) ) ;
}
static inline float
alt_log_meter ( float power )
{
return _log_meter ( power , - 192.0 , 0.0 , 8.0 ) ;
}
2013-12-30 15:46:44 -05:00
void
WaveView : : set_clip_level ( double dB )
{
2014-05-26 18:33:15 -04:00
const double clip_level = dB_to_coefficient ( dB ) ;
if ( clip_level ! = _clip_level ) {
_clip_level = clip_level ;
ClipLevelChanged ( ) ;
}
2013-12-30 15:46:44 -05:00
}
2015-03-28 11:13:40 -04:00
void
WaveView : : invalidate_image_cache ( )
{
2016-03-01 08:43:44 -05:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1 invalidates image cache and cancels current request \n " , this ) ) ;
2015-06-01 14:45:32 -04:00
cancel_my_render_request ( ) ;
2015-09-02 08:28:26 -04:00
Glib : : Threads : : Mutex : : Lock lci ( current_image_lock ) ;
2015-06-01 14:45:32 -04:00
_current_image . reset ( ) ;
2014-06-07 11:47:38 -04:00
}
2013-06-27 17:23:34 -04:00
2015-06-19 08:14:02 -04:00
void
WaveView : : compute_tips ( PeakData const & peak , WaveView : : LineTips & tips ) const
2014-06-23 14:57:55 -04:00
{
2015-06-19 08:14:02 -04:00
const double effective_height = _height ;
2015-10-04 14:51:05 -04:00
/* remember: canvas (and cairo) coordinate space puts the origin at the upper left.
2015-10-05 10:17:49 -04:00
2015-06-19 08:14:02 -04:00
So , a sample value of 1.0 ( 0 dbFS ) will be computed as :
( 1.0 - 1.0 ) * 0.5 * effective_height
which evaluates to 0 , or the top of the image .
A sample value of - 1.0 will be computed as
( 1.0 + 1.0 ) * 0.5 * effective height
which evaluates to effective height , or the bottom of the image .
*/
const double pmax = ( 1.0 - peak . max ) * 0.5 * effective_height ;
const double pmin = ( 1.0 - peak . min ) * 0.5 * effective_height ;
/* remember that the bottom of the image (pmin) has larger y-coordinates
than the top ( pmax ) .
*/
double spread = ( pmin - pmax ) * 0.5 ;
/* find the nearest pixel to the nominal center. */
const double center = round ( pmin - spread ) ;
if ( spread < 1.0 ) {
/* minimum distance between line ends is 1 pixel, and we want it "centered" on a pixel,
as per cairo single - pixel line issues .
NOTE : the caller will not draw a line between these two points if the spread is
less than 2 pixels . So only the tips . top value matters , which is where we will
draw a single pixel as part of the outline .
2014-09-12 19:34:27 -04:00
*/
2015-06-19 08:14:02 -04:00
tips . top = center ;
tips . bot = center + 1.0 ;
} else {
/* round spread above and below center to an integer number of pixels */
spread = round ( spread ) ;
/* top and bottom are located equally either side of the center */
tips . top = center - spread ;
tips . bot = center + spread ;
2014-06-23 14:57:55 -04:00
}
2015-10-05 10:17:49 -04:00
2015-06-19 08:14:02 -04:00
tips . top = min ( effective_height , max ( 0.0 , tips . top ) ) ;
tips . bot = min ( effective_height , max ( 0.0 , tips . bot ) ) ;
}
2015-10-05 10:17:49 -04:00
2015-06-19 08:14:02 -04:00
Coord
WaveView : : y_extent ( double s ) const
{
assert ( _shape = = Rectified ) ;
return floor ( ( 1.0 - s ) * _height ) ;
2014-06-23 14:57:55 -04:00
}
2015-03-21 09:31:32 -04:00
void
WaveView : : draw_absent_image ( Cairo : : RefPtr < Cairo : : ImageSurface > & image , PeakData * _peaks , int n_peaks ) const
{
Cairo : : RefPtr < Cairo : : ImageSurface > stripe = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_A8 , n_peaks , _height ) ;
Cairo : : RefPtr < Cairo : : Context > stripe_context = Cairo : : Context : : create ( stripe ) ;
stripe_context - > set_antialias ( Cairo : : ANTIALIAS_NONE ) ;
uint32_t stripe_separation = 150 ;
double start = - floor ( _height / stripe_separation ) * stripe_separation ;
int stripe_x = 0 ;
while ( start < n_peaks ) {
stripe_context - > move_to ( start , 0 ) ;
stripe_x = start + _height ;
stripe_context - > line_to ( stripe_x , _height ) ;
start + = stripe_separation ;
}
stripe_context - > set_source_rgba ( 1.0 , 1.0 , 1.0 , 1.0 ) ;
stripe_context - > set_line_cap ( Cairo : : LINE_CAP_SQUARE ) ;
stripe_context - > set_line_width ( 50 ) ;
stripe_context - > stroke ( ) ;
Cairo : : RefPtr < Cairo : : Context > context = Cairo : : Context : : create ( image ) ;
context - > set_source_rgba ( 1.0 , 1.0 , 0.0 , 0.3 ) ;
context - > mask ( stripe , 0 , 0 ) ;
context - > fill ( ) ;
}
2014-07-15 07:59:22 -04:00
struct ImageSet {
Cairo : : RefPtr < Cairo : : ImageSurface > wave ;
Cairo : : RefPtr < Cairo : : ImageSurface > outline ;
Cairo : : RefPtr < Cairo : : ImageSurface > clip ;
Cairo : : RefPtr < Cairo : : ImageSurface > zero ;
2014-09-12 19:44:05 -04:00
ImageSet ( ) :
wave ( 0 ) , outline ( 0 ) , clip ( 0 ) , zero ( 0 ) { }
2014-07-15 07:59:22 -04:00
} ;
2014-06-07 11:47:38 -04:00
void
2015-06-01 14:45:32 -04:00
WaveView : : draw_image ( Cairo : : RefPtr < Cairo : : ImageSurface > & image , PeakData * _peaks , int n_peaks , boost : : shared_ptr < WaveViewThreadRequest > req ) const
2014-06-07 11:47:38 -04:00
{
2014-07-15 07:59:22 -04:00
ImageSet images ;
images . wave = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_A8 , n_peaks , _height ) ;
images . outline = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_A8 , n_peaks , _height ) ;
images . clip = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_A8 , n_peaks , _height ) ;
images . zero = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_A8 , n_peaks , _height ) ;
Cairo : : RefPtr < Cairo : : Context > wave_context = Cairo : : Context : : create ( images . wave ) ;
Cairo : : RefPtr < Cairo : : Context > outline_context = Cairo : : Context : : create ( images . outline ) ;
Cairo : : RefPtr < Cairo : : Context > clip_context = Cairo : : Context : : create ( images . clip ) ;
Cairo : : RefPtr < Cairo : : Context > zero_context = Cairo : : Context : : create ( images . zero ) ;
2015-03-10 08:35:53 -04:00
wave_context - > set_antialias ( Cairo : : ANTIALIAS_NONE ) ;
outline_context - > set_antialias ( Cairo : : ANTIALIAS_NONE ) ;
clip_context - > set_antialias ( Cairo : : ANTIALIAS_NONE ) ;
zero_context - > set_antialias ( Cairo : : ANTIALIAS_NONE ) ;
2014-07-15 07:59:22 -04:00
2013-06-27 17:23:34 -04:00
boost : : scoped_array < LineTips > tips ( new LineTips [ n_peaks ] ) ;
2013-06-28 20:21:30 -04:00
/* Clip level nominally set to -0.9dBFS to account for inter-sample
interpolation possibly clipping ( value may be too low ) .
We adjust by the region ' s own gain ( but note : not by any gain
automation or its gain envelope ) so that clip indicators are closer
to providing data about on - disk data . This multiplication is
needed because the data we get from AudioRegion : : read_peaks ( )
has been scaled by scale_amplitude ( ) already .
*/
2015-06-18 09:02:31 -04:00
const double clip_level = _clip_level * _region_amplitude ;
2013-06-27 17:23:34 -04:00
if ( _shape = = WaveView : : Rectified ) {
/* each peak is a line from the bottom of the waveview
* to a point determined by max ( _peaks [ i ] . max ,
* _peaks [ i ] . min )
*/
if ( _logscaled ) {
for ( int i = 0 ; i < n_peaks ; + + i ) {
2014-06-23 14:57:55 -04:00
2014-09-12 19:34:27 -04:00
tips [ i ] . bot = height ( ) - 1.0 ;
2014-06-23 14:57:55 -04:00
const double p = alt_log_meter ( fast_coefficient_to_dB ( max ( fabs ( _peaks [ i ] . max ) , fabs ( _peaks [ i ] . min ) ) ) ) ;
2015-06-19 08:14:02 -04:00
tips [ i ] . top = y_extent ( p ) ;
tips [ i ] . spread = p * _height ;
2013-06-28 13:40:51 -04:00
2015-03-10 08:35:53 -04:00
if ( _peaks [ i ] . max > = clip_level ) {
2013-06-28 13:40:51 -04:00
tips [ i ] . clip_max = true ;
}
2015-03-10 08:35:53 -04:00
if ( - ( _peaks [ i ] . min ) > = clip_level ) {
2015-02-20 07:30:48 -05:00
tips [ i ] . clip_min = true ;
2013-06-28 13:40:51 -04:00
}
2013-06-27 17:23:34 -04:00
}
2014-06-23 14:57:55 -04:00
2015-03-10 08:35:53 -04:00
} else {
for ( int i = 0 ; i < n_peaks ; + + i ) {
2014-06-23 14:57:55 -04:00
2014-09-12 19:34:27 -04:00
tips [ i ] . bot = height ( ) - 1.0 ;
const double p = max ( fabs ( _peaks [ i ] . max ) , fabs ( _peaks [ i ] . min ) ) ;
2015-06-19 08:14:02 -04:00
tips [ i ] . top = y_extent ( p ) ;
tips [ i ] . spread = p * _height ;
2014-09-12 19:34:27 -04:00
if ( p > = clip_level ) {
2013-06-28 13:40:51 -04:00
tips [ i ] . clip_max = true ;
}
2013-06-27 17:23:34 -04:00
}
2014-09-12 19:34:27 -04:00
2013-06-27 17:23:34 -04:00
}
} else {
if ( _logscaled ) {
for ( int i = 0 ; i < n_peaks ; + + i ) {
2015-06-19 08:14:02 -04:00
PeakData p ;
p . max = _peaks [ i ] . max ;
p . min = _peaks [ i ] . min ;
2013-06-27 17:23:34 -04:00
2015-03-10 08:35:53 -04:00
if ( _peaks [ i ] . max > = clip_level ) {
2015-06-19 08:14:02 -04:00
tips [ i ] . clip_max = true ;
2013-06-28 13:40:51 -04:00
}
2015-03-10 08:35:53 -04:00
if ( - ( _peaks [ i ] . min ) > = clip_level ) {
tips [ i ] . clip_min = true ;
2013-06-28 13:40:51 -04:00
}
2015-06-19 08:14:02 -04:00
if ( p . max > 0.0 ) {
p . max = alt_log_meter ( fast_coefficient_to_dB ( p . max ) ) ;
} else if ( p . max < 0.0 ) {
p . max = - alt_log_meter ( fast_coefficient_to_dB ( - p . max ) ) ;
2013-06-27 17:23:34 -04:00
} else {
2015-06-19 08:14:02 -04:00
p . max = 0.0 ;
2013-06-27 17:23:34 -04:00
}
2015-06-19 08:14:02 -04:00
if ( p . min > 0.0 ) {
p . min = alt_log_meter ( fast_coefficient_to_dB ( p . min ) ) ;
} else if ( p . min < 0.0 ) {
p . min = - alt_log_meter ( fast_coefficient_to_dB ( - p . min ) ) ;
2013-06-27 17:23:34 -04:00
} else {
2015-06-19 08:14:02 -04:00
p . min = 0.0 ;
2013-06-27 17:23:34 -04:00
}
2015-10-05 10:17:49 -04:00
2015-06-19 08:14:02 -04:00
compute_tips ( p , tips [ i ] ) ;
2015-03-10 08:35:53 -04:00
tips [ i ] . spread = tips [ i ] . bot - tips [ i ] . top ;
2014-09-12 19:44:05 -04:00
}
2013-06-27 17:23:34 -04:00
} else {
for ( int i = 0 ; i < n_peaks ; + + i ) {
2015-03-10 08:35:53 -04:00
if ( _peaks [ i ] . max > = clip_level ) {
tips [ i ] . clip_max = true ;
2013-06-28 13:40:51 -04:00
}
2015-03-10 08:35:53 -04:00
if ( - ( _peaks [ i ] . min ) > = clip_level ) {
tips [ i ] . clip_min = true ;
2013-06-28 13:40:51 -04:00
}
2015-06-19 08:14:02 -04:00
compute_tips ( _peaks [ i ] , tips [ i ] ) ;
2015-03-10 08:35:53 -04:00
tips [ i ] . spread = tips [ i ] . bot - tips [ i ] . top ;
2013-06-27 17:23:34 -04:00
}
2014-09-12 19:44:05 -04:00
2013-06-27 17:23:34 -04:00
}
}
2015-06-01 14:45:32 -04:00
if ( req - > should_stop ( ) ) {
return ;
}
2015-10-05 10:17:49 -04:00
2014-07-15 07:59:22 -04:00
Color alpha_one = rgba_to_color ( 0 , 0 , 0 , 1.0 ) ;
2013-06-27 17:23:34 -04:00
2014-07-15 07:59:22 -04:00
set_source_rgba ( wave_context , alpha_one ) ;
set_source_rgba ( outline_context , alpha_one ) ;
set_source_rgba ( clip_context , alpha_one ) ;
set_source_rgba ( zero_context , alpha_one ) ;
2013-06-27 17:23:34 -04:00
2014-07-15 07:59:22 -04:00
/* ensure single-pixel lines */
2014-09-12 19:44:05 -04:00
2014-09-12 10:43:20 -04:00
wave_context - > set_line_width ( 1.0 ) ;
2015-06-19 08:14:02 -04:00
wave_context - > translate ( 0.5 , 0.5 ) ;
2013-06-27 17:23:34 -04:00
2014-09-12 10:43:20 -04:00
outline_context - > set_line_width ( 1.0 ) ;
2015-06-19 08:14:02 -04:00
outline_context - > translate ( 0.5 , 0.5 ) ;
2013-06-27 17:23:34 -04:00
2014-09-12 10:43:20 -04:00
clip_context - > set_line_width ( 1.0 ) ;
2015-06-19 08:14:02 -04:00
clip_context - > translate ( 0.5 , 0.5 ) ;
2013-06-27 17:23:34 -04:00
2014-09-12 10:43:20 -04:00
zero_context - > set_line_width ( 1.0 ) ;
2015-06-19 08:14:02 -04:00
zero_context - > translate ( 0.5 , 0.5 ) ;
2013-06-27 17:23:34 -04:00
2014-06-23 14:57:55 -04:00
/* the height of the clip-indicator should be at most 7 pixels,
2014-06-08 13:13:44 -04:00
* or 5 % of the height of the waveview item .
2013-06-27 17:23:34 -04:00
*/
2014-06-23 15:12:30 -04:00
2014-06-08 13:13:44 -04:00
const double clip_height = min ( 7.0 , ceil ( _height * 0.05 ) ) ;
2014-09-12 19:44:05 -04:00
2014-06-23 15:12:30 -04:00
/* There are 3 possible components to draw at each x-axis position: the
waveform " line " , the zero line and an outline / clip indicator . We
have to decide which of the 3 to draw at each position , pixel by
pixel . This makes the rendering less efficient but it is the only
way I can see to do this correctly .
2014-07-15 07:59:22 -04:00
To avoid constant source swapping and stroking , we draw the components separately
onto four alpha only image surfaces for use as a mask .
2014-06-23 15:12:30 -04:00
With only 1 pixel of spread between the top and bottom of the line ,
we just draw the upper outline / clip indicator .
With 2 pixels of spread , we draw the upper and lower outline clip
2014-09-12 19:44:05 -04:00
indicators .
With 3 pixels of spread we draw the upper and lower outline / clip
2014-06-23 15:12:30 -04:00
indicators and at least 1 pixel of the waveform line .
2014-09-12 19:44:05 -04:00
2014-06-23 15:12:30 -04:00
With 5 pixels of spread , we draw all components .
We can do rectified as two separate passes because we have a much
easier decision regarding whether to draw the waveform line . We
always draw the clip / outline indicators .
*/
2014-06-23 14:57:55 -04:00
if ( _shape = = WaveView : : Rectified ) {
2014-06-08 12:50:14 -04:00
2014-06-23 14:57:55 -04:00
for ( int i = 0 ; i < n_peaks ; + + i ) {
2014-06-23 10:06:52 -04:00
2014-06-23 15:12:30 -04:00
/* waveform line */
2014-09-12 19:34:27 -04:00
if ( tips [ i ] . spread > = 1.0 ) {
2014-07-15 07:59:22 -04:00
wave_context - > move_to ( i , tips [ i ] . top ) ;
wave_context - > line_to ( i , tips [ i ] . bot ) ;
2014-06-23 14:57:55 -04:00
}
2014-06-23 15:12:30 -04:00
2015-04-16 15:26:57 -04:00
/* clip indicator */
2015-04-16 15:36:13 -04:00
2015-04-16 15:26:57 -04:00
if ( _global_show_waveform_clipping & & ( tips [ i ] . clip_max | | tips [ i ] . clip_min ) ) {
2014-07-15 07:59:22 -04:00
clip_context - > move_to ( i , tips [ i ] . top ) ;
2014-06-23 14:57:55 -04:00
/* clip-indicating upper terminal line */
2014-09-12 19:34:27 -04:00
clip_context - > rel_line_to ( 0 , min ( clip_height , ceil ( tips [ i ] . spread + .5 ) ) ) ;
2015-04-16 15:36:13 -04:00
} else {
2014-07-15 07:59:22 -04:00
outline_context - > move_to ( i , tips [ i ] . top ) ;
2014-06-23 14:57:55 -04:00
/* normal upper terminal dot */
2015-04-16 15:26:57 -04:00
outline_context - > rel_line_to ( 0 , - 1.0 ) ;
2014-06-23 14:57:55 -04:00
}
2014-06-08 13:13:44 -04:00
}
2014-06-23 15:12:30 -04:00
2014-07-15 07:59:22 -04:00
wave_context - > stroke ( ) ;
clip_context - > stroke ( ) ;
outline_context - > stroke ( ) ;
} else {
2015-06-24 14:12:13 -04:00
const int height_zero = floor ( _height * .5 ) ;
2014-06-23 15:12:30 -04:00
2014-06-23 14:57:55 -04:00
for ( int i = 0 ; i < n_peaks ; + + i ) {
2013-06-28 20:21:30 -04:00
2014-06-23 15:12:30 -04:00
/* waveform line */
2014-09-12 19:34:27 -04:00
if ( tips [ i ] . spread > = 2.0 ) {
2014-07-15 07:59:22 -04:00
wave_context - > move_to ( i , tips [ i ] . top ) ;
wave_context - > line_to ( i , tips [ i ] . bot ) ;
2014-06-23 14:57:55 -04:00
}
2015-10-05 10:17:49 -04:00
2015-03-10 08:35:53 -04:00
/* draw square waves and other discontiguous points clearly */
2014-09-12 10:42:55 -04:00
if ( i > 0 ) {
2015-03-10 08:35:53 -04:00
if ( tips [ i - 1 ] . top + 2 < tips [ i ] . top ) {
2014-09-12 10:42:55 -04:00
wave_context - > move_to ( i - 1 , tips [ i - 1 ] . top ) ;
2015-03-10 08:35:53 -04:00
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 ) ;
2014-09-12 10:42:55 -04:00
wave_context - > line_to ( i , tips [ i ] . top ) ;
2015-03-10 08:35:53 -04:00
} 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 ) ;
2014-09-12 10:42:55 -04:00
}
}
2015-10-04 14:51:05 -04:00
/* zero line, show only if there is enough spread
2015-06-24 14:12:13 -04:00
or the waveform line does not cross zero line */
2014-06-23 15:12:30 -04:00
2015-06-24 14:12:13 -04:00
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 ) ;
2014-09-12 19:34:27 -04:00
zero_context - > rel_line_to ( 1.0 , 0 ) ;
2014-06-23 14:57:55 -04:00
}
2014-09-12 19:34:27 -04:00
if ( tips [ i ] . spread > 1.0 ) {
2015-04-16 15:03:51 -04:00
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 ;
}
2014-07-15 07:59:22 -04:00
if ( _global_show_waveform_clipping & & tips [ i ] . clip_min ) {
clip_context - > move_to ( i , tips [ i ] . bot ) ;
2014-06-23 14:57:55 -04:00
/* clip-indicating lower terminal line */
2015-04-16 15:03:51 -04:00
clip_context - > rel_line_to ( 0 , - min ( clip_height , ceil ( tips [ i ] . spread + 0.5 ) ) ) ;
clipped = true ;
}
2015-06-23 17:12:52 -04:00
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 ) .
*/
2015-06-19 08:14:02 -04:00
outline_context - > move_to ( i , tips [ i ] . bot ) ;
/* normal lower terminal dot; line moves up */
2015-04-16 15:03:51 -04:00
outline_context - > rel_line_to ( 0 , - 1.0 ) ;
2015-06-19 08:14:02 -04:00
outline_context - > move_to ( i , tips [ i ] . top ) ;
/* normal upper terminal dot, line moves down */
2015-04-16 15:03:51 -04:00
outline_context - > rel_line_to ( 0 , 1.0 ) ;
2014-09-12 19:34:27 -04:00
}
} else {
2015-04-16 15:03:51 -04:00
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 ;
2014-06-23 14:57:55 -04:00
}
2014-09-12 19:34:27 -04:00
2015-04-16 15:03:51 -04:00
if ( ! clipped ) {
2015-06-23 17:12:52 -04:00
/* special case where only 1 pixel of
* the waveform line is drawn ( and
* nothing else ) .
*
* we draw a 1 px " line " , pretending
* that the span is 1.0 ( whether it is
* zero or 1.0 )
*/
2015-04-16 15:03:51 -04:00
wave_context - > move_to ( i , tips [ i ] . top ) ;
wave_context - > rel_line_to ( 0 , 1.0 ) ;
2015-03-10 08:35:53 -04:00
}
2014-09-12 19:34:27 -04:00
}
2013-06-27 17:23:34 -04:00
}
2014-07-15 07:59:22 -04:00
wave_context - > stroke ( ) ;
outline_context - > stroke ( ) ;
clip_context - > stroke ( ) ;
zero_context - > stroke ( ) ;
2013-06-27 17:23:34 -04:00
}
2014-07-15 07:59:22 -04:00
2015-06-01 14:45:32 -04:00
if ( req - > should_stop ( ) ) {
return ;
}
2015-10-05 10:17:49 -04:00
2014-07-15 07:59:22 -04:00
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 ) {
2014-09-12 19:44:05 -04:00
2014-07-15 07:59:22 -04:00
Cairo : : RefPtr < Cairo : : LinearGradient > gradient ( Cairo : : LinearGradient : : create ( 0 , 0 , 0 , _height ) ) ;
2014-09-12 19:44:05 -04:00
2014-07-15 07:59:22 -04:00
double stops [ 3 ] ;
2014-09-12 19:44:05 -04:00
2014-07-15 07:59:22 -04:00
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 ( ) ;
2014-11-19 13:14:26 -05:00
Color center = hsva_to_color ( h , s , v , a ) ;
2014-07-15 07:59:22 -04:00
color_to_rgba ( center , r , g , b , a ) ;
2014-09-12 19:44:05 -04:00
2014-07-15 07:59:22 -04:00
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 ) ;
}
2015-06-01 14:45:32 -04:00
if ( req - > should_stop ( ) ) {
return ;
}
2015-10-05 10:17:49 -04:00
2014-07-15 07:59:22 -04:00
context - > mask ( images . wave , 0 , 0 ) ;
2014-09-12 19:44:05 -04:00
context - > fill ( ) ;
2014-07-15 07:59:22 -04:00
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 ( ) ;
2013-06-27 17:23:34 -04:00
}
2015-06-01 14:45:32 -04:00
boost : : shared_ptr < WaveViewCache : : Entry >
WaveView : : cache_request_result ( boost : : shared_ptr < WaveViewThreadRequest > req ) const
2013-06-27 17:23:34 -04:00
{
2015-09-02 12:58:07 -04:00
if ( ! req - > image ) {
2016-03-01 12:36:45 -05:00
// cerr << "asked to cache null image!!!\n";
2015-09-02 12:58:07 -04:00
return boost : : shared_ptr < WaveViewCache : : Entry > ( ) ;
}
2015-06-01 14:45:32 -04:00
boost : : shared_ptr < WaveViewCache : : Entry > ret ( new WaveViewCache : : Entry ( req - > channel ,
req - > height ,
2015-06-19 08:10:45 -04:00
req - > amplitude ,
2015-06-01 14:45:32 -04:00
req - > fill_color ,
req - > samples_per_pixel ,
req - > start ,
req - > end ,
req - > image ) ) ;
images - > add ( _region - > audio_source ( _channel ) , ret ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* consolidate cache first (removes fully-contained
* duplicate images )
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
images - > consolidate_image_cache ( _region - > audio_source ( _channel ) ,
2015-06-19 08:29:05 -04:00
req - > channel , req - > height , req - > amplitude ,
req - > fill_color , req - > samples_per_pixel ) ;
2014-09-12 19:44:05 -04:00
2015-06-01 14:45:32 -04:00
return ret ;
}
2013-06-20 14:37:31 -04:00
2015-06-01 14:45:32 -04:00
boost : : shared_ptr < WaveViewCache : : Entry >
2015-06-04 17:45:58 -04:00
WaveView : : get_image ( framepos_t start , framepos_t end , bool & full_image ) const
2015-06-01 14:45:32 -04:00
{
boost : : shared_ptr < WaveViewCache : : Entry > ret ;
2015-06-04 17:45:58 -04:00
full_image = true ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* this is called from a ::render() call, when we need an image to
draw with .
2014-06-07 11:47:38 -04:00
*/
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1 needs image from %2 .. %3 \n " , name , start , end ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
{
Glib : : Threads : : Mutex : : Lock lmq ( request_queue_lock ) ;
2014-06-17 10:16:51 -04:00
2015-06-01 14:45:32 -04:00
/* 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 .
*/
2015-06-23 14:26:10 -04:00
2016-03-01 08:43:44 -05:00
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 ) ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
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 ) {
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
ret . reset ( new WaveViewCache : : Entry ( current_request - > channel ,
current_request - > height ,
2015-06-19 08:10:45 -04:00
current_request - > amplitude ,
2015-06-01 14:45:32 -04:00
current_request - > fill_color ,
current_request - > samples_per_pixel ,
current_request - > start ,
current_request - > end ,
current_request - > image ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
cache_request_result ( current_request ) ;
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1: got image from completed request, spans %2..%3 \n " ,
name , current_request - > start , current_request - > end ) ) ;
2015-06-01 14:45:32 -04:00
}
/* drop our handle on the current request */
current_request . reset ( ) ;
2014-06-07 11:47:38 -04:00
}
2015-06-01 14:45:32 -04:00
}
2014-06-07 11:47:38 -04:00
2015-06-01 14:45:32 -04:00
if ( ! ret ) {
2014-06-07 11:47:38 -04:00
2015-06-01 14:45:32 -04:00
/* no current image draw request, so look in the cache */
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
ret = get_image_from_cache ( start , end , full_image ) ;
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1: lookup from cache gave %2 (full %3) \n " ,
name , ret , full_image ) ) ;
2014-06-07 11:47:38 -04:00
}
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
if ( ! ret | | ! full_image ) {
2014-06-07 11:47:38 -04:00
2015-12-16 10:28:17 -05:00
# ifndef ENABLE_THREADED_WAVEFORM_RENDERING
if ( 1 )
# else
if ( ( rendered & & get_image_in_thread ) | | always_get_image_in_thread )
# endif
{
2015-10-04 14:51:05 -04:00
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1: generating image in caller thread \n " , name ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
boost : : shared_ptr < WaveViewThreadRequest > req ( new WaveViewThreadRequest ) ;
2013-06-20 14:37:31 -04:00
2015-06-01 14:45:32 -04:00
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 ;
2015-06-19 08:32:05 -04:00
req - > amplitude = _region_amplitude * _amplitude_above_axis ;
2016-03-01 08:43:44 -05:00
req - > width = desired_image_width ( ) ;
2013-06-20 14:37:31 -04:00
2015-06-01 14:45:32 -04:00
/* draw image in this (the GUI thread) */
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
generate_image ( req , false ) ;
2013-06-20 14:37:31 -04:00
2015-06-01 14:45:32 -04:00
/* cache the result */
2013-06-24 16:28:53 -04:00
2015-06-01 14:45:32 -04:00
ret = cache_request_result ( req ) ;
2013-06-27 17:23:34 -04:00
2015-06-01 14:45:32 -04:00
/* reset this so that future missing images are
* generated in a a worker thread .
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
get_image_in_thread = false ;
} else {
queue_get_image ( _region , start , end ) ;
}
2015-04-29 07:54:21 -04:00
}
2014-06-07 11:47:38 -04:00
2015-06-10 18:48:01 -04:00
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 ) ) ;
}
2015-06-01 14:45:32 -04:00
return ret ;
}
boost : : shared_ptr < WaveViewCache : : Entry >
2015-06-04 17:45:58 -04:00
WaveView : : get_image_from_cache ( framepos_t start , framepos_t end , bool & full ) const
2015-06-01 14:45:32 -04:00
{
if ( ! images ) {
return boost : : shared_ptr < WaveViewCache : : Entry > ( ) ;
2015-03-21 09:31:32 -04:00
}
2014-06-07 11:47:38 -04:00
2015-06-01 14:45:32 -04:00
return images - > lookup_image ( _region - > audio_source ( _channel ) , start , end , _channel ,
2015-06-19 08:32:05 -04:00
_height , _region_amplitude * _amplitude_above_axis , _fill_color , _samples_per_pixel , full ) ;
2015-06-01 14:45:32 -04:00
}
2015-06-23 14:26:10 -04:00
framecnt_t
WaveView : : desired_image_width ( ) const
{
2016-03-01 08:43:44 -05:00
/* 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 / 10 th of a second , use 1 / 10 th of
* a second instead .
*/
2015-10-04 14:51:05 -04:00
2016-03-01 08:43:44 -05:00
framecnt_t canvas_width_samples = _canvas - > visible_area ( ) . width ( ) * _samples_per_pixel ;
const framecnt_t one_tenth_of_second = _region - > session ( ) . frame_rate ( ) / 10 ;
2015-06-23 14:26:10 -04:00
2016-03-01 08:43:44 -05:00
if ( canvas_width_samples > one_tenth_of_second ) {
return canvas_width_samples ;
}
2015-06-23 14:26:10 -04:00
2016-03-01 08:43:44 -05:00
return one_tenth_of_second ;
2015-06-23 14:26:10 -04:00
}
2015-06-01 14:45:32 -04:00
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 ;
2015-06-19 08:10:45 -04:00
req - > amplitude = _region_amplitude * _amplitude_above_axis ;
2016-03-01 08:43:44 -05:00
req - > width = desired_image_width ( ) ;
2015-06-01 14:45:32 -04:00
2015-06-11 17:04:08 -04:00
if ( current_request ) {
/* this will stop rendering in progress (which might otherwise
be long lived ) for any current request .
*/
2016-03-01 08:43:28 -05:00
Glib : : Threads : : Mutex : : Lock lm ( request_queue_lock ) ;
if ( current_request ) {
current_request - > cancel ( ) ;
}
2015-06-11 17:04:08 -04:00
}
start_drawing_thread ( ) ;
/* swap requests (protected by lock) */
{
Glib : : Threads : : Mutex : : Lock lm ( request_queue_lock ) ;
current_request = req ;
2016-03-01 08:43:44 -05:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1 now has current request %2 \n " , this , req ) ) ;
2015-06-11 17:04:08 -04:00
2016-03-01 08:43:44 -05:00
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 ( ) ;
}
2015-06-23 14:26:10 -04:00
}
2015-06-01 14:45:32 -04:00
}
void
WaveView : : generate_image ( boost : : shared_ptr < WaveViewThreadRequest > req , bool in_render_thread ) const
{
if ( ! req - > should_stop ( ) ) {
2014-06-07 11:47:38 -04:00
2015-06-01 14:45:32 -04:00
/* sample position is canonical here, and we want to generate
2015-10-04 14:51:05 -04:00
* an image that spans about 3 x the canvas width . We get to that
2016-03-01 08:43:44 -05:00
* width by using an image sample count of the screen width added
* on each side of the desired image center .
2015-06-01 14:45:32 -04:00
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
const framepos_t center = req - > start + ( ( req - > end - req - > start ) / 2 ) ;
2015-06-23 14:26:10 -04:00
const framecnt_t image_samples = req - > width ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* we can request data from anywhere in the Source, between 0 and its length
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
framepos_t sample_start = max ( _region_start , ( center - image_samples ) ) ;
framepos_t sample_end = min ( center + image_samples , region_end ( ) ) ;
2016-02-28 12:07:46 -05:00
const int n_peaks = std : : max ( 1LL , llrint ( ceil ( ( sample_end - sample_start ) / ( req - > samples_per_pixel ) ) ) ) ;
2015-10-05 10:17:49 -04:00
2016-02-27 17:16:05 -05:00
assert ( n_peaks > 0 & & n_peaks < 32767 ) ;
2015-06-01 14:45:32 -04:00
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 .
*/
2015-10-05 10:17:49 -04:00
2015-06-01 21:08:42 -04:00
framecnt_t peaks_read = _region - > read_peaks ( peaks . get ( ) , n_peaks ,
sample_start , sample_end - sample_start ,
req - > channel ,
req - > samples_per_pixel ) ;
2015-10-05 10:17:49 -04:00
2016-03-01 08:43:28 -05:00
if ( req - > should_stop ( ) ) {
2016-03-01 12:36:45 -05:00
// cerr << "Request stopped after reading peaks\n";
2016-03-01 08:43:28 -05:00
return ;
}
2015-06-01 14:45:32 -04:00
req - > image = Cairo : : ImageSurface : : create ( Cairo : : FORMAT_ARGB32 , n_peaks , req - > height ) ;
2016-02-27 17:16:05 -05:00
// 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 ) ;
2015-06-01 14:45:32 -04:00
/* make sure we record the sample positions that were actually used */
req - > start = sample_start ;
req - > end = sample_end ;
2015-10-05 10:17:49 -04:00
2015-06-01 21:08:42 -04:00
if ( peaks_read > 0 ) {
2015-06-18 09:00:15 -04:00
/* 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 .
*/
2015-10-05 10:17:49 -04:00
2015-06-19 08:10:45 -04:00
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 ;
}
2015-06-01 21:08:42 -04:00
}
draw_image ( req - > image , peaks . get ( ) , n_peaks , req ) ;
} else {
draw_absent_image ( req - > image , peaks . get ( ) , n_peaks ) ;
}
2015-09-02 12:24:37 -04:00
} else {
2016-03-01 12:36:45 -05:00
// cerr << "Request stopped before image generation\n";
2015-10-05 10:17:49 -04:00
}
2015-06-01 14:45:32 -04:00
if ( in_render_thread & & ! req - > should_stop ( ) ) {
2016-03-01 08:43:44 -05:00
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 ) ) ;
2015-06-01 14:45:32 -04:00
const_cast < WaveView * > ( this ) - > ImageReady ( ) ; /* emit signal */
}
2015-09-02 12:24:37 -04:00
2014-06-07 11:47:38 -04:00
return ;
2013-06-20 14:37:31 -04:00
}
2015-06-01 14:45:32 -04:00
/** 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 ;
}
2013-04-04 00:32:52 -04:00
void
WaveView : : render ( Rect const & area , Cairo : : RefPtr < Cairo : : Context > context ) const
{
2013-04-12 11:31:17 -04:00
assert ( _samples_per_pixel ! = 0 ) ;
2015-10-05 10:17:49 -04:00
2013-04-04 00:32:52 -04:00
if ( ! _region ) {
return ;
}
2016-03-01 08:43:44 -05:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " render %1 at %2 \n " , this , g_get_monotonic_time ( ) ) ) ;
2015-06-23 14:26:10 -04:00
2015-06-01 14:45:32 -04:00
/* 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 .
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
Rect self = item_to_window ( Rect ( 0.0 , 0.0 , region_length ( ) / _samples_per_pixel , _height ) ) ;
2015-06-10 18:48:01 -04:00
// cerr << name << " RENDER " << area << " self = " << self << endl;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* Now lets get the intersection with the area we've been asked to draw */
2013-06-18 23:03:20 -04:00
boost : : optional < Rect > d = self . intersection ( area ) ;
2013-06-18 08:23:06 -04:00
2013-06-18 23:03:20 -04:00
if ( ! d ) {
2013-06-18 08:23:06 -04:00
return ;
}
2014-09-12 19:44:05 -04:00
2013-06-18 23:03:20 -04:00
Rect draw = d . get ( ) ;
2013-06-18 08:23:06 -04:00
2015-06-01 14:45:32 -04:00
/* "draw" is now a rectangle that defines the rectangle we need to
* update / render the waveview into , in window coordinate space .
*/
2015-10-05 10:17:49 -04:00
2013-06-20 23:12:19 -04:00
/* window coordinates - pixels where x=0 is the left edge of the canvas
2014-05-07 10:35:45 -04:00
* window . We round down in case we were asked to
2013-06-20 23:12:19 -04:00
* draw " between " pixels at the start and / or end .
2013-06-20 14:37:31 -04:00
*/
2015-10-05 10:17:49 -04:00
2015-02-11 21:59:25 -05:00
double draw_start = floor ( draw . x0 ) ;
2014-05-07 10:35:45 -04:00
const double draw_end = floor ( draw . x1 ) ;
2013-06-20 23:12:19 -04:00
2015-06-01 14:45:32 -04:00
// cerr << "Need to draw " << draw_start << " .. " << draw_end << " vs. " << area << " and self = " << self << endl;
2015-10-05 10:17:49 -04:00
2013-06-20 23:12:19 -04:00
/* image coordnates: pixels where x=0 is the start of this waveview,
* wherever it may be positioned . thus image_start = N means " an image
2015-06-01 14:45:32 -04:00
* that begins N pixels after the start of region that this waveview is
2014-09-12 19:44:05 -04:00
* representing .
2013-06-20 23:12:19 -04:00
*/
const framepos_t image_start = window_to_image ( self . x0 , draw_start ) ;
const framepos_t image_end = window_to_image ( self . x0 , draw_end ) ;
2015-10-05 10:17:49 -04:00
2013-06-20 23:12:19 -04:00
// cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
2015-10-05 10:17:49 -04:00
2015-10-04 14:51:05 -04:00
/* sample coordinates - note, these are not subject to rounding error
2015-06-01 14:45:32 -04:00
*
* " sample_start = N " means " the first sample we need to represent is N
* samples after the first sample of the region "
*/
2015-10-05 10:17:49 -04:00
2013-06-20 23:12:19 -04:00
framepos_t sample_start = _region_start + ( image_start * _samples_per_pixel ) ;
framepos_t sample_end = _region_start + ( image_end * _samples_per_pixel ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
// 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 ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
// cerr << debug_name() << " will need image spanning " << sample_start << " .. " << sample_end << " region spans " << _region_start << " .. " << region_end() << endl;
2015-06-10 18:48:01 -04:00
double image_origin_in_self_coordinates ;
2015-06-04 17:45:58 -04:00
boost : : shared_ptr < WaveViewCache : : Entry > image_to_draw ;
2015-10-05 10:17:49 -04:00
2015-09-02 08:28:26 -04:00
Glib : : Threads : : Mutex : : Lock lci ( current_image_lock ) ;
2015-06-01 14:45:32 -04:00
if ( _current_image ) {
/* check it covers the right sample range */
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
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 ) ;
2015-06-04 17:45:58 -04:00
image_to_draw = _current_image ;
2015-06-01 14:45:32 -04:00
}
}
2015-06-04 17:45:58 -04:00
if ( ! image_to_draw ) {
2013-06-20 23:12:19 -04:00
2015-06-01 14:45:32 -04:00
/* look it up */
2013-06-20 23:12:19 -04:00
2015-06-04 17:45:58 -04:00
bool full_image ;
image_to_draw = get_image ( sample_start , sample_end , full_image ) ;
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1 image to draw = %2 (full? %3) \n " , name , image_to_draw , full_image ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
if ( ! image_to_draw ) {
2015-06-01 14:45:32 -04:00
/* image not currently available. A redraw will be scheduled
when it is ready .
*/
return ;
}
2015-06-04 17:45:58 -04:00
if ( full_image ) {
/* found an image that covers our entire sample range,
* so keep a reference to it .
*/
_current_image = image_to_draw ;
}
2015-06-01 14:45:32 -04:00
}
2013-06-20 23:12:19 -04:00
2015-06-04 17:45:58 -04:00
/* compute the first pixel of the image that should be used when we
2015-10-04 14:51:05 -04:00
* render the specified range .
2015-06-04 17:45:58 -04:00
*/
2015-06-01 14:45:32 -04:00
2015-06-10 18:48:01 -04:00
image_origin_in_self_coordinates = ( image_to_draw - > start - _region_start ) / _samples_per_pixel ;
2015-10-05 10:17:49 -04:00
2015-02-11 21:59:25 -05:00
if ( _start_shift & & ( sample_start = = _region_start ) & & ( self . x0 = = draw . x0 ) ) {
2015-10-04 14:51:05 -04:00
/* we are going to draw the first pixel for this region, but
2015-02-11 21:59:25 -05:00
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;
2015-06-10 18:48:01 -04:00
//image_origin_in_self_coordinates += _start_shift;
2015-02-11 21:59:25 -05:00
}
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
/* 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 ;
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
if ( image_to_draw ! = _current_image ) {
2015-09-02 08:28:26 -04:00
lci . release ( ) ;
2015-06-10 18:48:01 -04:00
/* 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 .
*/
2016-03-01 12:06:29 -05:00
draw_width = min ( ( double ) image_to_draw - > image - > get_width ( ) , ( draw_end - draw_start ) ) ;
2015-06-10 18:48:01 -04:00
2016-03-01 12:06:29 -05:00
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 ) ,
2015-06-10 18:48:01 -04:00
image_to_draw - > image - > get_width ( ) , image_origin_in_self_coordinates ,
2016-03-01 12:06:29 -05:00
image_to_draw - > start , _region_start , draw_start ) ) ;
2015-06-04 17:45:58 -04:00
} else {
draw_width = draw_end - draw_start ;
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " use current image, span entire render width %1..%2 \n " , draw_start , draw_end ) ) ;
2015-06-04 17:45:58 -04:00
}
context - > rectangle ( draw_start , draw . y0 , draw_width , draw . height ( ) ) ;
2013-06-24 16:28:53 -04:00
/* round image origin position to an exact pixel in device space to
* avoid blurring
*/
2015-06-10 18:48:01 -04:00
double x = self . x0 + image_origin_in_self_coordinates ;
2013-06-24 16:28:53 -04:00
double y = self . y0 ;
context - > user_to_device ( x , y ) ;
x = round ( x ) ;
y = round ( y ) ;
context - > device_to_user ( x , y ) ;
2015-06-04 17:45:58 -04:00
/* the coordinates specify where in "user coordinates" (i.e. what we
* generally call " canvas coordinates " in this code ) the image origin
* will appear . So specifying ( 10 , 10 ) will put the upper left corner of
* the image at ( 10 , 10 ) in user space .
*/
2015-10-05 10:17:49 -04:00
2015-06-04 17:45:58 -04:00
context - > set_source ( image_to_draw - > image , x , y ) ;
2013-06-20 14:37:31 -04:00
context - > fill ( ) ;
2014-07-15 07:59:22 -04:00
2015-06-10 18:48:01 -04:00
/* image obtained, some of it painted to display: we are rendered.
Future calls to get_image_in_thread are now meaningful .
*/
rendered = true ;
2013-04-04 00:32:52 -04:00
}
void
WaveView : : compute_bounding_box ( ) const
{
if ( _region ) {
2015-06-01 14:45:32 -04:00
_bounding_box = Rect ( 0.0 , 0.0 , region_length ( ) / _samples_per_pixel , _height ) ;
2013-04-04 00:32:52 -04:00
} else {
_bounding_box = boost : : optional < Rect > ( ) ;
}
2014-09-12 19:44:05 -04:00
2013-04-04 00:32:52 -04:00
_bounding_box_dirty = false ;
}
2014-09-12 19:44:05 -04:00
2013-04-04 00:32:52 -04:00
void
WaveView : : set_height ( Distance height )
{
2013-04-24 15:42:14 -04:00
if ( height ! = _height ) {
begin_change ( ) ;
2014-06-07 11:47:38 -04:00
invalidate_image_cache ( ) ;
2013-04-24 15:42:14 -04:00
_height = height ;
2015-06-01 14:45:32 -04:00
get_image_in_thread = true ;
2015-10-05 10:17:49 -04:00
2013-04-24 15:42:14 -04:00
_bounding_box_dirty = true ;
end_change ( ) ;
}
2013-04-04 00:32:52 -04:00
}
void
WaveView : : set_channel ( int channel )
{
2013-04-24 15:42:14 -04:00
if ( channel ! = _channel ) {
begin_change ( ) ;
2014-06-07 11:47:38 -04:00
invalidate_image_cache ( ) ;
2013-04-24 15:42:14 -04:00
_channel = channel ;
2014-06-07 11:47:38 -04:00
2013-04-24 15:42:14 -04:00
_bounding_box_dirty = true ;
end_change ( ) ;
}
2013-04-04 00:32:52 -04:00
}
2013-04-16 10:07:52 -04:00
void
WaveView : : set_logscaled ( bool yn )
{
if ( _logscaled ! = yn ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-16 10:07:52 -04:00
_logscaled = yn ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 10:07:52 -04:00
}
}
void
2013-04-17 15:22:09 -04:00
WaveView : : gain_changed ( )
2013-04-16 10:07:52 -04:00
{
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
_region_amplitude = _region - > scale_amplitude ( ) ;
2015-06-19 08:10:45 -04:00
get_image_in_thread = true ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 10:07:52 -04:00
}
void
WaveView : : set_zero_color ( Color c )
{
if ( _zero_color ! = c ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-16 10:07:52 -04:00
_zero_color = c ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 10:07:52 -04:00
}
}
void
WaveView : : set_clip_color ( Color c )
{
if ( _clip_color ! = c ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-16 10:07:52 -04:00
_clip_color = c ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 10:07:52 -04:00
}
}
void
WaveView : : set_show_zero_line ( bool yn )
{
if ( _show_zero ! = yn ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-16 10:07:52 -04:00
_show_zero = yn ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 10:07:52 -04:00
}
}
void
WaveView : : set_shape ( Shape s )
{
if ( _shape ! = s ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-16 10:07:52 -04:00
_shape = s ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-16 18:02:12 -04:00
}
}
2013-04-17 15:22:09 -04:00
void
WaveView : : set_amplitude_above_axis ( double a )
{
2015-04-29 13:56:35 -04:00
if ( fabs ( _amplitude_above_axis - a ) > 0.01 ) {
2014-06-07 11:47:38 -04:00
begin_visual_change ( ) ;
invalidate_image_cache ( ) ;
2013-04-17 15:22:09 -04:00
_amplitude_above_axis = a ;
2015-06-19 08:10:45 -04:00
get_image_in_thread = true ;
2014-06-07 11:47:38 -04:00
end_visual_change ( ) ;
2013-04-17 15:22:09 -04:00
}
}
2013-04-16 18:02:12 -04:00
void
WaveView : : set_global_shape ( Shape s )
{
if ( _global_shape ! = s ) {
_global_shape = s ;
2015-08-19 21:01:51 -04:00
if ( images ) {
images - > clear_cache ( ) ;
}
2013-04-16 18:02:12 -04:00
VisualPropertiesChanged ( ) ; /* EMIT SIGNAL */
}
}
void
WaveView : : set_global_logscaled ( bool yn )
{
if ( _global_logscaled ! = yn ) {
_global_logscaled = yn ;
2015-08-19 21:01:51 -04:00
if ( images ) {
images - > clear_cache ( ) ;
}
2013-04-16 18:02:12 -04:00
VisualPropertiesChanged ( ) ; /* EMIT SIGNAL */
2013-04-16 10:07:52 -04:00
}
}
2015-06-01 14:45:32 -04:00
framecnt_t
WaveView : : region_length ( ) const
{
return _region - > length ( ) - ( _region_start - _region - > start ( ) ) ;
}
framepos_t
WaveView : : region_end ( ) const
{
return _region_start + region_length ( ) ;
}
2013-04-04 00:32:52 -04:00
void
2014-07-03 10:00:12 -04:00
WaveView : : set_region_start ( frameoffset_t start )
2013-04-04 00:32:52 -04:00
{
2013-04-24 15:42:14 -04:00
if ( ! _region ) {
return ;
}
2014-07-03 10:00:12 -04:00
if ( _region_start = = start ) {
return ;
}
begin_change ( ) ;
_region_start = start ;
_bounding_box_dirty = true ;
end_change ( ) ;
}
void
WaveView : : region_resized ( )
{
/* Called when the region start or end (thus length) has changed.
2013-04-24 15:42:14 -04:00
*/
2014-07-03 10:00:12 -04:00
if ( ! _region ) {
return ;
}
2013-04-24 15:42:14 -04:00
2014-07-03 10:00:12 -04:00
begin_change ( ) ;
_region_start = _region - > start ( ) ;
2013-04-04 00:32:52 -04:00
_bounding_box_dirty = true ;
2013-04-24 15:42:14 -04:00
end_change ( ) ;
2013-04-04 00:32:52 -04:00
}
2013-04-16 14:04:59 -04:00
void
2013-04-16 21:23:50 -04:00
WaveView : : set_global_gradient_depth ( double depth )
2013-04-16 14:04:59 -04:00
{
2013-04-16 21:23:50 -04:00
if ( _global_gradient_depth ! = depth ) {
_global_gradient_depth = depth ;
2013-04-16 18:02:12 -04:00
VisualPropertiesChanged ( ) ; /* EMIT SIGNAL */
2013-04-16 14:04:59 -04:00
}
}
2013-06-28 20:21:30 -04:00
void
WaveView : : set_global_show_waveform_clipping ( bool yn )
{
if ( _global_show_waveform_clipping ! = yn ) {
_global_show_waveform_clipping = yn ;
2015-01-21 13:35:04 -05:00
ClipLevelChanged ( ) ;
2013-06-28 20:21:30 -04:00
}
}
2015-02-09 17:55:05 -05:00
void
WaveView : : set_start_shift ( double pixels )
{
if ( pixels < 0 ) {
return ;
}
begin_visual_change ( ) ;
_start_shift = pixels ;
end_visual_change ( ) ;
}
2015-06-01 14:45:32 -04:00
void
WaveView : : cancel_my_render_request ( ) const
{
if ( ! images ) {
return ;
}
/* try to stop any current rendering of the request, or prevent it from
* ever starting up .
*/
2015-10-05 10:17:49 -04:00
2016-03-01 08:43:28 -05:00
Glib : : Threads : : Mutex : : Lock lm ( request_queue_lock ) ;
2015-06-01 14:45:32 -04:00
if ( current_request ) {
current_request - > cancel ( ) ;
}
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* now remove it from the queue and reset our request pointer so that
have no outstanding request ( that we know about )
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
request_queue . erase ( this ) ;
current_request . reset ( ) ;
2016-03-01 08:43:44 -05:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " %1 now has no request %2 \n " , this ) ) ;
2015-06-23 14:26:10 -04:00
2015-06-01 14:45:32 -04:00
}
2015-06-23 08:17:48 -04:00
void
WaveView : : set_image_cache_size ( uint64_t sz )
{
if ( ! images ) {
images = new WaveViewCache ;
}
images - > set_image_cache_threshold ( sz ) ;
}
2015-06-01 14:45:32 -04:00
/*-------------------------------------------------*/
void
WaveView : : start_drawing_thread ( )
{
if ( ! _drawing_thread ) {
_drawing_thread = Glib : : Threads : : Thread : : create ( sigc : : ptr_fun ( WaveView : : drawing_thread ) ) ;
}
}
void
WaveView : : stop_drawing_thread ( )
{
2015-10-22 06:20:42 -04:00
while ( _drawing_thread ) {
2015-06-01 14:45:32 -04:00
Glib : : Threads : : Mutex : : Lock lm ( request_queue_lock ) ;
g_atomic_int_set ( & drawing_thread_should_quit , 1 ) ;
request_cond . signal ( ) ;
}
}
void
WaveView : : drawing_thread ( )
{
using namespace Glib : : Threads ;
WaveView const * requestor ;
Mutex : : Lock lm ( request_queue_lock ) ;
bool run = true ;
while ( run ) {
/* remember that we hold the lock at this point, no matter what */
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( g_atomic_int_get ( & drawing_thread_should_quit ) ) {
break ;
}
if ( request_queue . empty ( ) ) {
request_cond . wait ( request_queue_lock ) ;
}
2015-09-01 19:46:31 -04:00
if ( request_queue . empty ( ) ) {
2015-10-22 06:20:42 -04:00
continue ;
2015-09-01 19:46:31 -04:00
}
2015-06-01 14:45:32 -04:00
/* remove the request from the queue (remember: the "request"
* is just a pointer to a WaveView object )
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
requestor = * ( request_queue . begin ( ) ) ;
request_queue . erase ( request_queue . begin ( ) ) ;
2016-03-01 08:43:44 -05:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " start request for %1 at %2 \n " , requestor , g_get_monotonic_time ( ) ) ) ;
2015-06-23 14:26:10 -04:00
2015-06-01 14:45:32 -04:00
boost : : shared_ptr < WaveViewThreadRequest > req = requestor - > current_request ;
if ( ! req ) {
continue ;
}
/* Generate an image. Unlock the request queue lock
* while we do this , so that other things can happen
* as we do rendering .
*/
2016-03-01 06:53:54 -05:00
lm . release ( ) ; /* some RAII would be good here */
2015-06-01 14:45:32 -04:00
try {
requestor - > generate_image ( req , true ) ;
} catch ( . . . ) {
req - > image . clear ( ) ; /* just in case it was set before the exception, whatever it was */
}
2016-03-01 06:53:54 -05:00
lm . acquire ( ) ;
2015-06-01 14:45:32 -04:00
req . reset ( ) ; /* drop/delete request as appropriate */
}
/* thread is vanishing */
_drawing_thread = 0 ;
}
/*-------------------------------------------------*/
WaveViewCache : : WaveViewCache ( )
: image_cache_size ( 0 )
, _image_cache_threshold ( 100 * 1048576 ) /* bytes */
{
}
WaveViewCache : : ~ WaveViewCache ( )
{
}
boost : : shared_ptr < WaveViewCache : : Entry >
WaveViewCache : : lookup_image ( boost : : shared_ptr < ARDOUR : : AudioSource > src ,
framepos_t start , framepos_t end ,
int channel ,
Coord height ,
float amplitude ,
Color fill_color ,
2015-06-04 17:45:58 -04:00
double samples_per_pixel ,
bool & full_coverage )
2015-06-01 14:45:32 -04:00
{
ImageCache : : iterator x ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( ( x = cache_map . find ( src ) ) = = cache_map . end ( ) ) {
/* nothing in the cache for this audio source at all */
return boost : : shared_ptr < WaveViewCache : : Entry > ( ) ;
}
CacheLine & caches = x - > second ;
2015-06-04 17:45:58 -04:00
boost : : shared_ptr < Entry > best_partial ;
framecnt_t max_coverage = 0 ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* Find a suitable ImageSurface, if it exists.
*/
for ( CacheLine : : iterator c = caches . begin ( ) ; c ! = caches . end ( ) ; + + c ) {
boost : : shared_ptr < Entry > e ( * c ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( channel ! = e - > channel
| | height ! = e - > height
| | amplitude ! = e - > amplitude
| | samples_per_pixel ! = e - > samples_per_pixel
| | fill_color ! = e - > fill_color ) {
continue ;
}
2016-03-01 08:43:44 -05:00
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 ;
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 ;
2015-06-04 17:45:58 -04:00
}
}
if ( best_partial ) {
2015-06-10 18:48:01 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " found PARTIAL image spanning %1..%2 partially covers %3..%4 \n " ,
best_partial - > start , best_partial - > end , start , end ) ) ;
2015-06-04 17:45:58 -04:00
use ( src , best_partial ) ;
full_coverage = false ;
return best_partial ;
2015-06-01 14:45:32 -04:00
}
return boost : : shared_ptr < Entry > ( ) ;
}
void
WaveViewCache : : consolidate_image_cache ( boost : : shared_ptr < ARDOUR : : AudioSource > src ,
int channel ,
Coord height ,
float amplitude ,
Color fill_color ,
double samples_per_pixel )
{
list < uint32_t > deletion_list ;
uint32_t other_entries = 0 ;
ImageCache : : iterator x ;
/* MUST BE CALLED FROM (SINGLE) GUI THREAD */
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( ( x = cache_map . find ( src ) ) = = cache_map . end ( ) ) {
return ;
}
CacheLine & caches = x - > second ;
for ( CacheLine : : iterator c1 = caches . begin ( ) ; c1 ! = caches . end ( ) ; ) {
CacheLine : : iterator nxt = c1 ;
+ + nxt ;
boost : : shared_ptr < Entry > e1 ( * c1 ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( channel ! = e1 - > channel
| | height ! = e1 - > height
| | amplitude ! = e1 - > amplitude
| | samples_per_pixel ! = e1 - > samples_per_pixel
| | fill_color ! = e1 - > fill_color ) {
/* doesn't match current properties, ignore and move on
* to the next one .
*/
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
other_entries + + ;
c1 = nxt ;
continue ;
}
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* c1 now points to a cached image entry that matches current
* properties . Check all subsequent cached imaged entries to
* see if there are others that also match but represent
* subsets of the range covered by this one .
*/
for ( CacheLine : : iterator c2 = c1 ; c2 ! = caches . end ( ) ; ) {
CacheLine : : iterator nxt2 = c2 ;
+ + nxt2 ;
boost : : shared_ptr < Entry > e2 ( * c2 ) ;
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( e1 = = e2 | | channel ! = e2 - > channel
| | height ! = e2 - > height
| | amplitude ! = e2 - > amplitude
| | samples_per_pixel ! = e2 - > samples_per_pixel
| | fill_color ! = e2 - > fill_color ) {
/* properties do not match, ignore for the
* purposes of consolidation .
*/
c2 = nxt2 ;
continue ;
}
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
if ( e2 - > start > = e1 - > start & & e2 - > end < = e1 - > end ) {
/* c2 is fully contained by c1, so delete it */
2015-06-19 07:55:38 -04:00
caches . erase ( c2 ) ;
/* and re-start the whole iteration */
nxt = caches . begin ( ) ;
break ;
2015-06-01 14:45:32 -04:00
}
c2 = nxt2 ;
}
c1 = nxt ;
}
}
void
WaveViewCache : : use ( boost : : shared_ptr < ARDOUR : : AudioSource > src , boost : : shared_ptr < Entry > ce )
{
ce - > timestamp = g_get_monotonic_time ( ) ;
}
void
WaveViewCache : : add ( boost : : shared_ptr < ARDOUR : : AudioSource > src , boost : : shared_ptr < Entry > ce )
{
/* MUST BE CALLED FROM (SINGLE) GUI THREAD */
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
Cairo : : RefPtr < Cairo : : ImageSurface > img ( ce - > image ) ;
image_cache_size + = img - > get_height ( ) * img - > get_width ( ) * 4 ; /* 4 = bytes per FORMAT_ARGB32 pixel */
if ( cache_full ( ) ) {
cache_flush ( ) ;
}
ce - > timestamp = g_get_monotonic_time ( ) ;
cache_map [ src ] . push_back ( ce ) ;
}
uint64_t
WaveViewCache : : compute_image_cache_size ( )
{
uint64_t total = 0 ;
for ( ImageCache : : iterator s = cache_map . begin ( ) ; s ! = cache_map . end ( ) ; + + s ) {
CacheLine & per_source_cache ( s - > second ) ;
for ( CacheLine : : iterator c = per_source_cache . begin ( ) ; c ! = per_source_cache . end ( ) ; + + c ) {
Cairo : : RefPtr < Cairo : : ImageSurface > img ( ( * c ) - > image ) ;
total + = img - > get_height ( ) * img - > get_width ( ) * 4 ; /* 4 = bytes per FORMAT_ARGB32 pixel */
}
}
return total ;
}
bool
WaveViewCache : : cache_full ( )
{
return image_cache_size > _image_cache_threshold ;
}
void
WaveViewCache : : cache_flush ( )
{
2015-06-23 08:42:55 -04:00
/* Build a sortable list of all cache entries */
2015-10-05 10:17:49 -04:00
2015-06-23 08:42:55 -04:00
CacheList cache_list ;
2015-06-01 14:45:32 -04:00
2015-06-23 08:42:55 -04:00
for ( ImageCache : : const_iterator cm = cache_map . begin ( ) ; cm ! = cache_map . end ( ) ; + + cm ) {
for ( CacheLine : : const_iterator cl = cm - > second . begin ( ) ; cl ! = cm - > second . end ( ) ; + + cl ) {
cache_list . push_back ( make_pair ( cm - > first , * cl ) ) ;
}
}
2015-10-05 10:17:49 -04:00
2015-06-01 14:45:32 -04:00
/* sort list in LRU order */
2015-06-23 08:42:55 -04:00
SortByTimestamp sorter ;
2015-06-01 14:45:32 -04:00
sort ( cache_list . begin ( ) , cache_list . end ( ) , sorter ) ;
2015-06-22 22:00:45 -04:00
while ( image_cache_size > _image_cache_threshold & & ! cache_map . empty ( ) & & ! cache_list . empty ( ) ) {
2015-06-01 14:45:32 -04:00
ListEntry & le ( cache_list . front ( ) ) ;
ImageCache : : iterator x ;
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
if ( ( x = cache_map . find ( le . first ) ) ! = cache_map . end ( ) ) {
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
CacheLine & cl = x - > second ;
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
for ( CacheLine : : iterator c = cl . begin ( ) ; c ! = cl . end ( ) ; + + c ) {
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
if ( * c = = le . second ) {
2015-06-23 08:17:48 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " Removing cache line entry for %1 \n " , x - > first - > name ( ) ) ) ;
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
/* Remove this entry from this cache line */
cl . erase ( c ) ;
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
if ( cl . empty ( ) ) {
/* remove cache line from main cache: no more entries */
cache_map . erase ( x ) ;
}
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
break ;
2015-06-01 14:45:32 -04:00
}
}
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
Cairo : : RefPtr < Cairo : : ImageSurface > img ( le . second - > image ) ;
uint64_t size = img - > get_height ( ) * img - > get_width ( ) * 4 ; /* 4 = bytes per FORMAT_ARGB32 pixel */
2015-10-05 10:17:49 -04:00
2015-06-22 22:00:45 -04:00
if ( image_cache_size > size ) {
image_cache_size - = size ;
} else {
image_cache_size = 0 ;
}
2015-06-23 08:17:48 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " cache shrunk to %1 \n " , image_cache_size ) ) ;
2015-06-01 14:45:32 -04:00
}
2015-10-04 14:51:05 -04:00
2015-06-22 22:00:45 -04:00
/* Remove from the linear list, even if we didn't find it in
* the actual cache_mao
*/
2015-06-01 14:45:32 -04:00
cache_list . erase ( cache_list . begin ( ) ) ;
}
}
2015-08-19 21:01:51 -04:00
void
WaveViewCache : : clear_cache ( )
{
DEBUG_TRACE ( DEBUG : : WaveView , " clear cache \n " ) ;
const uint64_t image_cache_threshold = _image_cache_threshold ;
_image_cache_threshold = 0 ;
cache_flush ( ) ;
_image_cache_threshold = image_cache_threshold ;
}
2015-06-01 14:45:32 -04:00
void
WaveViewCache : : set_image_cache_threshold ( uint64_t sz )
{
2015-08-19 21:01:51 -04:00
DEBUG_TRACE ( DEBUG : : WaveView , string_compose ( " new image cache size %1 \n " , sz ) ) ;
2015-06-01 14:45:32 -04:00
_image_cache_threshold = sz ;
cache_flush ( ) ;
}