[amarok] /: Add OpenGL analyzers, and make analyzers configurable (right-click).

Mark Kretschmann kretschmann at kde.org
Thu May 30 14:54:15 UTC 2013


Git commit 7c139193a4fcb74a0a3cfdddbe85a3c1168f0de0 by Mark Kretschmann.
Committed on 26/05/2013 at 10:49.
Pushed by markey into branch 'master'.

Add OpenGL analyzers, and make analyzers configurable (right-click).

I've ported two OpenGL analyzers from Amarok 1.x. One of them shows
a whirly background, with disco lights in front (named "Disco"). The
second one shows something like bouncing balls between two walls
(named "Balls").

One of the main advantages of using OpenGL instead of QPainter is the
significantly lower CPU load, if you have anything resembling a GPU in
your computer.

I've also made the height of the widget configurable in 3 different
sizes.

NOTE: The Balls Analyzer looks somewhat broken. I'll fix that.

CCMAIL: amarok-devel at kde.org

M  +5    -0    images/CMakeLists.txt
A  +-    --    images/ball.png
A  +-    --    images/dot.png
A  +-    --    images/grid.png
A  +-    --    images/wirl1.png
A  +-    --    images/wirl2.png
M  +133  -9    src/context/applets/analyzer/AnalyzerApplet.cpp
M  +18   -0    src/context/applets/analyzer/AnalyzerApplet.h
M  +52   -19   src/context/applets/analyzer/AnalyzerBase.cpp
M  +45   -15   src/context/applets/analyzer/AnalyzerBase.h
A  +482  -0    src/context/applets/analyzer/BallsAnalyzer.cpp     [License: GPL (v2+)]
C  +52   -14   src/context/applets/analyzer/BallsAnalyzer.h [from: src/context/applets/analyzer/AnalyzerApplet.h - 051% similarity]
M  +1    -3    src/context/applets/analyzer/BlockAnalyzer.cpp
M  +2    -0    src/context/applets/analyzer/CMakeLists.txt
A  +336  -0    src/context/applets/analyzer/DiscoAnalyzer.cpp     [License: GPL (v2+)]
C  +47   -14   src/context/applets/analyzer/DiscoAnalyzer.h [from: src/context/applets/analyzer/AnalyzerApplet.h - 054% similarity]     [License: GPL]

http://commits.kde.org/amarok/7c139193a4fcb74a0a3cfdddbe85a3c1168f0de0

diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt
index f0db448..c4509d8 100644
--- a/images/CMakeLists.txt
+++ b/images/CMakeLists.txt
@@ -4,7 +4,9 @@ add_subdirectory( icons )
 
 install(FILES
         amarok_icon.svg
+        ball.png
         default-theme-clean.svg
+        dot.png
         emblem-amazon.png
         emblem-default.png
         emblem-jamendo.png
@@ -19,6 +21,7 @@ install(FILES
         emblem-ampache-scalable.svgz
         emblem-scripted.png
         emblem-scripted-scalable.svgz
+        grid.png
         lastfm-default-cover.png
         echonest.png
         lastfm.png
@@ -38,6 +41,8 @@ install(FILES
         star.png
         volume_icon.png
         volume_muted_icon.png
+        wirl1.png
+        wirl2.png
         service_info_loading1.png
         service_info_loading2.png
         service_info_loading3.png
diff --git a/images/ball.png b/images/ball.png
new file mode 100644
index 0000000..2e406b2
Binary files /dev/null and b/images/ball.png differ
diff --git a/images/dot.png b/images/dot.png
new file mode 100644
index 0000000..11e3b58
Binary files /dev/null and b/images/dot.png differ
diff --git a/images/grid.png b/images/grid.png
new file mode 100644
index 0000000..3dbbc25
Binary files /dev/null and b/images/grid.png differ
diff --git a/images/wirl1.png b/images/wirl1.png
new file mode 100644
index 0000000..e6af564
Binary files /dev/null and b/images/wirl1.png differ
diff --git a/images/wirl2.png b/images/wirl2.png
new file mode 100644
index 0000000..1307c5e
Binary files /dev/null and b/images/wirl2.png differ
diff --git a/src/context/applets/analyzer/AnalyzerApplet.cpp b/src/context/applets/analyzer/AnalyzerApplet.cpp
index 5c0c189..91b4cb6 100644
--- a/src/context/applets/analyzer/AnalyzerApplet.cpp
+++ b/src/context/applets/analyzer/AnalyzerApplet.cpp
@@ -18,21 +18,33 @@
 
 #include "AnalyzerApplet.h"
 
+#include "core/support/Amarok.h"
+#include "core/support/Debug.h"
+
+#include "BallsAnalyzer.h"
 #include "BlockAnalyzer.h"
+#include "DiscoAnalyzer.h"
 
-#include <QGraphicsLinearLayout>
-#include <QGraphicsProxyWidget>
-#include <QGraphicsScene>
+#include <QAction>
+#include <QGraphicsView>
+#include <QMenu>
 
 
 AnalyzerApplet::AnalyzerApplet( QObject* parent, const QVariantList& args )
     : Context::Applet( parent, args )
+    , m_analyzer( 0 )
 {
     setHasConfigurationInterface( false );
+
+    connect( this, SIGNAL( geometryChanged() ), this, SLOT( newGeometry() ) );
 }
 
 AnalyzerApplet::~AnalyzerApplet()
-{}
+{
+    KConfigGroup config = Amarok::config( "Analyzer Applet" );
+    config.writeEntry( "Height", (int)m_currentHeight );
+    config.writeEntry( "Current Analyzer", m_analyzerName );
+}
 
 void
 AnalyzerApplet::init()
@@ -40,14 +52,126 @@ AnalyzerApplet::init()
     // Call the base implementation.
     Context::Applet::init();
 
-    BlockAnalyzer *analyzer = new BlockAnalyzer( 0 );
+    m_analyzerNames["Balls"] = i18n( "Balls (OpenGL)" );
+    m_analyzerNames["Blocky"] = i18n( "Blocky" );
+    m_analyzerNames["Disco"] = i18n( "Disco (OpenGL)" );
+
+    KConfigGroup config = Amarok::config( "Analyzer Applet" );
+
+    if( config.readEntry( "Height", (int)Small ) == Small )
+        setHeightSmall();
+    if( config.readEntry( "Height", (int)Small ) == Medium )
+        setHeightMedium();
+    if( config.readEntry( "Height", (int)Small ) == Tall )
+        setHeightTall();
+
+    setCurrentAnalyzer( config.readEntry( "Current Analyzer", "Blocky" ) );
+}
+
+void AnalyzerApplet::newGeometry()
+{
+    m_analyzer->setGeometry( geometry().toRect() );
+}
+
+QList<QAction *>
+AnalyzerApplet::contextualActions ()
+{
+    QList<QAction*> actions;
+    QAction *action;
+
+    QMenu *heightMenu = new QMenu( i18n( "Height" ), view() );
+    actions << heightMenu->menuAction();
+
+    QActionGroup *heightActions = new QActionGroup( this );
+
+    action = heightMenu->addAction( i18n( "Small" ) );
+    action->setCheckable( true );
+    action->setChecked( m_currentHeight == Small );
+    action->setActionGroup( heightActions );
+    connect( action, SIGNAL( triggered() ), this, SLOT( setHeightSmall() ) );
+    action = heightMenu->addAction( i18n( "Medium" ) );
+    action->setCheckable( true );
+    action->setChecked( m_currentHeight == Medium );
+    action->setActionGroup( heightActions );
+    connect( action, SIGNAL( triggered() ), this, SLOT( setHeightMedium() ) );
+    action = heightMenu->addAction( i18n( "Tall" ) );
+    action->setCheckable( true );
+    action->setChecked( m_currentHeight == Tall );
+    action->setActionGroup( heightActions );
+    connect( action, SIGNAL( triggered() ), this, SLOT( setHeightTall() ) );
+
+    action = new QAction( this );
+    action->setSeparator( true );
+    actions << action;
+
+    QActionGroup *analyzerActions = new QActionGroup( this );
+    connect( analyzerActions, SIGNAL( triggered( QAction* ) ), this, SLOT( analyzerAction( QAction* ) ) );
+
+    QMap<QString, QString>::const_iterator i = m_analyzerNames.constBegin();
+    while ( i != m_analyzerNames.constEnd() ) {
+        action = new QAction( i.value(), this );
+        action->setData( i.key() );
+        action->setCheckable( true );
+        action->setChecked( m_analyzerName == i.key() );
+        action->setActionGroup( analyzerActions );
+        actions << action;
+        i++;
+    }
+
+    return actions;
+}
+
+void
+AnalyzerApplet::setHeightSmall()
+{
+    setMinimumHeight( 100 );
+    m_currentHeight = Small;
+}
+
+void
+AnalyzerApplet::setHeightMedium()
+{
+    setMinimumHeight( 150 );
+    m_currentHeight = Medium;
+}
+
+void
+AnalyzerApplet::setHeightTall()
+{
+    setMinimumHeight( 200 );
+    m_currentHeight = Tall;
+}
+
+void
+AnalyzerApplet::analyzerAction( QAction *action )
+{
+    DEBUG_BLOCK
+
+    setCurrentAnalyzer( action->data().toString() );
+}
+
+void
+AnalyzerApplet::setCurrentAnalyzer( const QString &name )
+{
+    DEBUG_BLOCK
+
+    debug() << "name: " << name;
+
+    if( m_analyzerName == name )
+        return;
 
-    QGraphicsProxyWidget *proxy = scene()->addWidget( analyzer );
+    delete m_analyzer;
+    m_analyzerName = name;
 
-    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical, this );
-    layout->addItem( proxy );
+    if( name == "Balls" )
+        m_analyzer = new BallsAnalyzer( view()->viewport() );
+    else if( name == "Blocky" )
+        m_analyzer = new BlockAnalyzer( view()->viewport() );
+    else if( name == "Disco" )
+        m_analyzer = new DiscoAnalyzer( view()->viewport() );
 
-    updateConstraints();
+    newGeometry();
+    m_analyzer->show();
 }
 
 #include "AnalyzerApplet.moc"
diff --git a/src/context/applets/analyzer/AnalyzerApplet.h b/src/context/applets/analyzer/AnalyzerApplet.h
index b47246f..45912f9 100644
--- a/src/context/applets/analyzer/AnalyzerApplet.h
+++ b/src/context/applets/analyzer/AnalyzerApplet.h
@@ -26,11 +26,29 @@ class AnalyzerApplet : public Context::Applet
     Q_OBJECT
 
 public:
+    enum WidgetHeight { Small = 0, Medium = 1, Tall = 2 };
+
     AnalyzerApplet( QObject* parent, const QVariantList& args );
     virtual ~AnalyzerApplet();
 
 public slots:
     virtual void init();
+
+private slots:
+    void newGeometry();
+    void setHeightSmall();
+    void setHeightMedium();
+    void setHeightTall();
+    void analyzerAction( QAction* );
+
+private:
+    void setCurrentAnalyzer( const QString &name);
+    QList<QAction *> contextualActions();
+
+    QWidget *m_analyzer;
+    QString m_analyzerName;
+    QMap<QString, QString> m_analyzerNames;
+    WidgetHeight m_currentHeight;
 };
 
 AMAROK_EXPORT_APPLET( analyzer, AnalyzerApplet )
