@ -4,18 +4,107 @@
# include "analyzer.h"
# include "../codeine.h"
# include "../debug.h"
# include <math.h> //interpolate()
# include <cmath> //interpolate()
# include <tdeglobalsettings.h>
# include <tqevent.h> //event()
# include "xineEngine.h"
# include "fht.cpp"
template < class W >
Analyzer : : Base < W > : : Base ( TQWidget * parent , uint timeout )
Analyzer : : Base < W > : : Base ( TQWidget * parent , uint timeout , uint scopeSize )
: W ( parent , " Analyzer " )
, m_timeout ( timeout )
, m_fht ( new FHT ( scopeSize ) )
{ }
template < class W > void
Analyzer : : Base < W > : : transform ( Scope & scope ) //virtual
{
// This is a standard transformation that should give
// an FFT scope that has bands for pretty analyzers
// NOTE: resizing here is redundant as FHT routines only calculate FHT::size() values
// scope.resize( m_fht->size() );
float * front = & scope . front ( ) ;
auto * f = new float [ m_fht - > size ( ) ] ;
m_fht - > copy ( & f [ 0 ] , front ) ;
m_fht - > logSpectrum ( front , & f [ 0 ] ) ;
m_fht - > scale ( front , 1.0 / 20 ) ;
scope . resize ( m_fht - > size ( ) / 2 ) ; //second half of values are rubbish
delete [ ] f ;
}
template < class W >
void Analyzer : : Base < W > : : drawFrame ( )
{
switch ( Codeine : : engine ( ) - > state ( ) )
{
case Engine : : Playing :
{
const Engine : : Scope & theScope = Codeine : : engine ( ) - > scope ( ) ;
static Scope scope ( 512 ) ;
int i = 0 ;
// Convert to mono.
// The Analyzer requires mono, but xine reports interleaved PCM.
for ( int x = 0 ; x < m_fht - > size ( ) ; + + x )
{
// Average between the channels.
scope [ x ] = static_cast < double > ( theScope [ i ] + theScope [ i + 1 ] ) / ( 2 * ( 1 < < 15 ) ) ;
i + = 2 ;
}
transform ( scope ) ;
analyze ( scope ) ;
scope . resize ( m_fht - > size ( ) ) ;
break ;
}
case Engine : : Paused :
{
break ;
}
default :
{
demo ( ) ;
break ;
}
}
}
template < class W >
void Analyzer : : Base < W > : : demo ( )
{
static int t = 201 ; //FIXME make static to namespace perhaps
if ( t > 999 )
{
// 0 = wasted calculations
t = 1 ;
}
if ( t < 201 )
{
Scope s ( 32 ) ;
const auto dt = static_cast < double > ( t ) / 200.0 ;
for ( unsigned i = 0 ; i < s . size ( ) ; + + i )
{
s [ i ] = dt * ( sin ( M_PI + ( i * M_PI ) / s . size ( ) ) + 1.0 ) ;
}
analyze ( s ) ;
}
else
{
analyze ( Scope ( 32 , 0 ) ) ;
}
+ + t ;
}
template < class W > bool
Analyzer : : Base < W > : : event ( TQEvent * e )
{
@ -36,98 +125,516 @@ Analyzer::Base<W>::event( TQEvent *e )
}
Analyzer : : Base2D : : Base2D ( TQWidget * parent , uint timeout )
: Base < TQWidget > ( parent , timeout )
Analyzer : : Base2D : : Base2D ( TQWidget * parent , uint timeout , uint scopeSize )
: Base < TQWidget > ( parent , timeout , scopeSize )
{
setWFlags ( TQt : : WNoAutoErase ) ; //no flicker
connect ( & m_timer , TQ_SIGNAL ( timeout ( ) ) , TQ_SLOT ( draw ( ) ) ) ;
}
void
Analyzer : : Base2D : : draw ( )
{
switch ( Codeine : : engine ( ) - > state ( ) ) {
case Engine : : Playing :
Analyzer : : Base2D : : resizeEvent ( TQResizeEvent * e )
{
const Engine : : Scope & thescope = Codeine : : engine ( ) - > scope ( ) ;
static Analyzer : : Scope scope ( Analyzer : : SCOPE_SIZE ) ;
for ( int x = 0 ; x < Analyzer : : SCOPE_SIZE ; + + x )
scope [ x ] = double ( thescope [ x ] ) / ( 1 < < 15 ) ;
transform ( scope ) ;
analyze ( scope ) ;
scope . resize ( Analyzer : : SCOPE_SIZE ) ;
bitBlt ( this , 0 , 0 , canvas ( ) ) ;
break ;
}
case Engine : : Paused :
break ;
m_background . resize ( size ( ) ) ;
m_canvas . resize ( size ( ) ) ;
m_background . fill ( backgroundColor ( ) ) ;
eraseCanvas ( ) ;
default :
erase ( ) ;
}
TQWidget : : resizeEvent ( e ) ;
}
void
Analyzer : : Base2D : : resizeEvent ( TQResizeEvent * )
void Analyzer : : Base2D : : paletteChange ( const TQPalette & )
{
m_ canvas. resize ( size ( ) ) ;
m_canvas. fill ( colorGroup ( ) . background ( ) ) ;
m_background . fill ( backgroundColor ( ) ) ;
eraseCanvas ( ) ;
}
// Author: Max Howell <max.howell@methylblue.com>, (C) 2003
// Copyright: See COPYING file that comes with this distribution
# include <tqpainter.h>
Analyzer : : Block : : Block ( TQWidget * parent )
: Analyzer : : Base2D ( parent , 20 )
: Analyzer : : Base2D ( parent , 20 , 9 )
, m_scope ( MIN_COLUMNS )
, m_barPixmap ( 1 , 1 )
, m_topBarPixmap ( WIDTH , HEIGHT )
, m_store ( 1 < < 8 , 0 )
, m_fadeBars ( FADE_SIZE )
, m_fadeIntensity ( 1 < < 8 , 32 )
, m_fadePos ( 1 < < 8 , 50 )
, m_columns ( 0 )
, m_rows ( 0 )
, m_y ( 0 )
, m_step ( 0 )
{
setMinimumWidth ( 64 ) ; //-1 is padding, no drawing takes place there
setMaximumWidth ( 128 ) ;
// -1 is padding, no drawing takes place there
setMinimumSize ( MIN_COLUMNS * ( WIDTH + 1 ) - 1 , MIN_ROWS * ( HEIGHT + 1 ) - 1 ) ;
setMaximumWidth ( MAX_COLUMNS * ( WIDTH + 1 ) - 1 ) ;
//TODO yes, do height for width
for ( auto & m_fadeBar : m_fadeBars )
{
m_fadeBar . resize ( 1 , 1 ) ;
}
}
void
Analyzer : : Block : : transform ( Analyzer : : Scope & scope ) //pure virtual
Analyzer : : Block : : transform ( Analyzer : : Scope & s ) //pure virtual
{
static FHT fht ( Analyzer : : SCOPE_SIZE_EXP ) ;
for ( uint x = 0 ; x < s . size ( ) ; + + x )
s [ x ] * = 2 ;
for ( uint x = 0 ; x < scope . size ( ) ; + + x )
scope [ x ] * = 2 ;
float * front = static_cast < float * > ( & s . front ( ) ) ;
float * front = static_cast < float * > ( & scope . front ( ) ) ;
m_fht - > spectrum ( front ) ;
m_fht - > scale ( front , 1.0 / 20 ) ;
fht . spectrum ( front ) ;
fht . scale ( front , 1.0 / 40 ) ;
//the second half is pretty dull, so only show it if the user has a large analyzer
//by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good!
s . resize ( m_scope . size ( ) < = MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : m_scope . size ( ) ) ;
}
void
Analyzer : : Block : : analyze ( const Analyzer : : Scope & s )
{
canvas ( ) - > fill ( colorGroup ( ) . foreground ( ) . light ( ) ) ;
// y = 2 3 2 1 0 2
// . . . . # .
// . . . # # .
// # . # # # #
// # # # # # #
//
// visual aid for how this analyzer works.
// y represents the number of blanks
// y starts from the top and increases in units of blocks
// m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
// if it contains 6 elements there are 5 rows in the analyzer
interpolate ( s , m_scope ) ;
// Paint the background
bitBlt ( canvas ( ) , 0 , 0 , background ( ) ) ;
unsigned y ;
for ( unsigned x = 0 ; x < m_scope . size ( ) ; + + x )
{
if ( m_yScale . empty ( ) )
{
return ;
}
// determine y
for ( y = 0 ; m_scope [ x ] < m_yScale [ y ] ; + + y )
;
TQPainter p ( canvas ( ) ) ;
p . setPen ( colorGroup ( ) . background ( ) ) ;
// this is opposite to what you'd think, higher than y
// means the bar is lower than y (physically)
if ( static_cast < float > ( y ) > m_store [ x ] )
{
y = static_cast < int > ( m_store [ x ] + = m_step ) ;
}
else
{
m_store [ x ] = y ;
}
const double F = double ( height ( ) ) / ( log10 ( 256 ) * 1.1 /*<- max. amplitude*/ ) ;
// if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout
// if the fadeout is quite faded now, then display the new one
if ( y < = m_fadePos [ x ] /*|| m_fadeIntensity[x] < FADE_SIZE / 3*/ )
{
m_fadePos [ x ] = y ;
m_fadeIntensity [ x ] = FADE_SIZE ;
}
for ( uint x = 0 ; x < s . size ( ) ; + + x )
//we draw the blank bit
p . drawLine ( x , 0 , x , int ( height ( ) - log10 ( s [ x ] * 256.0 ) * F ) ) ;
if ( m_fadeIntensity [ x ] > 0 )
{
const unsigned offset = - - m_fadeIntensity [ x ] ;
const unsigned y = m_y + ( m_fadePos [ x ] * ( HEIGHT + 1 ) ) ;
bitBlt ( canvas ( ) , x * ( WIDTH + 1 ) , y , & m_fadeBars [ offset ] , 0 , 0 , WIDTH , height ( ) - y ) ;
}
if ( m_fadeIntensity [ x ] = = 0 )
{
m_fadePos [ x ] = m_rows ;
}
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are
bitBlt ( canvas ( ) , x * ( WIDTH + 1 ) , y * ( HEIGHT + 1 ) + m_y , bar ( ) , 0 , y * ( HEIGHT + 1 ) ) ;
}
for ( unsigned x = 0 ; x < m_store . size ( ) ; + + x )
{
bitBlt ( canvas ( ) , x * ( WIDTH + 1 ) , int ( m_store [ x ] ) * ( HEIGHT + 1 ) + m_y , & m_topBarPixmap ) ;
}
}
static void adjustToLimits ( const int & b , int & f , unsigned & amount )
{
// with a range of 0-255 and maximum adjustment of amount,
// maximise the difference between f and b
if ( b < f )
{
if ( b > 255 - f )
{
amount - = f ;
f = 0 ;
}
else
{
amount - = ( 255 - f ) ;
f = 255 ;
}
}
else
{
if ( f > 255 - b )
{
amount - = f ;
f = 0 ;
}
else
{
amount - = ( 255 - f ) ;
f = 255 ;
}
}
}
/**
* Clever contrast function
*
* It will try to adjust the foreground color such that it contrasts well with the background
* It won ' t modify the hue of fg unless absolutely necessary
* @ return the adjusted form of fg
*/
TQColor ensureContrast ( const TQColor & bg , const TQColor & fg , unsigned _amount = 150 )
{
class OutputOnExit
{
public :
explicit OutputOnExit ( const TQColor & color )
: c ( color )
{
}
~ OutputOnExit ( )
{
int h , s , v ;
c . getHsv ( & h , & s , & v ) ;
}
private :
const TQColor & c ;
} ;
// hack so I don't have to cast everywhere
# define amount static_cast<int>(_amount)
// #define STAMP debug() << (TQValueList<int>() << fh << fs << fv) << endl;
// #define STAMP1( string ) debug() << string << ": " << (TQValueList<int>() << fh << fs << fv) << endl;
// #define STAMP2( string, value ) debug() << string << "=" << value << ": " << (TQValueList<int>() << fh << fs << fv) << endl;
OutputOnExit allocateOnTheStack ( fg ) ;
int bh , bs , bv ;
int fh , fs , fv ;
bg . getHsv ( & bh , & bs , & bv ) ;
fg . getHsv ( & fh , & fs , & fv ) ;
int dv = abs ( bv - fv ) ;
// STAMP2( "DV", dv );
// value is the best measure of contrast
// if there is enough difference in value already, return fg unchanged
if ( dv > amount )
{
return fg ;
}
int ds = abs ( bs - fs ) ;
// STAMP2( "DS", ds );
// saturation is good enough too. But not as good. TODO adapt this a little
if ( ds > amount )
{
return fg ;
}
int dh = abs ( bh - fh ) ;
// STAMP2( "DH", dh );
if ( dh > 120 )
{
// a third of the colour wheel automatically guarantees contrast
// but only if the values are high enough and saturations significant enough
// to allow the colours to be visible and not be shades of grey or black
// check the saturation for the two colours is sufficient that hue alone can
// provide sufficient contrast
if ( ds > amount / 2 & & ( bs > 125 & & fs > 125 ) )
{
// STAMP1( "Sufficient saturation difference, and hues are complimentary" );
return fg ;
}
if ( dv > amount / 2 & & ( bv > 125 & & fv > 125 ) )
{
// STAMP1( "Sufficient value difference, and hues are complimentary" );
return fg ;
}
// STAMP1( "Hues are complimentary but we must modify the value or saturation of the contrasting colour" );
// but either the colours are two desaturated, or too dark
// so we need to adjust the system, although not as much
///_amount /= 2;
}
if ( fs < 50 & & ds < 40 )
{
// low saturation on a low saturation is sad
const int tmp = 50 - fs ;
fs = 50 ;
if ( amount > tmp )
{
_amount - = tmp ;
}
else
{
_amount = 0 ;
}
}
// test that there is available value to honor our contrast requirement
if ( 255 - dv < amount )
{
// we have to modify the value and saturation of fg
//adjustToLimits( bv, fv, amount );
// STAMP
// see if we need to adjust the saturation
if ( amount > 0 )
{
adjustToLimits ( bs , fs , _amount ) ;
}
// STAMP
// see if we need to adjust the hue
if ( amount > 0 )
{
fh + = amount ; // cycles around
}
int
Analyzer : : Block : : heightForWidth ( int w ) const
// STAMP
return TQColor ( fh , fs , fv , TQColor : : Hsv ) ;
}
// STAMP
if ( fv > bv & & bv > amount )
{
return TQColor ( fh , fs , bv - amount , TQColor : : Hsv ) ;
}
// STAMP
if ( fv < bv & & fv > amount )
{
return TQColor ( fh , fs , fv - amount , TQColor : : Hsv ) ;
}
// STAMP
if ( fv > bv & & ( 255 - fv > amount ) )
{
return TQColor ( fh , fs , fv + amount , TQColor : : Hsv ) ;
}
// STAMP
if ( fv < bv & & ( 255 - bv > amount ) )
{
return w / 2 ;
return TQColor ( fh , fs , bv + amount , TQColor : : Hsv ) ;
}
// STAMP
// debug() << "Something went wrong!\n";
return TQt : : blue ;
# undef amount
// #undef STAMP
}
void Analyzer : : Block : : paletteChange ( const TQPalette & )
{
const TQColor bg = palette ( ) . active ( ) . background ( ) ;
const TQColor fg = ensureContrast ( bg , TDEGlobalSettings : : activeTitleColor ( ) ) ;
m_topBarPixmap . fill ( fg ) ;
const double dr = 15 * double ( bg . red ( ) - fg . red ( ) ) / ( m_rows * 16 ) ;
const double dg = 15 * double ( bg . green ( ) - fg . green ( ) ) / ( m_rows * 16 ) ;
const double db = 15 * double ( bg . blue ( ) - fg . blue ( ) ) / ( m_rows * 16 ) ;
const int r = fg . red ( ) , g = fg . green ( ) , b = fg . blue ( ) ;
bar ( ) - > fill ( bg ) ;
TQPainter p ( bar ( ) ) ;
for ( int y = 0 ; ( uint ) y < m_rows ; + + y )
{
// graduate the fg color
p . fillRect ( 0 , y * ( HEIGHT + 1 ) , WIDTH , HEIGHT , TQColor ( r + int ( dr * y ) , g + int ( dg * y ) , b + int ( db * y ) ) ) ;
}
{
const TQColor bg = palette ( ) . active ( ) . background ( ) . dark ( 112 ) ;
// make a complimentary fadebar colour
// TODO dark is not always correct, dumbo!
int h , s , v ;
palette ( ) . active ( ) . background ( ) . dark ( 150 ) . getHsv ( & h , & s , & v ) ;
const TQColor fg ( h + 120 , s , v , TQColor : : Hsv ) ;
const double dr = fg . red ( ) - bg . red ( ) ;
const double dg = fg . green ( ) - bg . green ( ) ;
const double db = fg . blue ( ) - bg . blue ( ) ;
const int r = bg . red ( ) , g = bg . green ( ) , b = bg . blue ( ) ;
// Precalculate all fade-bar pixmaps
for ( int y = 0 ; y < FADE_SIZE ; + + y )
{
m_fadeBars [ y ] . fill ( palette ( ) . active ( ) . background ( ) ) ;
TQPainter f ( & m_fadeBars [ y ] ) ;
for ( int z = 0 ; ( uint ) z < m_rows ; + + z )
{
const double Y = 1.0 - ( log10 ( static_cast < float > ( FADE_SIZE ) - y ) / log10 ( static_cast < float > ( FADE_SIZE ) ) ) ;
f . fillRect ( 0 , z * ( HEIGHT + 1 ) , WIDTH , HEIGHT , TQColor ( r + int ( dr * Y ) , g + int ( dg * Y ) , b + int ( db * Y ) ) ) ;
}
}
}
drawBackground ( ) ;
}
void Analyzer : : Block : : resizeEvent ( TQResizeEvent * e )
{
TQWidget : : resizeEvent ( e ) ;
canvas ( ) - > resize ( size ( ) ) ;
background ( ) - > resize ( size ( ) ) ;
const uint oldRows = m_rows ;
// all is explained in analyze()..
// +1 to counter -1 in maxSizes, trust me we need this!
m_columns = kMax ( uint ( double ( width ( ) + 1 ) / ( WIDTH + 1 ) ) , ( uint ) MAX_COLUMNS ) ;
m_rows = uint ( double ( height ( ) + 1 ) / ( HEIGHT + 1 ) ) ;
// this is the y-offset for drawing from the top of the widget
m_y = ( height ( ) - ( m_rows * ( HEIGHT + 1 ) ) + 2 ) / 2 ;
m_scope . resize ( m_columns ) ;
if ( m_rows ! = oldRows )
{
m_barPixmap . resize ( WIDTH , m_rows * ( HEIGHT + 1 ) ) ;
for ( uint i = 0 ; i < FADE_SIZE ; + + i )
{
m_fadeBars [ i ] . resize ( WIDTH , m_rows * ( HEIGHT + 1 ) ) ;
}
m_yScale . resize ( m_rows + 1 ) ;
const uint PRE = 1 , PRO = 1 ; //PRE and PRO allow us to restrict the range somewhat
for ( uint z = 0 ; z < m_rows ; + + z )
{
m_yScale [ z ] = 1 - ( log10 ( PRE + z ) / log10 ( PRE + m_rows + PRO ) ) ;
}
m_yScale [ m_rows ] = 0 ;
determineStep ( ) ;
paletteChange ( palette ( ) ) ;
}
else if ( width ( ) > e - > oldSize ( ) . width ( ) | | height ( ) > e - > oldSize ( ) . height ( ) )
{
drawBackground ( ) ;
}
analyze ( m_scope ) ;
}
void Analyzer : : Block : : determineStep ( )
{
// falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels)
// I calculated the value 30 based on some trial and error
const double fallTime = 30 * m_rows ;
m_step = double ( m_rows * 80 ) / fallTime ; // 80 = ~milliseconds between signals with audio data
}
void Analyzer : : Block : : drawBackground ( )
{
const TQColor bg = palette ( ) . active ( ) . background ( ) ;
const TQColor bgdark = bg . dark ( 112 ) ;
background ( ) - > fill ( bg ) ;
TQPainter p ( background ( ) ) ;
for ( int x = 0 ; ( uint ) x < m_columns ; + + x )
{
for ( int y = 0 ; ( uint ) y < m_rows ; + + y )
{
p . fillRect ( x * ( WIDTH + 1 ) , y * ( HEIGHT + 1 ) + m_y , WIDTH , HEIGHT , bgdark ) ;
}
}
setErasePixmap ( * background ( ) ) ;
}
void Analyzer : : interpolate ( const Scope & inVec , Scope & outVec )
{
double pos = 0.0 ;
const double step = ( double ) inVec . size ( ) / ( double ) outVec . size ( ) ;
for ( uint i = 0 ; i < outVec . size ( ) ; + + i , pos + = step )
{
const double error = pos - std : : floor ( pos ) ;
const unsigned long offset = ( unsigned long ) pos ;
unsigned long indexLeft = offset + 0 ;
if ( indexLeft > = inVec . size ( ) )
{
indexLeft = inVec . size ( ) - 1 ;
}
unsigned long indexRight = offset + 1 ;
if ( indexRight > = inVec . size ( ) )
{
indexRight = inVec . size ( ) - 1 ;
}
outVec [ i ] = inVec [ indexLeft ] * ( 1.0 - error ) +
inVec [ indexRight ] * error ;
}
}
# include "analyzer.moc"