[amarok] /: Add an audio analyzer visualization applet.

Mark Kretschmann kretschmann at kde.org
Wed May 22 11:41:13 UTC 2013


Git commit 0009faab5a60f89c3db59aafb6c000d3642be14f by Mark Kretschmann.
Committed on 14/05/2013 at 11:26.
Pushed by markey into branch 'master'.

Add an audio analyzer visualization applet.

The analyzer code itself is lifted from Amarok 1.x and then adapted for our
needs. The architecture allows adding multiple different visualizations, both
2d (QPainter) and 3d (OpenGL). Currently I have only implemented the
"BlockAnalyzer" which is rendered with QPainter, so it's guaranteed to work
everywhere.

Caveat:
Due to limitations in Phonon-VLC (and libVLC), the analyzer currently
only works with Phonon-GStreamer.

CCMAIL: amarok-devel at kde.org

M  +1    -0    ChangeLog
M  +3    -0    src/CMakeLists.txt
A  +236  -0    src/analyzer/AnalyzerBase.cpp     [License: GPL (v2+)]
A  +126  -0    src/analyzer/AnalyzerBase.h     [License: GPL (v2+)]
A  +286  -0    src/analyzer/BlockAnalyzer.cpp     [License: GPL (v2+)]
A  +73   -0    src/analyzer/BlockAnalyzer.h     [License: GPL (v2+)]
A  +253  -0    src/analyzer/fht.cpp     [License: GPL (v2+)]
A  +125  -0    src/analyzer/fht.h     [License: Public Domain GPL (v2+)]
M  +4    -4    src/context/applets/CMakeLists.txt
A  +56   -0    src/context/applets/analyzer/AnalyzerApplet.cpp     [License: GPL (v2+)]
A  +38   -0    src/context/applets/analyzer/AnalyzerApplet.h     [License: GPL (v2+)]
A  +24   -0    src/context/applets/analyzer/CMakeLists.txt
A  +18   -0    src/context/applets/analyzer/amarok-context-applet-analyzer.desktop
M  +3    -5    src/context/engines/CMakeLists.txt

http://commits.kde.org/amarok/0009faab5a60f89c3db59aafb6c000d3642be14f

diff --git a/ChangeLog b/ChangeLog
index 78be61a..ecf25f8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,7 @@ Amarok ChangeLog
 
 VERSION 2.8-Beta 1
   FEATURES:
+   * Added an audio analyzer visualization applet.
    * Added a function for resetting the GUI layout back to default state. (BR 300753)
    * Pressing enter when searching collections now adds found tracks to the playlist and
      clears the search bar, this is a very convenient way to populate your playlist.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2d51909..0d8ef1d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -616,6 +616,9 @@ set(amaroklib_LIB_SRCS
     SvgHandler.cpp
     SvgTinter.cpp
     TrayIcon.cpp
+    analyzer/AnalyzerBase.cpp
+    analyzer/BlockAnalyzer.cpp
+    analyzer/fht.cpp
     core-impl/meta/timecode/TimecodeObserver.cpp
     core-impl/meta/timecode/TimecodeMeta.cpp
     core-impl/meta/timecode/TimecodeTrackProvider.cpp
diff --git a/src/analyzer/AnalyzerBase.cpp b/src/analyzer/AnalyzerBase.cpp
new file mode 100644
index 0000000..83f33ba
--- /dev/null
+++ b/src/analyzer/AnalyzerBase.cpp
@@ -0,0 +1,236 @@
+/****************************************************************************************
+ * Copyright (c) 2003 Max Howell <max.howell at methylblue.com>                            *
+ * Copyright (c) 2009 Martin Sandsmark <martin.sandsmark at kde.org>                       *
+ *                                                                                      *
+ * 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, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#include "AnalyzerBase.h"
+
+#include "EngineController.h"
+
+#include <cmath> // interpolate()
+
+#include <QEvent> // event()
+#include <QPainter>
+
+#include <phonon/audiodataoutput.h>
+
+
+// INSTRUCTIONS Base2D
+// 1. do anything that depends on height() in init(), Base2D will call it before you are shown
+// 2. otherwise you can use the constructor to initialise things
+// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
+// 4. if you want to manipulate the scope, reimplement transform()
+// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
+// TODO make an INSTRUCTIONS file
+//can't mod scope in analyze you have to use transform
+
+
+Analyzer::Base::Base( QWidget *parent, uint scopeSize )
+    : QWidget( parent )
+    , m_fht( new FHT( scopeSize ) )
+{
+    connect( EngineController::instance(), SIGNAL( audioDataReady( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ),
+             this,   SLOT( drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ) );
+}
+
+void
+Analyzer::Base::transform( QVector<float> &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 = static_cast<float*>( &scope.front() );
+
+    float* 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;
+}
+
+void
+Analyzer::Base::drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope )
+{
+    if( thescope.isEmpty() )
+        return;
+
+    static QVector<float> scope( 512 );
+    int i = 0;
+
+    for( uint x = 0; ( int )x < m_fht->size(); ++x )
+    {
+        if( thescope.size() == 1 )  // Mono
+        {
+            scope[x] = double( thescope[Phonon::AudioDataOutput::LeftChannel][x] );
+        }
+        else     // Anything > Mono is treated as Stereo
+        {
+            scope[x] = double( thescope[Phonon::AudioDataOutput::LeftChannel][x]
+                               + thescope[Phonon::AudioDataOutput::RightChannel][x] )
+                       / ( 2 * ( 1 << 15 ) ); // Average between the channels
+        }
+        i += 2;
+    }
+
+    transform( scope );
+    analyze( scope );
+
+    scope.resize( m_fht->size() );
+
+    update();
+}
+
+int
+Analyzer::Base::resizeExponent( int exp )
+{
+    if( exp < 3 )
+        exp = 3;
+    else if( exp > 9 )
+        exp = 9;
+
+    if( exp != m_fht->sizeExp() )
+    {
+        delete m_fht;
+        m_fht = new FHT( exp );
+    }
+    return exp;
+}
+
+int
+Analyzer::Base::resizeForBands( int bands )
+{
+    int exp;
+    if( bands <= 8 )
+        exp = 4;
+    else if( bands <= 16 )
+        exp = 5;
+    else if( bands <= 32 )
+        exp = 6;
+    else if( bands <= 64 )
+        exp = 7;
+    else if( bands <= 128 )
+        exp = 8;
+    else
+        exp = 9;
+
+    resizeExponent( exp );
+    return m_fht->size() / 2;
+}
+
+void
+Analyzer::Base::paused() //virtual
+{}
+
+void
+Analyzer::Base::demo() //virtual
+{
+    static int t = 201; //FIXME make static to namespace perhaps
+//    qDebug() << Q_FUNC_INFO << t;
+
+    if( t > 300 ) t = 1; //0 = wasted calculations
+    if( t < 201 )
+    {
+        QVector<float> s( 512 );
+
+        const double dt = double( t ) / 200;
+        for( int i = 0; i < s.size(); ++i )
+            s[i] = dt * ( sin( M_PI + ( i * M_PI ) / s.size() ) + 1.0 );
+
+        analyze( s );
+    }
+    else analyze( QVector<float>( 1, 0 ) );
+
+    ++t;
+}
+
+
+
+Analyzer::Base2D::Base2D( QWidget *parent, uint scopeSize )
+    : Base( parent, scopeSize )
+{
+    connect( EngineController::instance(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackStateChanged() ) );
+
+    QTimer::singleShot( 0, this, SLOT( init() ) ); // needs to know the size
+    timer.setInterval( 34 );
+    timer.setSingleShot( false );
+    connect( &timer, SIGNAL( timeout() ), this, SLOT( demo() ) );
+    timer.start();
+}
+
+void Analyzer::Base2D::resizeEvent( QResizeEvent *e )
+{
+    QWidget::resizeEvent( e );
+
+    m_canvas = QPixmap( size() );
+    m_canvas.fill( Qt::transparent );
+}
+
+void Analyzer::Base2D::paintEvent( QPaintEvent* )
+{
+    if( m_canvas.isNull() )
+        return;
+
+    QPainter painter( this );
+    painter.drawPixmap( rect(), m_canvas );
+}
+
+void Analyzer::Base2D::playbackStateChanged()
+{
+    enableDemo( !EngineController::instance()->isPlaying() );
+}
+
+void
+Analyzer::interpolate( const QVector<float> &inVec, QVector<float> &outVec ) //static
+{
+    double pos = 0.0;
+    const double step = ( double )inVec.size() / outVec.size();
+
+    for( int i = 0; i < outVec.size(); ++i, pos += step )
+    {
+        const double error = pos - std::floor( pos );
+        const unsigned long offset = ( unsigned long )pos;
+
+        long indexLeft = offset + 0;
+
+        if( indexLeft >= inVec.size() )
+            indexLeft = inVec.size() - 1;
+
+        long indexRight = offset + 1;
+
+        if( indexRight >= inVec.size() )
+            indexRight = inVec.size() - 1;
+
+        outVec[i] = inVec[indexLeft ] * ( 1.0 - error ) +
+                    inVec[indexRight] * error;
+    }
+}
+
+void
+Analyzer::initSin( QVector<float> &v, const uint size ) //static
+{
+    double step = ( M_PI * 2 ) / size;
+    double radian = 0;
+
+    for( uint i = 0; i < size; i++ )
+    {
+        v.push_back( sin( radian ) );
+        radian += step;
+    }
+}
diff --git a/src/analyzer/AnalyzerBase.h b/src/analyzer/AnalyzerBase.h
new file mode 100644
index 0000000..aa54181
--- /dev/null
+++ b/src/analyzer/AnalyzerBase.h
@@ -0,0 +1,126 @@
+/****************************************************************************************
+ * Copyright (c) 2004 Max Howell <max.howell at methylblue.com>                            *
+ * Copyright (c) 2009 Martin Sandsmark <martin.sandsmark at kde.org>                       *
+ *                                                                                      *
+ * 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, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#ifndef ANALYZERBASE_H
+#define ANALYZERBASE_H
+
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#endif
+
+#include "fht.h"     //stack allocated and convenience
+#include <qpixmap.h> //stack allocated and convenience
+#include <qtimer.h>  //stack allocated
+#include <qwidget.h> //baseclass
+#include <vector>    //included for convenience
+
+#include <phonon/audiodataoutput.h>
+
+class QEvent;
+class QPaintEvent;
+class QResizeEvent;
+
+
+namespace Analyzer
+{
+
+typedef std::vector<float> Scope;
+
+class Base : public QWidget
+{
+    Q_OBJECT
+
+public slots:
+    void drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope );
+
+protected:
+    Base( QWidget*, uint = 7 );
+    ~Base()
+    {
+        delete m_fht;
+    }
+
+    int  resizeExponent( int );
+    int  resizeForBands( int );
+    virtual void transform( QVector<float>& );
+    virtual void analyze( const QVector<float>& ) = 0;
+    virtual void paused();
+public slots:
+    void demo();
+protected:
+    FHT    *m_fht;
+};
+
+
+class Base2D : public Base
+{
+    Q_OBJECT
+public:
+    const QPixmap *canvas()     const
+    {
+        return &m_canvas;
+    }
+
+private slots:
+    void enableDemo( bool enable )
+    {
+        enable ? timer.start() : timer.stop();
+    }
+    void playbackStateChanged();
+
+protected:
+    Base2D( QWidget*, uint scopeSize = 7 );
+
+    QPixmap *canvas()
+    {
+        return &m_canvas;
+    }
+
+    void paintEvent( QPaintEvent* );
+    void resizeEvent( QResizeEvent* );
+
+protected slots:
+    virtual void init() {}
+
+private:
+    QPixmap m_canvas;
+    QTimer timer;
+};
+
+class Factory
+{
+    //Currently this is a rather small class, its only purpose
+    //to ensure that making changes to analyzers will not require
+    //rebuilding the world!
+
+    //eventually it would be better to make analyzers pluggable
+    //but I can't be arsed, nor can I see much reason to do so
+    //yet!
+public:
+    static QWidget* createAnalyzer( QWidget* );
+    static QWidget* createPlaylistAnalyzer( QWidget * );
+};
+
+
+void interpolate( const QVector<float>&, QVector<float>& );
+void initSin( QVector<float>&, const uint = 6000 );
+
+} //END namespace Analyzer
+
+using Analyzer::Scope;
+
+#endif
diff --git a/src/analyzer/BlockAnalyzer.cpp b/src/analyzer/BlockAnalyzer.cpp
new file mode 100644
index 0000000..d171437
--- /dev/null
+++ b/src/analyzer/BlockAnalyzer.cpp
@@ -0,0 +1,286 @@
+/****************************************************************************************
+ * Copyright (c) 2003-2005 Max Howell <max.howell at methylblue.com>                       *
+ * Copyright (c) 2005-2013 Mark Kretschmann <kretschmann at kde.org>                       *
+ *                                                                                      *
+ * 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 Pulic License for more details.              *
+ *                                                                                      *
+ * You should have received a copy of the GNU General Public License along with         *
+ * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#include "BlockAnalyzer.h"
+
+#include "PaletteHandler.h"
+
+#include <cmath>
+
+#include <QPainter>
+#include <QResizeEvent>
+
+
+static inline uint myMax( uint v1, uint v2 )
+{
+    return v1 > v2 ? v1 : v2;
+}
+
+BlockAnalyzer::BlockAnalyzer( QWidget *parent )
+    : Analyzer::Base2D( parent, 9 )
+    , m_columns( 0 )         //uint
+    , m_rows( 0 )            //uint
+    , m_y( 0 )               //uint
+    , m_barPixmap( 1, 1 )    //null qpixmaps cause crashes
+    , m_topBarPixmap( WIDTH, HEIGHT )
+    , m_scope( MIN_COLUMNS ) //Scope
+    , m_store( 1 << 8, 0 )   //vector<uint>
+    , m_fade_bars( FADE_SIZE ) //vector<QPixmap>
+    , m_fade_pos( 1 << 8, 50 ) //vector<uint>
+    , m_fade_intensity( 1 << 8, 32 ) //vector<uint>
+{
+    setMinimumSize( MIN_COLUMNS * ( WIDTH + 1 ) - 1, MIN_ROWS * ( HEIGHT + 1 ) - 1 ); //-1 is padding, no drawing takes place there
+    setMaximumWidth( MAX_COLUMNS * ( WIDTH + 1 ) - 1 );
+    setMaximumHeight( MIN_ROWS * ( HEIGHT + 1 ) - 1 );
+}
+
+BlockAnalyzer::~BlockAnalyzer()
+{}
+
+void
+BlockAnalyzer::resizeEvent( QResizeEvent *e )
+{
+    Analyzer::Base2D::resizeEvent( e );
+
+    m_background = QPixmap( size() );
+
+    const uint oldRows = m_rows;
+
+    //all is explained in analyze()..
+    //+1 to counter -1 in maxSizes, trust me we need this!
+    m_columns = qMin<uint>( uint( double( width() + 1 ) / ( WIDTH + 1 ) ), 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 = QPixmap( WIDTH, m_rows * ( HEIGHT + 1 ) );
+
+        for( int i = 0; i < FADE_SIZE; ++i )
+            m_fade_bars[i] = QPixmap( WIDTH, m_rows * ( HEIGHT + 1 ) );
+
+        m_yscale.resize( m_rows + 1 );
+
+        const float 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() );
+    }
+
+    drawBackground();
+    analyze( m_scope );
+}
+
+void
+BlockAnalyzer::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
+BlockAnalyzer::transform( QVector<float> &s ) //pure virtual
+{
+    for( int x = 0; x < s.size(); ++x )
+        s[x] *= 2;
+
+    float *front = static_cast<float*>( &s.front() );
+
+    m_fht->spectrum( front );
+    m_fht->scale( front, 1.0 / 20 );
+
+    //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
+BlockAnalyzer::analyze( const QVector<float> &s )
+{
+    Analyzer::interpolate( s, m_scope );
+    update();
+}
+
+void
+BlockAnalyzer::paintEvent( QPaintEvent* )
+{
+    // 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
+
+    QPainter p( this );
+
+    // Paint the background
+    p.drawPixmap( 0, 0, m_background );
+
+    for( uint y, x = 0; x < m_scope.size(); ++x )
+    {
+        // determine y
+        for( y = 0; m_scope[x] < m_yscale[y]; ++y )
+            ;
+
+        // this is opposite to what you'd think, higher than y
+        // means the bar is lower than y (physically)
+        if( ( float )y > m_store[x] )
+            y = uint( m_store[x] += m_step );
+        else
+            m_store[x] = y;
+
+        // 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_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/ )
+        {
+            m_fade_pos[x] = y;
+            m_fade_intensity[x] = FADE_SIZE;
+        }
+
+        if( m_fade_intensity[x] > 0 )
+        {
+            const uint offset = --m_fade_intensity[x];
+            const uint y = m_y + ( m_fade_pos[x] * ( HEIGHT + 1 ) );
+            if( y < height() )
+                p.drawPixmap( x * ( WIDTH + 1 ), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y );
+        }
+
+        if( m_fade_intensity[x] == 0 )
+            m_fade_pos[x] = m_rows;
+
+        // REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are
+        p.drawPixmap( x * ( WIDTH + 1 ), y * ( HEIGHT + 1 ) + m_y, *bar(), 0, y * ( HEIGHT + 1 ), -1, -1 );
+    }
+
+    for( uint x = 0; x < m_store.size(); ++x )
+        p.drawPixmap( x * ( WIDTH + 1 ), int( m_store[x] ) * ( HEIGHT + 1 ) + m_y, m_topBarPixmap );
+}
+
+static inline void
+adjustToLimits( int &b, int &f, uint &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;
+        }
+    }
+}
+
+void
+BlockAnalyzer::paletteChange( const QPalette& ) //virtual
+{
+    QPainter p( bar() );
+
+    const QColor bg = The::paletteHandler()->backgroundColor();
+    const QColor fg = The::paletteHandler()->palette().color( QPalette::Highlight );
+
+    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 );
+
+    for( int y = 0; ( uint )y < m_rows; ++y )
+        //graduate the fg color
+        p.fillRect( 0, y * ( HEIGHT + 1 ), WIDTH, HEIGHT, QColor( r + int( dr * y ), g + int( dg * y ), b + int( db * y ) ) );
+
+    {
+        const QColor bg = palette().color( QPalette::Active, QPalette::Background ).dark( 112 );
+
+        //make a complimentary fadebar colour
+        //TODO dark is not always correct, dumbo!
+        int h, s, v; palette().color( QPalette::Active, QPalette::Background ).dark( 150 ).getHsv( &h, &s, &v );
+        const QColor fg = QColor::fromHsv( h + 60, s, v );
+
+        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_fade_bars[y].fill( palette().color( QPalette::Active, QPalette::Background ) );
+            const double Y = 1.0 - ( log10( ( FADE_SIZE ) - y ) / log10( ( FADE_SIZE ) ) );
+            QPainter f( &m_fade_bars[y] );
+            for( int z = 0; ( uint )z < m_rows; ++z )
+                f.fillRect( 0, z * ( HEIGHT + 1 ), WIDTH, HEIGHT, QColor( r + int( dr * Y ), g + int( dg * Y ), b + int( db * Y ) ) );
+        }
+    }
+
+    drawBackground();
+}
+
+void
+BlockAnalyzer::drawBackground()
+{
+    const QColor bg = palette().color( QPalette::Active, QPalette::Background );
+    const QColor bgdark = bg.dark( 112 );
+
+    m_background.fill( bg );
+
+    QPainter p( &m_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 );
+
+}
diff --git a/src/analyzer/BlockAnalyzer.h b/src/analyzer/BlockAnalyzer.h
new file mode 100644
index 0000000..ae0d01a
--- /dev/null
+++ b/src/analyzer/BlockAnalyzer.h
@@ -0,0 +1,73 @@
+/****************************************************************************************
+ * Copyright (c) 2003-2005 Max Howell <max.howell at methylblue.com>                       *
+ * Copyright (c) 2005-2013 Mark Kretschmann <kretschmann at kde.org>                       *
+ *                                                                                      *
+ * 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 Pulic License for more details.              *
+ *                                                                                      *
+ * You should have received a copy of the GNU General Public License along with         *
+ * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#ifndef BLOCKANALYZER_H
+#define BLOCKANALYZER_H
+
+#include "AnalyzerBase.h"
+
+class QMouseEvent;
+class QPalette;
+class QResizeEvent;
+
+class BlockAnalyzer : public Analyzer::Base2D
+{
+public:
+    BlockAnalyzer( QWidget* );
+    ~BlockAnalyzer();
+
+    // Signed ints because most of what we compare them against are ints
+    static const int HEIGHT      = 2;
+    static const int WIDTH       = 4;
+    static const int MIN_ROWS    = 30;  //arbitrary
+    static const int MIN_COLUMNS = 32;  //arbitrary
+    static const int MAX_COLUMNS = 256; //must be 2**n
+    static const int FADE_SIZE   = 90;
+
+protected:
+    virtual void transform( QVector<float>& );
+    virtual void analyze( const QVector<float>& );
+    virtual void paintEvent( QPaintEvent* );
+    virtual void resizeEvent( QResizeEvent* );
+    virtual void paletteChange( const QPalette& );
+
+    void drawBackground();
+    void determineStep();
+
+private:
+    QPixmap* bar()
+    {
+        return &m_barPixmap;
+    }
+
+    uint m_columns, m_rows;      //number of rows and columns of blocks
+    uint m_y;                    //y-offset from top of widget
+    QPixmap m_barPixmap;
+    QPixmap m_topBarPixmap;
+    QVector<float> m_scope;      //so we don't create a vector every frame
+    std::vector<float> m_store;  //current bar heights
+    std::vector<float> m_yscale;
+
+    std::vector<QPixmap> m_fade_bars;
+    std::vector<uint>    m_fade_pos;
+    std::vector<int>     m_fade_intensity;
+    QPixmap              m_background;
+
+    float m_step; //rows to fall per frame
+};
+
+#endif
diff --git a/src/analyzer/fht.cpp b/src/analyzer/fht.cpp
new file mode 100644
index 0000000..7500261
--- /dev/null
+++ b/src/analyzer/fht.cpp
@@ -0,0 +1,253 @@
+// FHT - Fast Hartley Transform Class
+//
+// Copyright (C) 2004  Melchior FRANZ - mfranz at kde.org
+//
+// 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// $Id: fht.cpp 871912 2008-10-16 02:27:10Z mitchell $
+
+
+#include "fht.h"
+
+#include <math.h>
+#include <string.h>
+
+FHT::FHT( int n ) :
+    m_buf( 0 ),
+    m_tab( 0 ),
+    m_log( 0 )
+{
+    if( n < 3 )
+    {
+        m_num = 0;
+        m_exp2 = -1;
+        return;
+    }
+    m_exp2 = n;
+    m_num = 1 << n;
+    if( n > 3 )
+    {
+        m_buf = new float[m_num];
+        m_tab = new float[m_num * 2];
+        makeCasTable();
+    }
+}
+
+
+FHT::~FHT()
+{
+    delete[] m_buf;
+    delete[] m_tab;
+    delete[] m_log;
+}
+
+
+void FHT::makeCasTable( void )
+{
+    float d, *costab, *sintab;
+    int ul, ndiv2 = m_num / 2;
+
+    for( costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++ )
+    {
+        d = M_PI * ul / ndiv2;
+        *costab = *sintab = cos( d );
+
+        costab += 2, sintab += 2;
+        if( sintab > m_tab + m_num * 2 )
+            sintab = m_tab + 1;
+    }
+}
+
+
+float* FHT::copy( float *d, float *s )
+{
+    return ( float * )memcpy( d, s, m_num * sizeof( float ) );
+}
+
+
+float* FHT::clear( float *d )
+{
+    return ( float * )memset( d, 0, m_num * sizeof( float ) );
+}
+
+
+void FHT::scale( float *p, float d )
+{
+    for( int i = 0; i < ( m_num / 2 ); i++ )
+        *p++ *= d;
+}
+
+
+void FHT::ewma( float *d, float *s, float w )
+{
+    for( int i = 0; i < ( m_num / 2 ); i++, d++, s++ )
+        *d = *d * w + *s * ( 1 - w );
+}
+
+
+void FHT::logSpectrum( float *out, float *p )
+{
+    int n = m_num / 2, i, j, k, *r;
+    if( !m_log )
+    {
+        m_log = new int[n];
+        float f = n / log10( ( double )n );
+        for( i = 0, r = m_log; i < n; i++, r++ )
+        {
+            j = int( rint( log10( i + 1.0 ) * f ) );
+            *r = j >= n ? n - 1 : j;
+        }
+    }
+    semiLogSpectrum( p );
+    *out++ = *p = *p / 100;
+    for( k = i = 1, r = m_log; i < n; ++i )
+    {
+        j = *r++;
+        if( i == j )
+            *out++ = p[i];
+        else
+        {
+            float base = p[k - 1];
+            float step = ( p[j] - base ) / ( j - ( k - 1 ) );
+            for( float corr = 0; k <= j; k++, corr += step )
+                * out++ = base + corr;
+        }
+    }
+}
+
+
+void FHT::semiLogSpectrum( float *p )
+{
+    float e;
+    power2( p );
+    for( int i = 0; i < ( m_num / 2 ); i++, p++ )
+    {
+        e = 10.0 * log10( sqrt( *p * .5 ) );
+        *p = e < 0 ? 0 : e;
+    }
+}
+
+
+void FHT::spectrum( float *p )
+{
+    power2( p );
+    for( int i = 0; i < ( m_num / 2 ); i++, p++ )
+        *p = ( float )sqrt( *p * .5 );
+}
+
+
+void FHT::power( float *p )
+{
+    power2( p );
+    for( int i = 0; i < ( m_num / 2 ); i++ )
+        *p++ *= .5;
+}
+
+
+void FHT::power2( float *p )
+{
+    int i;
+    float *q;
+    _transform( p, m_num, 0 );
+
+    *p = ( *p * *p ), *p += *p, p++;
+
+    for( i = 1, q = p + m_num - 2; i < ( m_num / 2 ); i++, --q )
+        *p = ( *p * *p ) + ( *q * *q ), p++;
+}
+
+
+void FHT::transform( float *p )
+{
+    if( m_num == 8 )
+        transform8( p );
+    else
+        _transform( p, m_num, 0 );
+}
+
+
+void FHT::transform8( float *p )
+{
+    float a, b, c, d, e, f, g, h, b_f2, d_h2;
+    float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh;
+
+    a = *p++, b = *p++, c = *p++, d = *p++;
+    e = *p++, f = *p++, g = *p++, h = *p;
+    b_f2 = ( b - f ) * M_SQRT2;
+    d_h2 = ( d - h ) * M_SQRT2;
+
+    a_c_eg = a - c - e + g;
+    a_ce_g = a - c + e - g;
+    ac_e_g = a + c - e - g;
+    aceg = a + c + e + g;
+
+    b_df_h = b - d + f - h;
+    bdfh = b + d + f + h;
+
+    *p = a_c_eg - d_h2;
+    *--p = a_ce_g - b_df_h;
+    *--p = ac_e_g - b_f2;
+    *--p = aceg - bdfh;
+    *--p = a_c_eg + d_h2;
+    *--p = a_ce_g + b_df_h;
+    *--p = ac_e_g + b_f2;
+    *--p = aceg + bdfh;
+}
+
+
+void FHT::_transform( float *p, int n, int k )
+{
+    if( n == 8 )
+    {
+        transform8( p + k );
+        return;
+    }
+
+    int i, j, ndiv2 = n / 2;
+    float a, *t1, *t2, *t3, *t4, *ptab, *pp;
+
+    for( i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++ )
+        *t1++ = *pp++, *t2++ = *pp++;
+
+    memcpy( p + k, m_buf, sizeof( float ) * n );
+
+    _transform( p, ndiv2, k );
+    _transform( p, ndiv2, k + ndiv2 );
+
+    j = m_num / ndiv2 - 1;
+    t1 = m_buf;
+    t2 = t1 + ndiv2;
+    t3 = p + k + ndiv2;
+    ptab = m_tab;
+    pp = p + k;
+
+    a = *ptab++ * *t3++;
+    a += *ptab * *pp;
+    ptab += j;
+
+    *t1++ = *pp + a;
+    *t2++ = *pp++ - a;
+
+    for( i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j )
+    {
+        a = *ptab++ * *t3++;
+        a += *ptab * *--t4;
+
+        *t1++ = *pp + a;
+        *t2++ = *pp++ - a;
+    }
+    memcpy( p + k, m_buf, sizeof( float ) * n );
+}
+
diff --git a/src/analyzer/fht.h b/src/analyzer/fht.h
new file mode 100644
index 0000000..9516f7a
--- /dev/null
+++ b/src/analyzer/fht.h
@@ -0,0 +1,125 @@
+// FHT - Fast Hartley Transform Class
+//
+// Copyright (C) 2004  Melchior FRANZ - mfranz at kde.org
+//
+// 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// $Id: fht.h 668973 2007-05-28 08:55:15Z mkossick $
+
+#ifndef FHT_H
+#define FHT_H
+
+/**
+ * Implementation of the Hartley Transform after Bracewell's discrete
+ * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987)
+ * but was put into public domain by the Board of Trustees of Stanford
+ * University in 1994 and is now freely available[1].
+ *
+ * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379
+ */
+class FHT
+{
+    int	m_exp2;
+    int	m_num;
+    float	*m_buf;
+    float	*m_tab;
+    int	*m_log;
+
+    /**
+     * Create a table of "cas" (cosine and sine) values.
+     * Has only to be done in the constructor and saves from
+     * calculating the same values over and over while transforming.
+     */
+    void	makeCasTable();
+
+    /**
+     * Recursive in-place Hartley transform. For internal use only!
+     */
+    void	_transform( float *, int, int );
+
+public:
+    /**
+    * Prepare transform for data sets with @f$2^n at f$ numbers, whereby @f$n at f$
+    * should be at least 3. Values of more than 3 need a trigonometry table.
+    * @see makeCasTable()
+    */
+    FHT( int );
+
+    ~FHT();
+    inline int sizeExp() const
+    {
+        return m_exp2;
+    }
+    inline int size() const
+    {
+        return m_num;
+    }
+    float	*copy( float *, float * );
+    float	*clear( float * );
+    void	scale( float *, float );
+
+    /**
+     * Exponentially Weighted Moving Average (EWMA) filter.
+     * @param d is the filtered data.
+     * @param s is fresh input.
+     * @param w is the weighting factor.
+     */
+    void	ewma( float *d, float *s, float w );
+
+    /**
+     * Logarithmic audio spectrum. Maps semi-logarithmic spectrum
+     * to logarithmic frequency scale, interpolates missing values.
+     * A logarithmic index map is calculated at the first run only.
+     * @param p is the input array.
+     * @param out is the spectrum.
+     */
+    void	logSpectrum( float *out, float *p );
+
+    /**
+     * Semi-logarithmic audio spectrum.
+     */
+    void	semiLogSpectrum( float * );
+
+    /**
+     * Fourier spectrum.
+     */
+    void	spectrum( float * );
+
+    /**
+     * Calculates a mathematically correct FFT power spectrum.
+     * If further scaling is applied later, use power2 instead
+     * and factor the 0.5 in the final scaling factor.
+     * @see FHT::power2()
+     */
+    void	power( float * );
+
+    /**
+     * Calculates an FFT power spectrum with doubled values as a
+     * result. The values need to be multiplied by 0.5 to be exact.
+     * Note that you only get @f$2^{n-1}@f$ power values for a data set
+     * of @f$2^n at f$ input values. This is the fastest transform.
+     * @see FHT::power()
+     */
+    void	power2( float * );
+
+    /**
+     * Discrete Hartley transform of data sets with 8 values.
+     */
+    void	transform8( float * );
+
+    void	transform( float * );
+};
+
+#endif
diff --git a/src/context/applets/CMakeLists.txt b/src/context/applets/CMakeLists.txt
index 9c6746a..3387a92 100644
--- a/src/context/applets/CMakeLists.txt
+++ b/src/context/applets/CMakeLists.txt
@@ -1,15 +1,15 @@
 add_subdirectory( albums )
+add_subdirectory( analyzer )
 add_subdirectory( currenttrack )
-add_subdirectory( lyrics )
 add_subdirectory( info )
-add_subdirectory( wikipedia )
-add_subdirectory( photos )
 add_subdirectory( labels )
+add_subdirectory( lyrics )
+add_subdirectory( photos )
 add_subdirectory( tabs )
+add_subdirectory( wikipedia )
 
 if( LIBLASTFM_FOUND )
     add_subdirectory( upcomingevents )
     add_subdirectory( similarartists )
 endif()
 
-#add_subdirectory( songkick )
diff --git a/src/context/applets/analyzer/AnalyzerApplet.cpp b/src/context/applets/analyzer/AnalyzerApplet.cpp
new file mode 100644
index 0000000..f1df173
--- /dev/null
+++ b/src/context/applets/analyzer/AnalyzerApplet.cpp
@@ -0,0 +1,56 @@
+/****************************************************************************************
+ * Copyright (c) 2013 Mark Kretschmann <kretschmann at kde.org>                            *
+ *                                                                                      *
+ * 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, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#define DEBUG_PREFIX "AnalyzerApplet"
+
+#include "AnalyzerApplet.h"
+
+#include "App.h"
+#include "analyzer/BlockAnalyzer.h"
+#include "core/support/Amarok.h"
+#include "core/support/Debug.h"
+
+#include <QGraphicsLinearLayout>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsScene>
+
+
+AnalyzerApplet::AnalyzerApplet( QObject* parent, const QVariantList& args )
+    : Context::Applet( parent, args )
+{
+    setHasConfigurationInterface( false );
+}
+
+AnalyzerApplet::~AnalyzerApplet()
+{}
+
+void
+AnalyzerApplet::init()
+{
+    // Call the base implementation.
+    Context::Applet::init();
+
+    BlockAnalyzer *analyzer = new BlockAnalyzer( 0 );
+
+    QGraphicsProxyWidget *proxy = scene()->addWidget( analyzer );
+
+    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical, this );
+    layout->addItem( proxy );
+
+    updateConstraints();
+}
+
+#include "AnalyzerApplet.moc"
diff --git a/src/context/applets/analyzer/AnalyzerApplet.h b/src/context/applets/analyzer/AnalyzerApplet.h
new file mode 100644
index 0000000..b47246f
--- /dev/null
+++ b/src/context/applets/analyzer/AnalyzerApplet.h
@@ -0,0 +1,38 @@
+/****************************************************************************************
+ * Copyright (c) 2013 Mark Kretschmann <kretschmann at kde.org>                            *
+ *                                                                                      *
+ * 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, see <http://www.gnu.org/licenses/>.                           *
+ ****************************************************************************************/
+
+#ifndef ANALYZER_APPLET_H
+#define ANALYZER_APPLET_H
+
+#include "context/Applet.h"
+#include "context/DataEngine.h"
+
+
+class AnalyzerApplet : public Context::Applet
+{
+    Q_OBJECT
+
+public:
+    AnalyzerApplet( QObject* parent, const QVariantList& args );
+    virtual ~AnalyzerApplet();
+
+public slots:
+    virtual void init();
+};
+
+AMAROK_EXPORT_APPLET( analyzer, AnalyzerApplet )
+
+#endif
diff --git a/src/context/applets/analyzer/CMakeLists.txt b/src/context/applets/analyzer/CMakeLists.txt
new file mode 100644
index 0000000..a327571
--- /dev/null
+++ b/src/context/applets/analyzer/CMakeLists.txt
@@ -0,0 +1,24 @@
+project(context-analyzer)
+
+set(analyzer_SRCS
+AnalyzerApplet.cpp )
+
+include_directories(
+                    ../..
+                    ../../..
+                    )
+
+kde4_add_plugin(amarok_context_applet_analyzer ${analyzer_SRCS})
+if(APPLE)
+   SET_TARGET_PROPERTIES(amarok_context_applet_analyzer PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+endif(APPLE)
+
+target_link_libraries(amarok_context_applet_analyzer
+    amarokcore
+    amaroklib
+    ${KDE4_PLASMA_LIBS}
+    )
+
+install(TARGETS amarok_context_applet_analyzer DESTINATION ${PLUGIN_INSTALL_DIR})
+install(FILES amarok-context-applet-analyzer.desktop DESTINATION ${SERVICES_INSTALL_DIR})
+
diff --git a/src/context/applets/analyzer/amarok-context-applet-analyzer.desktop b/src/context/applets/analyzer/amarok-context-applet-analyzer.desktop
new file mode 100644
index 0000000..f17af87
--- /dev/null
+++ b/src/context/applets/analyzer/amarok-context-applet-analyzer.desktop
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Name=Analyzer
+Type=Service
+Icon=analyzer-amarok
+ServiceTypes=Plasma/Applet
+
+X-KDE-Library=amarok_context_applet_analyzer
+X-KDE-PluginInfo-Author=Mark Kretschmann
+X-KDE-PluginInfo-Email=kretschmann at kde.org
+X-KDE-PluginInfo-Name=analyzer
+X-KDE-PluginInfo-Version=1.0
+X-KDE-PluginInfo-Website=
+X-KDE-PluginInfo-Depends=
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-EnabledByDefault=true
+X-KDE-ParentApp=amarok
+X-KDE-PluginInfo-Category=Current
+
diff --git a/src/context/engines/CMakeLists.txt b/src/context/engines/CMakeLists.txt
index f82ee64..ec191a8 100644
--- a/src/context/engines/CMakeLists.txt
+++ b/src/context/engines/CMakeLists.txt
@@ -1,12 +1,10 @@
-add_subdirectory( wikipedia )
-add_subdirectory( lyrics )
 add_subdirectory( current )
 add_subdirectory( info )
-add_subdirectory( photos )
 add_subdirectory( labels )
+add_subdirectory( lyrics )
+add_subdirectory( photos )
 add_subdirectory( tabs )
-
-#add_subdirectory( songkick )
+add_subdirectory( wikipedia )
 
 if(LIBLASTFM_FOUND)
     add_subdirectory( similarartists )


More information about the Amarok-devel mailing list