diff --git a/src/context/applets/analyzer/AnalyzerBase.cpp b/src/context/applets/analyzer/AnalyzerBase.cpp
index 420623b..7ee4992 100644
--- a/src/context/applets/analyzer/AnalyzerBase.cpp
+++ b/src/context/applets/analyzer/AnalyzerBase.cpp
@@ -23,7 +23,6 @@
 
 #include <QEvent> // event()
 #include <QPainter>
-#include <Phonon/AudioOutput>
 
 
 // INSTRUCTIONS Base2D
@@ -36,16 +35,15 @@
 //can't mod scope in analyze you have to use transform
 
 
-Analyzer::Base::Base( QWidget *parent, uint scopeSize )
-    : QWidget( parent )
+template<class W>
+Analyzer::Base<W>::Base( QWidget *parent, uint scopeSize )
+    : W( 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
+template<class W> void
+Analyzer::Base<W>::transform( QVector<float> &scope ) //virtual
 {
     //this is a standard transformation that should give
     //an FFT scope that has bands for pretty analyzers
@@ -64,8 +62,8 @@ Analyzer::Base::transform( QVector<float> &scope ) //virtual
     delete [] f;
 }
 
-void
-Analyzer::Base::drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope )
+template<class W> void
+Analyzer::Base<W>::drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope )
 {
     if( thescope.isEmpty() )
         return;
@@ -94,8 +92,8 @@ Analyzer::Base::drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<
     scope.resize( m_fht->size() );
 }
 
-int
-Analyzer::Base::resizeExponent( int exp )
+template<class W> int
+Analyzer::Base<W>::resizeExponent( int exp )
 {
     if( exp < 3 )
         exp = 3;
@@ -110,8 +108,8 @@ Analyzer::Base::resizeExponent( int exp )
     return exp;
 }
 
-int
-Analyzer::Base::resizeForBands( int bands )
+template<class W> int
+Analyzer::Base<W>::resizeForBands( int bands )
 {
     int exp;
     if( bands <= 8 )
@@ -131,12 +129,12 @@ Analyzer::Base::resizeForBands( int bands )
     return m_fht->size() / 2;
 }
 
-void
-Analyzer::Base::paused() //virtual
+template<class W> void
+Analyzer::Base<W>::paused() //virtual
 {}
 
-void
-Analyzer::Base::demo() //virtual
+template<class W> void
+Analyzer::Base<W>::demo() //virtual
 {
     static int t = 201; //FIXME make static to namespace perhaps
 //    qDebug() << Q_FUNC_INFO << t;
@@ -160,15 +158,22 @@ Analyzer::Base::demo() //virtual
 
 
 Analyzer::Base2D::Base2D( QWidget *parent, uint scopeSize )
-    : Base( parent, scopeSize )
+    : Base<QWidget>( parent, scopeSize )
 {
     connect( EngineController::instance(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackStateChanged() ) );
 
+    connect( EngineController::instance(), SIGNAL( audioDataReady( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ),
+             this,   SLOT( draw( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ) );
+
     m_timer.setInterval( 34 );
     connect( &m_timer, SIGNAL( timeout() ), this, SLOT( demo() ) );
 
     enableDemo( !EngineController::instance()->isPlaying() );
-    QTimer::singleShot( 0, this, SLOT( init() ) ); // needs to know the size
+
+    QTimer *timer = new QTimer( this );
+    timer->setInterval( 20 );
+    connect( timer, SIGNAL( timeout() ), this, SLOT( update() ) );
+    timer->start();
 }
 
 void Analyzer::Base2D::resizeEvent( QResizeEvent *e )
@@ -193,6 +198,34 @@ void Analyzer::Base2D::playbackStateChanged()
     enableDemo( !EngineController::instance()->isPlaying() );
 }
 
+
+
+Analyzer::Base3D::Base3D( QWidget *parent, uint scopeSize )
+    : Base<QGLWidget>( parent, scopeSize )
+{
+    connect( EngineController::instance(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackStateChanged() ) );
+
+    connect( EngineController::instance(), SIGNAL( audioDataReady( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ),
+             this,   SLOT( draw( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > & ) ) );
+
+    m_timer.setInterval( 34 );
+    connect( &m_timer, SIGNAL( timeout() ), this, SLOT( demo() ) );
+
+    enableDemo( !EngineController::instance()->isPlaying() );
+
+    QTimer *timer = new QTimer( this );
+    timer->setInterval( 20 );
+    connect( timer, SIGNAL( timeout() ), this, SLOT( updateGL() ) );
+    timer->start();
+}
+
+void Analyzer::Base3D::playbackStateChanged()
+{
+    enableDemo( !EngineController::instance()->isPlaying() );
+}
+
+
+
 void
 Analyzer::interpolate( const QVector<float> &inVec, QVector<float> &outVec ) //static
 {
diff --git a/src/context/applets/analyzer/AnalyzerBase.h b/src/context/applets/analyzer/AnalyzerBase.h
index 75dc422..568cd2f 100644
--- a/src/context/applets/analyzer/AnalyzerBase.h
+++ b/src/context/applets/analyzer/AnalyzerBase.h
@@ -23,13 +23,17 @@
 #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 <QGLWidget>
+#include <QPixmap> //stack allocated and convenience
+#include <QTimer>  //stack allocated
+#include <QWidget> //baseclass
+
 #include <vector>    //included for convenience
 
 #include <phonon/audiodataoutput.h>
 
+
 class QEvent;
 class QPaintEvent;
 class QResizeEvent;
@@ -40,12 +44,10 @@ namespace Analyzer
 
 typedef std::vector<float> Scope;
 
-class Base : public QWidget
+template<class W> class Base : public W
 {
-    Q_OBJECT
-
-public slots:
-    void drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope );
+public:
+    virtual void drawFrame( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope );
 
 protected:
     Base( QWidget*, uint = 7 );
@@ -54,29 +56,30 @@ protected:
         delete m_fht;
     }
 
+    void demo();
+
     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
+class Base2D : public Base<QWidget>
 {
     Q_OBJECT
+
 public:
     const QPixmap *canvas()     const
     {
         return &m_canvas;
     }
 
+
 private slots:
     void enableDemo( bool enable )
     {
@@ -84,6 +87,11 @@ private slots:
     }
     void playbackStateChanged();
 
+    void draw( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope )
+    {
+        drawFrame( thescope );
+    }
+
 protected:
     Base2D( QWidget*, uint scopeSize = 7 );
 
@@ -95,14 +103,36 @@ protected:
     void paintEvent( QPaintEvent* );
     void resizeEvent( QResizeEvent* );
 
-protected slots:
-    virtual void init() {}
-
 private:
     QPixmap m_canvas;
     QTimer m_timer;
 };
 
+
+class Base3D : public Base<QGLWidget>
+{
+    Q_OBJECT
+
+private slots:
+    void enableDemo( bool enable )
+    {
+        enable ? m_timer.start() : m_timer.stop();
+    }
+    void playbackStateChanged();
+
+    void draw( const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope )
+    {
+        drawFrame( thescope );
+    }
+
+protected:
+    Base3D( QWidget*, uint scopeSize = 7 );
+
+private:
+    QTimer m_timer;
+};
+
+
 class Factory
 {
     //Currently this is a rather small class, its only purpose
diff --git a/src/context/applets/analyzer/BallsAnalyzer.cpp b/src/context/applets/analyzer/BallsAnalyzer.cpp
new file mode 100644
index 0000000..0896970
--- /dev/null
+++ b/src/context/applets/analyzer/BallsAnalyzer.cpp
@@ -0,0 +1,482 @@
+/****************************************************************************************
+ * Copyright (c) 2004 Enrico Ros <eros.kde at email.it>                                    *
+ *                                                                                      *
+ * 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 "BallsAnalyzer.h"
+
+#include <KStandardDirs>
+
+#include <QImage>
+
+#include <cmath>
+#include <cstdlib>
+#include <sys/time.h>
+
+
+#ifndef HAVE_FABSF
+inline float fabsf( float f )
+{
+    return f < 0.f ? -f : f;
+}
+#endif
+
+
+class Ball
+{
+public:
+    Ball() : x( drand48() - drand48() ), y( 1 - 2.0 * drand48() ),
+        z( drand48() ), vx( 0.0 ), vy( 0.0 ), vz( 0.0 ),
+        mass( 0.01 + drand48() / 10.0 )
+        //,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } )
+    {
+        //this is because GCC < 3.3 can't compile the above line, we aren't sure why though
+        color[0] = 0.0; color[1] = drand48() * 0.5; color[2] = 0.7 + drand48() * 0.3;
+    };
+
+    float x, y, z, vx, vy, vz, mass;
+    float color[3];
+
+    void updatePhysics( float dT )
+    {
+        x += vx * dT;                // position
+        y += vy * dT;                // position
+        z += vz * dT;                // position
+        if( y < -0.8 ) vy = fabsf( vy );
+        if( y > 0.8 ) vy = -fabsf( vy );
+        if( z < 0.1 ) vz = fabsf( vz );
+        if( z > 0.9 ) vz = -fabsf( vz );
+        vx += ( ( x > 0 ) ? 4.94 : -4.94 ) * dT;  // G-force
+        vx *= ( 1 - 2.9 * dT );          // air friction
+        vy *= ( 1 - 2.9 * dT );          // air friction
+        vz *= ( 1 - 2.9 * dT );          // air friction
+    }
+};
+
+class Paddle
+{
+public:
+    Paddle( float xPos ) : onLeft( xPos < 0 ), mass( 1.0 ),
+        X( xPos ), x( xPos ), vx( 0.0 ) {};
+
+    void updatePhysics( float dT )
+    {
+        x += vx * dT;                // posision
+        vx += ( 1300 * ( X - x ) / mass ) * dT;    // elasticity
+        vx *= ( 1 - 4.0 * dT );          // air friction
+    }
+
+    void renderGL()
+    {
+        glBegin( GL_TRIANGLE_STRIP );
+        glColor3f( 0.0f, 0.1f, 0.3f );
+        glVertex3f( x, -1.0f, 0.0 );
+        glVertex3f( x, 1.0f, 0.0 );
+        glColor3f( 0.1f, 0.2f, 0.6f );
+        glVertex3f( x, -1.0f, 1.0 );
+        glVertex3f( x, 1.0f, 1.0 );
+        glEnd();
+    }
+
+    void bounce( Ball * ball )
+    {
+        if( onLeft && ball->x < x )
+        {
+            ball->vx = vx * mass / ( mass + ball->mass ) + fabsf( ball->vx );
+            ball->vy = ( drand48() - drand48() ) * 1.8;
+            ball->vz = ( drand48() - drand48() ) * 0.9;
+            ball->x = x;
+        }
+        else if( !onLeft && ball->x > x )
+        {
+            ball->vx = vx * mass / ( mass + ball->mass ) - fabsf( ball->vx );
+            ball->vy = ( drand48() - drand48() ) * 1.8;
+            ball->vz = ( drand48() - drand48() ) * 0.9;
+            ball->x = x;
+        }
+    }
+
+    void impulse( float strength )
+    {
+        if( ( onLeft && strength > vx ) || ( !onLeft && strength < vx ) )
+            vx += strength;
+    }
+
+private:
+    bool onLeft;
+    float mass, X, x, vx;
+};
+
+
+BallsAnalyzer::BallsAnalyzer( QWidget *parent ):
+    Analyzer::Base3D( parent )
+{
+    setObjectName( "Balls" );
+
+    //initialize openGL context before managing GL calls
+    makeCurrent();
+    loadTexture( KStandardDirs::locate( "data", "amarok/images/ball.png" ), ballTexture );
+    loadTexture( KStandardDirs::locate( "data", "amarok/images/grid.png" ), gridTexture );
+
+    leftPaddle = new Paddle( -1.0 );
+    rightPaddle = new Paddle( 1.0 );
+    for( int i = 0; i < NUMBER_OF_BALLS; i++ )
+        balls.append( new Ball() );
+
+    show.colorK = 0.0;
+    show.gridScrollK = 0.0;
+    show.gridEnergyK = 0.0;
+    show.camRot = 0.0;
+    show.camRoll = 0.0;
+    show.peakEnergy = 1.0;
+    frame.silence = true;
+    frame.energy = 0.0;
+    frame.dEnergy = 0.0;
+}
+
+BallsAnalyzer::~BallsAnalyzer()
+{
+    freeTexture( ballTexture );
+    freeTexture( gridTexture );
+    delete leftPaddle;
+    delete rightPaddle;
+
+    foreach( Ball * ball, balls )
+    delete ball;
+}
+
+void BallsAnalyzer::initializeGL()
+{
+    // Set a smooth shade model
+    glShadeModel( GL_SMOOTH );
+
+    // Disable depth test (all is drawn 'z-sorted')
+    glDisable( GL_DEPTH_TEST );
+
+    // Set blending function (Alpha addition)
+    glBlendFunc( GL_SRC_ALPHA, GL_ONE );
+
+    // Clear frame with a black background
+    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+}
+
+void BallsAnalyzer::resizeGL( int w, int h )
+{
+    // Setup screen. We're going to manually do the perspective projection
+    glViewport( 0, 0, ( GLint )w, ( GLint )h );
+    glMatrixMode( GL_PROJECTION );
+    glLoadIdentity();
+    glFrustum( -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f );
+
+    // Get the aspect ratio of the screen to draw 'circular' particles
+    float ratio = ( float )w / ( float )h;
+    if( ratio >= 1.0 )
+    {
+        unitX = 0.34 / ratio;
+        unitY = 0.34;
+    }
+    else
+    {
+        unitX = 0.34;
+        unitY = 0.34 * ratio;
+    }
+
+    // Get current timestamp.
+    timeval tv;
+    gettimeofday( &tv, NULL );
+    show.timeStamp = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
+}
+
+void BallsAnalyzer::paused()
+{
+    //analyze( Scope() );
+}
+
+void BallsAnalyzer::analyze( const QVector<float> &s )
+{
+    // compute the dTime since the last call
+    timeval tv;
+    gettimeofday( &tv, NULL );
+    double currentTime = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
+    show.dT = currentTime - show.timeStamp;
+    show.timeStamp = currentTime;
+
+    // compute energy integrating frame's spectrum
+    if( !s.empty() )
+    {
+        int bands = s.size();
+        float currentEnergy = 0,
+              maxValue = 0;
+        // integrate spectrum -> energy
+        for( int i = 0; i < bands; i++ )
+        {
+            float value = s[i];
+            currentEnergy += value;
+            if( value > maxValue )
+                maxValue = value;
+        }
+        currentEnergy *= 100.0 / ( float )bands;
+        // emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds)
+        show.peakEnergy = 1.0 + ( show.peakEnergy - 1.0 ) * exp( - show.dT / 10.0 );
+        if( currentEnergy > show.peakEnergy )
+            show.peakEnergy = currentEnergy;
+        // check for silence
+        frame.silence = currentEnergy < 0.001;
+        // normalize frame energy against peak energy and compute frame stats
+        currentEnergy /= show.peakEnergy;
+        frame.dEnergy = currentEnergy - frame.energy;
+        frame.energy = currentEnergy;
+    }
+    else
+        frame.silence = true;
+}
+
+void BallsAnalyzer::paintGL()
+{
+    // limit max dT to 0.05 and update color and scroll constants
+    if( show.dT > 0.05 )
+        show.dT = 0.05;
+    show.colorK += show.dT * 0.4;
+    if( show.colorK > 3.0 )
+        show.colorK -= 3.0;
+    show.gridScrollK += 0.2 * show.peakEnergy * show.dT;
+
+    // Switch to MODEL matrix and clear screen
+    glMatrixMode( GL_MODELVIEW );
+    glLoadIdentity();
+    glClear( GL_COLOR_BUFFER_BIT );
+
+    // Draw scrolling grid
+    if( ( show.gridEnergyK > 0.05 ) || ( !frame.silence && frame.dEnergy < -0.3 ) )
+    {
+        show.gridEnergyK *= exp( -show.dT / 0.1 );
+        if( -frame.dEnergy > show.gridEnergyK )
+            show.gridEnergyK = -frame.dEnergy * 2.0;
+        float gridColor[4] = { 0.0, 1.0, 0.6, show.gridEnergyK };
+        drawScrollGrid( show.gridScrollK, gridColor );
+    }
+
+    // Roll camera up/down handling the beat
+    show.camRot += show.camRoll * show.dT;        // posision
+    show.camRoll -= 400 * show.camRot * show.dT;    // elasticity
+    show.camRoll *= ( 1 - 2.0 * show.dT );      // friction
+    if( !frame.silence && frame.dEnergy > 0.4 )
+        show.camRoll += show.peakEnergy * 2.0;
+    glRotatef( show.camRoll / 2.0, 1, 0, 0 );
+
+    // Translate the drawing plane
+    glTranslatef( 0.0f, 0.0f, -1.8f );
+
+    // Draw upper/lower planes and paddles
+    drawHFace( -1.0 );
+    drawHFace( 1.0 );
+    leftPaddle->renderGL();
+    rightPaddle->renderGL();
+
+    // Draw Balls
+    if( ballTexture )
+    {
+        glEnable( GL_TEXTURE_2D );
+        glBindTexture( GL_TEXTURE_2D, ballTexture );
+    }
+    else
+        glDisable( GL_TEXTURE_2D );
+    glEnable( GL_BLEND );
+    foreach( Ball * ball, balls )
+    {
+        float color[3],
+              angle = show.colorK;
+        // Rotate the color based on 'angle' value [0,3)
+        if( angle < 1.0 )
+        {
+            color[ 0 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
+            color[ 1 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
+            color[ 2 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
+        }
+        else if( angle < 2.0 )
+        {
+            angle -= 1.0;
+            color[ 0 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
+            color[ 1 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
+            color[ 2 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
+        }
+        else
+        {
+            angle -= 2.0;
+            color[ 0 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
+            color[ 1 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
+            color[ 2 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
+        }
+        // Draw the dot and update its physics also checking at bounces
+        glColor3fv( color );
+        drawDot3s( ball->x, ball->y, ball->z, 1.0 );
+        ball->updatePhysics( show.dT );
+        if( ball->x < 0 )
+            leftPaddle->bounce( ball );
+        else
+            rightPaddle->bounce( ball );
+    }
+    glDisable( GL_BLEND );
+    glDisable( GL_TEXTURE_2D );
+
+    // Update physics of paddles
+    leftPaddle->updatePhysics( show.dT );
+    rightPaddle->updatePhysics( show.dT );
+    if( !frame.silence )
+    {
+        leftPaddle->impulse( frame.energy * 3.0 + frame.dEnergy * 6.0 );
+        rightPaddle->impulse( -frame.energy * 3.0 - frame.dEnergy * 6.0 );
+    }
+}
+
+void BallsAnalyzer::drawDot3s( float x, float y, float z, float size )
+{
+    // Circular XY dot drawing functions
+    float sizeX = size * unitX,
+          sizeY = size * unitY,
+          pXm = x - sizeX,
+          pXM = x + sizeX,
+          pYm = y - sizeY,
+          pYM = y + sizeY;
+    // Draw the Dot
+    glBegin( GL_QUADS );
+    glTexCoord2f( 0, 0 );    // Bottom Left
+    glVertex3f( pXm, pYm, z );
+    glTexCoord2f( 0, 1 );    // Top Left
+    glVertex3f( pXm, pYM, z );
+    glTexCoord2f( 1, 1 );    // Top Right
+    glVertex3f( pXM, pYM, z );
+    glTexCoord2f( 1, 0 );    // Bottom Right
+    glVertex3f( pXM, pYm, z );
+    glEnd();
+
+    // Shadow XZ drawing functions
+    float sizeZ = size / 10.0,
+          pZm = z - sizeZ,
+          pZM = z + sizeZ,
+          currentColor[4];
+    glGetFloatv( GL_CURRENT_COLOR, currentColor );
+    float alpha = currentColor[3],
+          topSide = ( y + 1 ) / 4,
+          bottomSide = ( 1 - y ) / 4;
+    // Draw the top shadow
+    currentColor[3] = topSide * topSide * alpha;
+    glColor4fv( currentColor );
+    glBegin( GL_QUADS );
+    glTexCoord2f( 0, 0 );    // Bottom Left
+    glVertex3f( pXm, 1, pZm );
+    glTexCoord2f( 0, 1 );    // Top Left
+    glVertex3f( pXm, 1, pZM );
+    glTexCoord2f( 1, 1 );    // Top Right
+    glVertex3f( pXM, 1, pZM );
+    glTexCoord2f( 1, 0 );    // Bottom Right
+    glVertex3f( pXM, 1, pZm );
+    glEnd();
+    // Draw the bottom shadow
+    currentColor[3] = bottomSide * bottomSide * alpha;
+    glColor4fv( currentColor );
+    glBegin( GL_QUADS );
+    glTexCoord2f( 0, 0 );    // Bottom Left
+    glVertex3f( pXm, -1, pZm );
+    glTexCoord2f( 0, 1 );    // Top Left
+    glVertex3f( pXm, -1, pZM );
+    glTexCoord2f( 1, 1 );    // Top Right
+    glVertex3f( pXM, -1, pZM );
+    glTexCoord2f( 1, 0 );    // Bottom Right
+    glVertex3f( pXM, -1, pZm );
+    glEnd();
+}
+
+void BallsAnalyzer::drawHFace( float y )
+{
+    glBegin( GL_TRIANGLE_STRIP );
+    glColor3f( 0.0f, 0.1f, 0.2f );
+    glVertex3f( -1.0f, y, 0.0 );
+    glVertex3f( 1.0f, y, 0.0 );
+    glColor3f( 0.1f, 0.6f, 0.5f );
+    glVertex3f( -1.0f, y, 2.0 );
+    glVertex3f( 1.0f, y, 2.0 );
+    glEnd();
+}
+
+void BallsAnalyzer::drawScrollGrid( float scroll, float color[4] )
+{
+    if( !gridTexture )
+        return;
+    glMatrixMode( GL_TEXTURE );
+    glLoadIdentity();
+    glTranslatef( 0.0, -scroll, 0.0 );
+    glMatrixMode( GL_MODELVIEW );
+    float backColor[4] = { 1.0, 1.0, 1.0, 0.0 };
+    for( int i = 0; i < 3; i++ )
+        backColor[ i ] = color[ i ];
+    glEnable( GL_TEXTURE_2D );
+    glBindTexture( GL_TEXTURE_2D, gridTexture );
+    glEnable( GL_BLEND );
+    glBegin( GL_TRIANGLE_STRIP );
+    glColor4fv( color );    // top face
+    glTexCoord2f( 0.0f, 1.0f );
+    glVertex3f( -1.0f, 1.0f, -1.0f );
+    glTexCoord2f( 1.0f, 1.0f );
+    glVertex3f( 1.0f, 1.0f, -1.0f );
+    glColor4fv( backColor );    // central points
+    glTexCoord2f( 0.0f, 0.0f );
+    glVertex3f( -1.0f, 0.0f, -3.0f );
+    glTexCoord2f( 1.0f, 0.0f );
+    glVertex3f( 1.0f, 0.0f, -3.0f );
+    glColor4fv( color );    // bottom face
+    glTexCoord2f( 0.0f, 1.0f );
+    glVertex3f( -1.0f, -1.0f, -1.0f );
+    glTexCoord2f( 1.0f, 1.0f );
+    glVertex3f( 1.0f, -1.0f, -1.0f );
+    glEnd();
+    glDisable( GL_BLEND );
+    glDisable( GL_TEXTURE_2D );
+    glMatrixMode( GL_TEXTURE );
+    glLoadIdentity();
+    glMatrixMode( GL_MODELVIEW );
+}
+
+bool BallsAnalyzer::loadTexture( QString fileName, GLuint& textureID )
+{
+    //reset texture ID to the default EMPTY value
+    textureID = 0;
+
+    //load image
+    QImage tmp;
+    if( !tmp.load( fileName ) )
+        return false;
+
+    //convert it to suitable format (flipped RGBA)
+    QImage texture = QGLWidget::convertToGLFormat( tmp );
+    if( texture.isNull() )
+        return false;
+
+    //get texture number and bind loaded image to that texture
+    glGenTextures( 1, &textureID );
+    glBindTexture( GL_TEXTURE_2D, textureID );
+    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+    glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(),
+                  0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() );
+    return true;
+}
+
+void BallsAnalyzer::freeTexture( GLuint& textureID )
+{
+    if( textureID > 0 )
+        glDeleteTextures( 1, &textureID );
+    textureID = 0;
+}
+
diff --git a/src/context/applets/analyzer/AnalyzerApplet.h b/src/context/applets/analyzer/BallsAnalyzer.h
similarity index 51%
copy from src/context/applets/analyzer/AnalyzerApplet.h
copy to src/context/applets/analyzer/BallsAnalyzer.h
index b47246f..0bff339 100644
--- a/src/context/applets/analyzer/AnalyzerApplet.h
+++ b/src/context/applets/analyzer/BallsAnalyzer.h
@@ -1,5 +1,5 @@
 /****************************************************************************************
- * Copyright (c) 2013 Mark Kretschmann <kretschmann at kde.org>                            *
+ * Copyright (c) 2004 Enrico Ros <eros.kde at email.it>                                    *
  *                                                                                      *
  * 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        *
@@ -14,25 +14,63 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef ANALYZER_APPLET_H
-#define ANALYZER_APPLET_H
+#ifndef BALLS_ANALYZER_H
+#define BALLS_ANALYZER_H
 
-#include "context/Applet.h"
-#include "context/DataEngine.h"
+#include "AnalyzerBase.h"
 
 
-class AnalyzerApplet : public Context::Applet
-{
-    Q_OBJECT
+class QWidget;
+class Ball;
+class Paddle;
 
+class BallsAnalyzer : public Analyzer::Base3D
+{
 public:
-    AnalyzerApplet( QObject* parent, const QVariantList& args );
-    virtual ~AnalyzerApplet();
+    BallsAnalyzer( QWidget * );
+    ~BallsAnalyzer();
+    void analyze( const QVector<float> & );
+    void paused();
 
-public slots:
-    virtual void init();
-};
+protected:
+    void initializeGL();
+    void resizeGL( int w, int h );
+    void paintGL();
+
+private:
+    struct ShowProperties
+    {
+        double timeStamp;
+        double dT;
+        float colorK;
+        float gridScrollK;
+        float gridEnergyK;
+        float camRot;
+        float camRoll;
+        float peakEnergy;
+    } show;
+
+    struct FrameProperties
+    {
+        bool silence;
+        float energy;
+        float dEnergy;
+    } frame;
 
-AMAROK_EXPORT_APPLET( analyzer, AnalyzerApplet )
+    static const int NUMBER_OF_BALLS = 16;
+
+    QList<Ball*> balls;
+    Paddle * leftPaddle, * rightPaddle;
+    float unitX, unitY;
+    GLuint ballTexture;
+    GLuint gridTexture;
+
+    void drawDot3s( float x, float y, float z, float size );
+    void drawHFace( float y );
+    void drawScrollGrid( float scroll, float color[4] );
+
+    bool loadTexture( QString file, GLuint& textureID );
+    void freeTexture( GLuint& textureID );
+};
 
 #endif
diff --git a/src/context/applets/analyzer/BlockAnalyzer.cpp b/src/context/applets/analyzer/BlockAnalyzer.cpp
index d171437..3316c28 100644
--- a/src/context/applets/analyzer/BlockAnalyzer.cpp
+++ b/src/context/applets/analyzer/BlockAnalyzer.cpp
@@ -43,9 +43,8 @@ BlockAnalyzer::BlockAnalyzer( QWidget *parent )
     , 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
+    setObjectName( "Blocky" );
     setMaximumWidth( MAX_COLUMNS * ( WIDTH + 1 ) - 1 );
-    setMaximumHeight( MIN_ROWS * ( HEIGHT + 1 ) - 1 );
 }
 
 BlockAnalyzer::~BlockAnalyzer()
@@ -124,7 +123,6 @@ void
 BlockAnalyzer::analyze( const QVector<float> &s )
 {
     Analyzer::interpolate( s, m_scope );
-    update();
 }
 
 void
diff --git a/src/context/applets/analyzer/CMakeLists.txt b/src/context/applets/analyzer/CMakeLists.txt
index 0fd9d5e..e793742 100644
--- a/src/context/applets/analyzer/CMakeLists.txt
+++ b/src/context/applets/analyzer/CMakeLists.txt
@@ -7,7 +7,9 @@ include_directories(
 set(analyzer_SRCS
     AnalyzerApplet.cpp
     AnalyzerBase.cpp
+    BallsAnalyzer.cpp
     BlockAnalyzer.cpp
+    DiscoAnalyzer.cpp
     fht.cpp
     )
 
diff --git a/src/context/applets/analyzer/DiscoAnalyzer.cpp b/src/context/applets/analyzer/DiscoAnalyzer.cpp
new file mode 100644
index 0000000..6fc1c04
--- /dev/null
+++ b/src/context/applets/analyzer/DiscoAnalyzer.cpp
@@ -0,0 +1,336 @@
+/****************************************************************************************
+ * Copyright (c) 2004 Enrico Ros <eros.kde at email.it>                                    *
+ *                                                                                      *
+ * 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 "DiscoAnalyzer.h"
+
+#include "core/support/Debug.h"
+
+#include <QImage>
+#include <KStandardDirs>
+
+#include <cmath>
+#include <cstdlib>
+#include <sys/time.h>
+
+
+DiscoAnalyzer::DiscoAnalyzer( QWidget *parent ):
+    Analyzer::Base3D( parent )
+{
+    setObjectName( "Disco" );
+
+    //initialize openGL context before managing GL calls
+    makeCurrent();
+    loadTexture( KStandardDirs::locate( "data", "amarok/images/dot.png" ), dotTexture );
+    loadTexture( KStandardDirs::locate( "data", "amarok/images/wirl1.png" ), w1Texture );
+    loadTexture( KStandardDirs::locate( "data", "amarok/images/wirl2.png" ), w2Texture );
+
+    showStruct.paused = true;
+    showStruct.pauseTimer = 0.0;
+    showStruct.rotDegrees = 0.0;
+    frame.rotDegrees = 0.0;
+}
+
+DiscoAnalyzer::~DiscoAnalyzer()
+{
+    freeTexture( dotTexture );
+    freeTexture( w1Texture );
+    freeTexture( w2Texture );
+}
+
+void DiscoAnalyzer::initializeGL()
+{
+    // Set a smooth shade model
+    glShadeModel( GL_SMOOTH );
+
+    // Disable depth test (all is drawn on a 2d plane)
+    glDisable( GL_DEPTH_TEST );
+
+    // Set blend parameters for 'composting alpha'
+    glBlendFunc( GL_SRC_ALPHA, GL_ONE );                        //superpose
+    //glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );        //fade
+    glEnable( GL_BLEND );
+
+    // Clear frame with a black background
+    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+    glClear( GL_COLOR_BUFFER_BIT );
+}
+
+void DiscoAnalyzer::resizeGL( int w, int h )
+{
+    // Setup screen. We're going to manually do the perspective projection
+    glViewport( 0, 0, ( GLint )w, ( GLint )h );
+    glMatrixMode( GL_PROJECTION );
+    glLoadIdentity();
+    glOrtho( -10.0f, 10.0f, -10.0f, 10.0f, -5.0f, 5.0f );
+
+    // Get the aspect ratio of the screen to draw 'cicular' particles
+    float ratio = ( float )w / ( float )h,
+          eqPixH = 60,
+          eqPixW = 80;
+    if( ratio >= ( 4.0 / 3.0 ) )
+    {
+        unitX = 10.0 / ( eqPixH * ratio );
+        unitY = 10.0 / eqPixH;
+    }
+    else
+    {
+        unitX = 10.0 / eqPixW;
+        unitY = 10.0 / ( eqPixW / ratio );
+    }
+
+    // Get current timestamp.
+    timeval tv;
+    gettimeofday( &tv, NULL );
+    showStruct.timeStamp = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
+}
+
+void DiscoAnalyzer::paused()
+{
+    // FIXME
+//    analyze( Scope() );
+}
+
+void DiscoAnalyzer::analyze( const QVector<float> &s )
+{
+    bool haveNoData = s.empty();
+
+    // if we're going into pause mode, clear timers.
+    if( !showStruct.paused && haveNoData )
+        showStruct.pauseTimer = 0.0;
+
+    // if we have got data, interpolate it (asking myself why I'm doing it here..)
+    if( !( showStruct.paused = haveNoData ) )
+    {
+        int bands = s.size(),
+            lowbands = bands / 4,
+            hibands = bands / 3,
+            midbands = bands - lowbands - hibands; Q_UNUSED( midbands );
+        float currentEnergy = 0,
+              currentMeanBand = 0,
+              maxValue = 0;
+        for( int i = 0; i < bands; i++ )
+        {
+            float value = s[i];
+            currentEnergy += value;
+            currentMeanBand += ( float )i * value;
+            if( value > maxValue )
+                maxValue = value;
+        }
+        frame.silence = currentEnergy < 0.001;
+        if( !frame.silence )
+        {
+            frame.meanBand = 100.0 * currentMeanBand / ( currentEnergy * bands );
+            currentEnergy = 100.0 * currentEnergy / ( float )bands;
+            frame.dEnergy = currentEnergy - frame.energy;
+            frame.energy = currentEnergy;
+//            printf( "%d  [%f :: %f ]\t%f \n", bands, frame.energy, frame.meanBand, maxValue         );
+        }
+        else
+            frame.energy = 0.0;
+    }
+}
+
+void DiscoAnalyzer::paintGL()
+{
+    // Compute the dT since the last call to paintGL and update timings
+    timeval tv;
+    gettimeofday( &tv, NULL );
+    double currentTime = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
+    showStruct.dT = currentTime - showStruct.timeStamp;
+    showStruct.timeStamp = currentTime;
+
+    // Clear frame
+    glClear( GL_COLOR_BUFFER_BIT );
+
+    // Shitch to MODEL matrix and reset it to default
+    glMatrixMode( GL_MODELVIEW );
+    glLoadIdentity();
+
+    // Fade the previous drawings.
+    /*    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+        glBegin( GL_TRIANGLE_STRIP );
+          glColor4f( 0.0f, 0.0f, 0.0f, 0.2f );
+          glVertex2f( 10.0f, 10.0f );
+          glVertex2f( -10.0f, 10.0f );
+          glVertex2f( 10.0f, -10.0f );
+          glVertex2f( -10.0f, -10.0f );
+        glEnd();*/
+
+    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+    glEnable( GL_TEXTURE_2D );
+    float alphaN = showStruct.paused ? 0.2 : ( frame.energy / 10.0 ),
+          alphaP = showStruct.paused ? 1.0 : ( 1 - frame.energy / 20.0 );
+    if( alphaN > 1.0 )
+        alphaN = 1.0;
+    if( alphaP < 0.1 )
+        alphaP = 0.1;
+    glBindTexture( GL_TEXTURE_2D, w2Texture );
+    setTextureMatrix( showStruct.rotDegrees, 0.707 * alphaP );
+    glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
+    glBegin( GL_TRIANGLE_STRIP );
+    glTexCoord2f( 1.0, 1.0 );
+    glVertex2f( 10.0f, 10.0f );
+    glTexCoord2f( 0.0, 1.0 );
+    glVertex2f( -10.0f, 10.0f );
+    glTexCoord2f( 1.0, 0.0 );
+    glVertex2f( 10.0f, -10.0f );
+    glTexCoord2f( 0.0 , 0.0 );
+    glVertex2f( -10.0f, -10.0f );
+    glEnd();
+    glBindTexture( GL_TEXTURE_2D, w1Texture );
+    setTextureMatrix( -showStruct.rotDegrees * 2, 0.707 );
+    glColor4f( 1.0f, 1.0f, 1.0f, alphaN );
+    glBegin( GL_TRIANGLE_STRIP );
+    glTexCoord2f( 1.0, 1.0 );
+    glVertex2f( 10.0f, 10.0f );
+    glTexCoord2f( 0.0, 1.0 );
+    glVertex2f( -10.0f, 10.0f );
+    glTexCoord2f( 1.0, 0.0 );
+    glVertex2f( 10.0f, -10.0f );
+    glTexCoord2f( 0.0 , 0.0 );
+    glVertex2f( -10.0f, -10.0f );
+    glEnd();
+    setTextureMatrix( 0.0, 0.0 );
+    glDisable( GL_TEXTURE_2D );
+    glBlendFunc( GL_SRC_ALPHA, GL_ONE );
+
+    // Here begins the real draw loop
+    // some updates to the showStruct
+    showStruct.rotDegrees += 40.0 * showStruct.dT;
+    frame.rotDegrees += 80.0 * showStruct.dT;
+
+    // handle the 'pause' status
+    if( showStruct.paused )
+    {
+        if( showStruct.pauseTimer > 0.5 )
+        {
+            if( showStruct.pauseTimer > 0.6 )
+                showStruct.pauseTimer -= 0.6;
+            drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f );
+            drawFullDot( 0.0f, 0.4f, 0.8f, 1.0f );
+        }
+        showStruct.pauseTimer += showStruct.dT;
+        return;
+    }
+
+    if( dotTexture )
+    {
+        glEnable( GL_TEXTURE_2D );
+        glBindTexture( GL_TEXTURE_2D, dotTexture );
+    }
+    else
+        glDisable( GL_TEXTURE_2D );
+
+    glLoadIdentity();
+//    glRotatef( -frame.rotDegrees, 0,0,1 );
+    glBegin( GL_QUADS );
+//     Particle * particle = particleList.first();
+//     for (; particle; particle = particleList.next())
+    {
+        glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
+        drawDot( 0, 0, qMax( 10.0, ( 10.0 * frame.energy ) ) );
+        glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
+        drawDot( 6, 0, qMax( 10.0, ( 5.0 * frame.energy ) ) );
+        glColor4f( 0.0f, 0.4f, 1.0f, 1.0f );
+        drawDot( -6, 0, qMax( 10.0, ( 5.0 * frame.energy ) ) );
+    }
+    glEnd();
+}
+
+void DiscoAnalyzer::drawDot( float x, float y, float size )
+{
+    float sizeX = size * unitX,
+          sizeY = size * unitY,
+          pLeft = x - sizeX,
+          pTop = y + sizeY,
+          pRight = x + sizeX,
+          pBottom = y - sizeY;
+    glTexCoord2f( 0, 0 );        // Bottom Left
+    glVertex2f( pLeft, pBottom );
+    glTexCoord2f( 0, 1 );        // Top Left
+    glVertex2f( pLeft, pTop );
+    glTexCoord2f( 1, 1 );        // Top Right
+    glVertex2f( pRight, pTop );
+    glTexCoord2f( 1, 0 );        // Bottom Right
+    glVertex2f( pRight, pBottom );
+}
+
+void DiscoAnalyzer::drawFullDot( float r, float g, float b, float a )
+{
+    glBindTexture( GL_TEXTURE_2D, dotTexture );
+    glEnable( GL_TEXTURE_2D );
+    glColor4f( r, g, b, a );
+    glBegin( GL_TRIANGLE_STRIP );
+    glTexCoord2f( 1.0, 1.0 );
+    glVertex2f( 10.0f, 10.0f );
+    glTexCoord2f( 0.0, 1.0 );
+    glVertex2f( -10.0f, 10.0f );
+    glTexCoord2f( 1.0, 0.0 );
+    glVertex2f( 10.0f, -10.0f );
+    glTexCoord2f( 0.0 , 0.0 );
+    glVertex2f( -10.0f, -10.0f );
+    glEnd();
+    glDisable( GL_TEXTURE_2D );
+}
+
+
+void DiscoAnalyzer::setTextureMatrix( float rot, float scale )
+{
+    glMatrixMode( GL_TEXTURE );
+    glLoadIdentity();
+    if( rot != 0.0 || scale != 0.0 )
+    {
+        glTranslatef( 0.5f, 0.5f, 0.0f );
+        glRotatef( rot, 0.0f, 0.0f, 1.0f );
+        glScalef( scale, scale, 1.0f );
+        glTranslatef( -0.5f, -0.5f, 0.0f );
+    }
+    glMatrixMode( GL_MODELVIEW );
+}
+
+bool DiscoAnalyzer::loadTexture( QString fileName, GLuint& textureID )
+{
+    //reset texture ID to the default EMPTY value
+    textureID = 0;
+
+    //load image
+    QImage tmp;
+    if( !tmp.load( fileName ) )
+        return false;
+
+    //convert it to suitable format (flipped RGBA)
+    QImage texture = QGLWidget::convertToGLFormat( tmp );
+    if( texture.isNull() )
+        return false;
+
+    //get texture number and bind loaded image to that texture
+    glGenTextures( 1, &textureID );
+    glBindTexture( GL_TEXTURE_2D, textureID );
+    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+    glTexImage2D( GL_TEXTURE_2D, 0, 4, texture.width(), texture.height(),
+                  0, GL_RGBA, GL_UNSIGNED_BYTE, texture.bits() );
+    return true;
+}
+
+
+void DiscoAnalyzer::freeTexture( GLuint & textureID )
+{
+    if( textureID > 0 )
+        glDeleteTextures( 1, &textureID );
+    textureID = 0;
+}
+
diff --git a/src/context/applets/analyzer/AnalyzerApplet.h b/src/context/applets/analyzer/DiscoAnalyzer.h
similarity index 54%
copy from src/context/applets/analyzer/AnalyzerApplet.h
copy to src/context/applets/analyzer/DiscoAnalyzer.h
index b47246f..6aa7343 100644
--- a/src/context/applets/analyzer/AnalyzerApplet.h
+++ b/src/context/applets/analyzer/DiscoAnalyzer.h
@@ -1,5 +1,5 @@
 /****************************************************************************************
- * Copyright (c) 2013 Mark Kretschmann <kretschmann at kde.org>                            *
+ * Copyright (c) 2004 Enrico Ros <eros.kde at email.it>                                    *
  *                                                                                      *
  * 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        *
@@ -14,25 +14,58 @@
  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
  ****************************************************************************************/
 
-#ifndef ANALYZER_APPLET_H
-#define ANALYZER_APPLET_H
+#ifndef DISCO_ANALYZER_H
+#define DISCO_ANALYZER_H
 
-#include "context/Applet.h"
-#include "context/DataEngine.h"
 
+#include "AnalyzerBase.h"
 
-class AnalyzerApplet : public Context::Applet
-{
-    Q_OBJECT
 
+class QPaintEvent;
+
+class DiscoAnalyzer : public Analyzer::Base3D
+{
 public:
-    AnalyzerApplet( QObject* parent, const QVariantList& args );
-    virtual ~AnalyzerApplet();
+    DiscoAnalyzer( QWidget * );
+    ~DiscoAnalyzer();
+    void analyze( const QVector<float>& );
+    void paused();
 
-public slots:
-    virtual void init();
-};
+protected:
+    void initializeGL();
+    void resizeGL( int w, int h );
+    void paintGL();
+
+private:
+    struct ShowProperties
+    {
+        bool paused;
+        double timeStamp;
+        double dT;
+        double pauseTimer;
+        float rotDegrees;
+    } showStruct;
 
-AMAROK_EXPORT_APPLET( analyzer, AnalyzerApplet )
+    struct FrameProperties
+    {
+        float energy;
+        float dEnergy;
+        float meanBand;
+        float rotDegrees;
+        bool silence;
+    } frame;
+
+    GLuint dotTexture;
+    GLuint w1Texture;
+    GLuint w2Texture;
+    float unitX, unitY;
+
+    void drawDot( float x, float y, float size );
+    void drawFullDot( float r, float g, float b, float a );
+    void setTextureMatrix( float rot, float scale );
+
+    bool loadTexture( QString file, GLuint& textureID );
+    void freeTexture( GLuint& textureID );
+};
 
 #endif


More information about the Amarok-devel mailing list