MythTV  0.26-pre
visualize.cpp
Go to the documentation of this file.
00001 /*
00002         visualize.cpp
00003 
00004     (c) 2003 Thor Sigvaldason and Isaac Richards
00005         VERY closely based on code from mq3 by Brad Hughes
00006 
00007         Part of the mythTV project
00008 
00009         music visualizers
00010 */
00011 
00012 // C
00013 #include <cmath>
00014 
00015 // C++
00016 #include <iostream>
00017 using namespace std;
00018 
00019 // Qt
00020 #include <QCoreApplication>
00021 #include <QPainter>
00022 #include <QImage>
00023 
00024 // MythTV
00025 #include <mythdbcon.h>
00026 #include <mythcontext.h>
00027 #include <mythuihelper.h>
00028 
00029 // mythmusic
00030 #include "mainvisual.h"
00031 #include "visualize.h"
00032 #include "inlines.h"
00033 #include "decoder.h"
00034 #include "metadata.h"
00035 #include "musicplayer.h"
00036 
00037 #define FFTW_N 512
00038 // static_assert(FFTW_N==SAMPLES_DEFAULT_SIZE)
00039 
00040 
00041 VisFactory* VisFactory::g_pVisFactories = 0;
00042 
00043 VisualBase::VisualBase(bool screensaverenable)
00044     : m_fps(20), m_xscreensaverenable(screensaverenable)
00045 {
00046     if (!m_xscreensaverenable)
00047         GetMythUI()->DoDisableScreensaver();
00048 }
00049 
00050 VisualBase::~VisualBase()
00051 {
00052     //
00053     //    This is only here so
00054     //    that derived classes
00055     //    can destruct properly
00056     //
00057     if (!m_xscreensaverenable)
00058         GetMythUI()->DoRestoreScreensaver();
00059 }
00060 
00061 
00062 void VisualBase::drawWarning(QPainter *p, const QColor &back, const QSize &size, QString warning, int fontSize)
00063 {
00064     p->fillRect(0, 0, size.width(), size.height(), back);
00065     p->setPen(Qt::white);
00066     QFont font = GetMythUI()->GetMediumFont();
00067     font.setPointSizeF(fontSize * (size.width() / 800.0));
00068     p->setFont(font);
00069 
00070     p->drawText(0, 0, size.width(), size.height(), Qt::AlignVCenter | Qt::AlignHCenter | Qt::TextWordWrap, warning);
00071 }
00072 
00074 // LogScale
00075 
00076 LogScale::LogScale(int maxscale, int maxrange)
00077     : indices(0), s(0), r(0)
00078 {
00079     setMax(maxscale, maxrange);
00080 }
00081 
00082 LogScale::~LogScale()
00083 {
00084     if (indices)
00085         delete [] indices;
00086 }
00087 
00088 void LogScale::setMax(int maxscale, int maxrange)
00089 {
00090     if (maxscale == 0 || maxrange == 0)
00091         return;
00092 
00093     s = maxscale;
00094     r = maxrange;
00095 
00096     if (indices)
00097         delete [] indices;
00098 
00099     double alpha;
00100     int i, scaled;
00101     long double domain = (long double) maxscale;
00102     long double range  = (long double) maxrange;
00103     long double x  = 1.0;
00104     long double dx = 1.0;
00105     long double y  = 0.0;
00106     long double yy = 0.0;
00107     long double t  = 0.0;
00108     long double e4 = 1.0E-8;
00109 
00110     indices = new int[maxrange];
00111     for (i = 0; i < maxrange; i++)
00112         indices[i] = 0;
00113 
00114     // initialize log scale
00115     for (uint i=0; i<10000 && (std::abs(dx) > e4); i++)
00116     {
00117         t = std::log((domain + x) / x);
00118         y = (x * t) - range;
00119         yy = t - (domain / (x + domain));
00120         dx = y / yy;
00121         x -= dx;
00122     }
00123 
00124     alpha = x;
00125     for (i = 1; i < (int) domain; i++)
00126     {
00127         scaled = (int) floor(0.5 + (alpha * log((double(i) + alpha) / alpha)));
00128         if (scaled < 1)
00129             scaled = 1;
00130         if (indices[scaled - 1] < i)
00131             indices[scaled - 1] = i;
00132     }
00133 }
00134 
00135 int LogScale::operator[](int index)
00136 {
00137     return indices[index];
00138 }
00139 
00141 // StereoScope
00142 
00143 #define RUBBERBAND 0
00144 #define TWOCOLOUR 0
00145 StereoScope::StereoScope() :
00146     startColor(Qt::green), targetColor(Qt::red),
00147     rubberband(RUBBERBAND), falloff(1.0)
00148 {
00149     m_fps = 45;
00150 }
00151 
00152 StereoScope::~StereoScope()
00153 {
00154 }
00155 
00156 void StereoScope::resize( const QSize &newsize )
00157 {
00158     size = newsize;
00159 
00160     uint os = magnitudes.size();
00161     magnitudes.resize( size.width() * 2 );
00162     for ( ; os < magnitudes.size(); os++ )
00163         magnitudes[os] = 0.0;
00164 }
00165 
00166 bool StereoScope::process( VisualNode *node )
00167 {
00168     bool allZero = true;
00169 
00170 
00171     if (node)
00172     {
00173         double index = 0;
00174         double const step = (double)SAMPLES_DEFAULT_SIZE / size.width();
00175         for ( int i = 0; i < size.width(); i++)
00176         {
00177             unsigned long indexTo = (unsigned long)(index + step);
00178             if (indexTo == (unsigned long)(index))
00179                 indexTo = (unsigned long)(index + 1);
00180 
00181             double valL = 0, valR = 0;
00182 #if RUBBERBAND
00183             if ( rubberband ) {
00184                 valL = magnitudes[ i ];
00185                 valR = magnitudes[ i + size.width() ];
00186                 if (valL < 0.) {
00187                     valL += falloff;
00188                     if ( valL > 0. )
00189                         valL = 0.;
00190                 }
00191                 else
00192                 {
00193                     valL -= falloff;
00194                     if ( valL < 0. )
00195                         valL = 0.;
00196                 }
00197                 if (valR < 0.)
00198                 {
00199                     valR += falloff;
00200                     if ( valR > 0. )
00201                         valR = 0.;
00202                 }
00203                 else
00204                 {
00205                     valR -= falloff;
00206                     if ( valR < 0. )
00207                         valR = 0.;
00208                 }
00209             }
00210 #endif
00211             for (unsigned long s = (unsigned long)index; s < indexTo && s < node->length; s++)
00212             {
00213                 double tmpL = ( ( node->left ?
00214                        double( node->left[s] ) : 0.) *
00215                      double( size.height() / 4 ) ) / 32768.;
00216                 double tmpR = ( ( node->right ?
00217                        double( node->right[s]) : 0.) *
00218                      double( size.height() / 4 ) ) / 32768.;
00219                 if (tmpL > 0)
00220                     valL = (tmpL > valL) ? tmpL : valL;
00221                 else
00222                     valL = (tmpL < valL) ? tmpL : valL;
00223                 if (tmpR > 0)
00224                     valR = (tmpR > valR) ? tmpR : valR;
00225                 else
00226                     valR = (tmpR < valR) ? tmpR : valR;
00227             }
00228 
00229             if (valL != 0. || valR != 0.)
00230                 allZero = false;
00231 
00232             magnitudes[ i ] = valL;
00233             magnitudes[ i + size.width() ] = valR;
00234 
00235             index = index + step;
00236         }
00237 #if RUBBERBAND
00238     }
00239     else if (rubberband)
00240     {
00241         for ( int i = 0; i < size.width(); i++)
00242         {
00243             double valL = magnitudes[ i ];
00244             if (valL < 0) {
00245                 valL += 2;
00246                 if (valL > 0.)
00247                     valL = 0.;
00248             } else {
00249                 valL -= 2;
00250                 if (valL < 0.)
00251                     valL = 0.;
00252             }
00253 
00254             double valR = magnitudes[ i + size.width() ];
00255             if (valR < 0.) {
00256                 valR += falloff;
00257                 if (valR > 0.)
00258                     valR = 0.;
00259             }
00260             else
00261             {
00262                 valR -= falloff;
00263                 if (valR < 0.)
00264                     valR = 0.;
00265             }
00266 
00267             if (valL != 0. || valR != 0.)
00268                 allZero = false;
00269 
00270             magnitudes[ i ] = valL;
00271             magnitudes[ i + size.width() ] = valR;
00272         }
00273 #endif
00274     }
00275     else
00276     {
00277         for ( int i = 0; (unsigned) i < magnitudes.size(); i++ )
00278             magnitudes[ i ] = 0.;
00279     }
00280 
00281     return allZero;
00282 }
00283 
00284 bool StereoScope::draw( QPainter *p, const QColor &back )
00285 {
00286     p->fillRect(0, 0, size.width(), size.height(), back);
00287     for ( int i = 1; i < size.width(); i++ )
00288     {
00289 #if TWOCOLOUR
00290     double r, g, b, per;
00291 
00292     // left
00293     per = double( magnitudes[ i ] * 2 ) /
00294           double( size.height() / 4 );
00295     if (per < 0.0)
00296         per = -per;
00297     if (per > 1.0)
00298         per = 1.0;
00299     else if (per < 0.0)
00300         per = 0.0;
00301 
00302     r = startColor.red() + (targetColor.red() -
00303                 startColor.red()) * (per * per);
00304     g = startColor.green() + (targetColor.green() -
00305                   startColor.green()) * (per * per);
00306     b = startColor.blue() + (targetColor.blue() -
00307                  startColor.blue()) * (per * per);
00308 
00309     if (r > 255.0)
00310         r = 255.0;
00311     else if (r < 0.0)
00312         r = 0;
00313 
00314     if (g > 255.0)
00315         g = 255.0;
00316     else if (g < 0.0)
00317         g = 0;
00318 
00319     if (b > 255.0)
00320         b = 255.0;
00321     else if (b < 0.0)
00322         b = 0;
00323 
00324     p->setPen( QColor( int(r), int(g), int(b) ) );
00325 #else
00326     p->setPen(Qt::red);
00327 #endif
00328     p->drawLine( i - 1, (int)((size.height() / 4) + magnitudes[i - 1]),
00329              i, (int)((size.height() / 4) + magnitudes[i]));
00330 
00331 #if TWOCOLOUR
00332     // right
00333     per = double( magnitudes[ i + size.width() ] * 2 ) /
00334           double( size.height() / 4 );
00335     if (per < 0.0)
00336         per = -per;
00337     if (per > 1.0)
00338         per = 1.0;
00339     else if (per < 0.0)
00340         per = 0.0;
00341 
00342     r = startColor.red() + (targetColor.red() -
00343                 startColor.red()) * (per * per);
00344     g = startColor.green() + (targetColor.green() -
00345                   startColor.green()) * (per * per);
00346     b = startColor.blue() + (targetColor.blue() -
00347                  startColor.blue()) * (per * per);
00348 
00349     if (r > 255.0)
00350         r = 255.0;
00351     else if (r < 0.0)
00352         r = 0;
00353 
00354     if (g > 255.0)
00355         g = 255.0;
00356     else if (g < 0.0)
00357         g = 0;
00358 
00359     if (b > 255.0)
00360         b = 255.0;
00361     else if (b < 0.0)
00362         b = 0;
00363 
00364     p->setPen( QColor( int(r), int(g), int(b) ) );
00365 #else
00366     p->setPen(Qt::red);
00367 #endif
00368     p->drawLine( i - 1, (int)((size.height() * 3 / 4) +
00369              magnitudes[i + size.width() - 1]),
00370              i, (int)((size.height() * 3 / 4) +
00371                      magnitudes[i + size.width()]));
00372     }
00373 
00374     return true;
00375 }
00376 
00378 // MonoScope
00379 
00380 MonoScope::MonoScope()
00381 {
00382 }
00383 
00384 MonoScope::~MonoScope()
00385 {
00386 }
00387 
00388 bool MonoScope::process( VisualNode *node )
00389 {
00390     bool allZero = true;
00391 
00392     if (node)
00393     {
00394         double index = 0;
00395         double const step = (double)SAMPLES_DEFAULT_SIZE / size.width();
00396         for (int i = 0; i < size.width(); i++)
00397         {
00398             unsigned long indexTo = (unsigned long)(index + step);
00399             if (indexTo == (unsigned long)index)
00400                 indexTo = (unsigned long)(index + 1);
00401 
00402             double val = 0;
00403 #if RUBBERBAND
00404             if ( rubberband )
00405             {
00406                 val = magnitudes[ i ];
00407                 if (val < 0.)
00408                 {
00409                     val += falloff;
00410                     if ( val > 0. )
00411                     {
00412                         val = 0.;
00413                     }
00414                 }
00415                 else
00416                 {
00417                     val -= falloff;
00418                     if ( val < 0. )
00419                     {
00420                         val = 0.;
00421                     }
00422                 }
00423             }
00424 #endif
00425             for (unsigned long s = (unsigned long)index; s < indexTo && s < node->length; s++)
00426             {
00427                 double tmp = ( double( node->left[s] ) +
00428                         (node->right ? double( node->right[s] ) : 0) *
00429                         double( size.height() / 2 ) ) / 65536.;
00430                 if (tmp > 0)
00431                 {
00432                     val = (tmp > val) ? tmp : val;
00433                 }
00434                 else
00435                 {
00436                     val = (tmp < val) ? tmp : val;
00437                 }
00438             }
00439 
00440             if ( val != 0. )
00441             {
00442                 allZero = false;
00443             }
00444             magnitudes[ i ] = val;
00445             index = index + step;
00446         }
00447     }
00448 #if RUBBERBAND
00449     else if (rubberband)
00450     {
00451         for (int i = 0; i < size.width(); i++) {
00452             double val = magnitudes[ i ];
00453             if (val < 0) {
00454                 val += 2;
00455                 if (val > 0.)
00456                     val = 0.;
00457             } else {
00458                 val -= 2;
00459                 if (val < 0.)
00460                     val = 0.;
00461             }
00462 
00463             if ( val != 0. )
00464                 allZero = false;
00465             magnitudes[ i ] = val;
00466         }
00467     }
00468 #endif
00469     else
00470     {
00471         for (int i = 0; i < size.width(); i++ )
00472             magnitudes[ i ] = 0.;
00473     }
00474 
00475     return allZero;
00476 }
00477 
00478 bool MonoScope::draw( QPainter *p, const QColor &back )
00479 {
00480     p->fillRect( 0, 0, size.width(), size.height(), back );
00481     for ( int i = 1; i < size.width(); i++ ) {
00482 #if TWOCOLOUR
00483         double r, g, b, per;
00484 
00485         per = double( magnitudes[ i ] ) /
00486               double( size.height() / 4 );
00487         if (per < 0.0)
00488             per = -per;
00489         if (per > 1.0)
00490             per = 1.0;
00491         else if (per < 0.0)
00492             per = 0.0;
00493 
00494         r = startColor.red() + (targetColor.red() -
00495                                 startColor.red()) * (per * per);
00496         g = startColor.green() + (targetColor.green() -
00497                                   startColor.green()) * (per * per);
00498         b = startColor.blue() + (targetColor.blue() -
00499                                  startColor.blue()) * (per * per);
00500 
00501         if (r > 255.0)
00502             r = 255.0;
00503         else if (r < 0.0)
00504             r = 0;
00505 
00506         if (g > 255.0)
00507             g = 255.0;
00508         else if (g < 0.0)
00509             g = 0;
00510 
00511         if (b > 255.0)
00512             b = 255.0;
00513         else if (b < 0.0)
00514             b = 0;
00515 
00516         p->setPen(QColor(int(r), int(g), int(b)));
00517 #else
00518         p->setPen(Qt::red);
00519 #endif
00520         p->drawLine( i - 1, (int)(size.height() / 2 + magnitudes[ i - 1 ]),
00521                      i, (int)(size.height() / 2 + magnitudes[ i ] ));
00522     }
00523 
00524     return true;
00525 }
00526 
00528 // StereoScopeFactory
00529 
00530 static class StereoScopeFactory : public VisFactory
00531 {
00532   public:
00533     const QString &name(void) const
00534     {
00535         static QString name = QCoreApplication::translate("Visualizers",
00536                                                           "StereoScope");
00537         return name;
00538     }
00539 
00540     uint plugins(QStringList *list) const
00541     {
00542         *list << name();
00543         return 1;
00544     }
00545 
00546     VisualBase *create(MainVisual *parent, const QString &pluginName) const
00547     {
00548         (void)parent;
00549         (void)pluginName;
00550         return new StereoScope();
00551     }
00552 }StereoScopeFactory;
00553 
00554 
00556 // MonoScopeFactory
00557 
00558 static class MonoScopeFactory : public VisFactory
00559 {
00560   public:
00561     const QString &name(void) const
00562     {
00563         static QString name = QCoreApplication::translate("Visualizers",
00564                                                           "MonoScope");
00565         return name;
00566     }
00567 
00568     uint plugins(QStringList *list) const
00569     {
00570         *list << name();
00571         return 1;
00572     }
00573 
00574     VisualBase *create(MainVisual *parent, const QString &pluginName) const
00575     {
00576         (void)parent;
00577         (void)pluginName;
00578         return new MonoScope();
00579     }
00580 }MonoScopeFactory;
00581 
00583 // Spectrum
00584 //
00585 // NOTE: This visualiser requires mythplugins to be compiled with --enable-fft
00586 
00587 #if FFTW3_SUPPORT
00588 Spectrum::Spectrum()
00589     : lin(NULL), rin(NULL), lout(NULL), rout(NULL)
00590 {
00591     // Setup the "magical" audio data transformations
00592     // provided by the Fast Fourier Transforms library
00593     analyzerBarWidth = 6;
00594     scaleFactor = 2.0;
00595     falloff = 10.0;
00596     m_fps = 15;
00597 
00598     lin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N);
00599     memset(lin, 0, sizeof(myth_fftw_float)*FFTW_N);
00600 
00601     rin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N);
00602     memset(rin, 0, sizeof(myth_fftw_float)*FFTW_N);
00603 
00604     lout = (myth_fftw_complex*) av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1));
00605     memset(lout, 0, sizeof(myth_fftw_complex)*(FFTW_N/2+1));
00606 
00607     rout = (myth_fftw_complex*) av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1));
00608     memset(rout, 0, sizeof(myth_fftw_complex)*(FFTW_N/2+1));
00609 
00610     lplan = fftw_plan_dft_r2c_1d(FFTW_N, lin, (myth_fftw_complex_cast*)lout, FFTW_MEASURE);
00611     rplan = fftw_plan_dft_r2c_1d(FFTW_N, rin, (myth_fftw_complex_cast*)rout, FFTW_MEASURE);
00612 
00613     startColor = QColor(0,0,255);
00614     targetColor = QColor(255,0,0);
00615 }
00616 
00617 Spectrum::~Spectrum()
00618 {
00619     if (lin)
00620         av_free(lin);
00621     if (rin)
00622         av_free(rin);
00623     if (lout)
00624         av_free(lout);
00625     if (rout)
00626         av_free(rout);
00627 
00628     fftw_destroy_plan(lplan);
00629     fftw_destroy_plan(rplan);
00630 }
00631 
00632 void Spectrum::resize(const QSize &newsize)
00633 {
00634     // Just change internal data about the
00635     // size of the pixmap to be drawn (ie. the
00636     // size of the screen) and the logically
00637     // ensuing number of up/down bars to hold
00638     // the audio magnitudes
00639 
00640     size = newsize;
00641 
00642     analyzerBarWidth = size.width() / 64;
00643 
00644     if (analyzerBarWidth < 6)
00645         analyzerBarWidth = 6;
00646 
00647     scale.setMax(192, size.width() / analyzerBarWidth);
00648 
00649     rects.resize( scale.range() );
00650     unsigned int i = 0;
00651     int w = 0;
00652     for (; i < (uint)rects.size(); i++, w += analyzerBarWidth)
00653     {
00654         rects[i].setRect(w, size.height() / 2, analyzerBarWidth - 1, 1);
00655     }
00656 
00657     unsigned int os = magnitudes.size();
00658     magnitudes.resize( scale.range() * 2 );
00659     for (; os < (uint)magnitudes.size(); os++)
00660     {
00661         magnitudes[os] = 0.0;
00662     }
00663 
00664     scaleFactor = double( size.height() / 2 ) / log( (double)(FFTW_N) );
00665 }
00666 
00667 template<typename T> T sq(T a) { return a*a; };
00668 
00669 bool Spectrum::process(VisualNode *node)
00670 {
00671     // Take a bunch of data in *node
00672     // and break it down into spectrum
00673     // values
00674     bool allZero = true;
00675 
00676     uint i;
00677     long w = 0, index;
00678     QRect *rectsp = rects.data();
00679     double *magnitudesp = magnitudes.data();
00680     double magL, magR, tmp;
00681 
00682     if (node)
00683     {
00684         i = node->length;
00685         if (i > FFTW_N)
00686             i = FFTW_N;
00687         fast_real_set_from_short(lin, node->left, i);
00688         if (node->right)
00689             fast_real_set_from_short(rin, node->right, i);
00690     }
00691     else
00692         i = 0;
00693 
00694     fast_reals_set(lin + i, rin + i, 0, FFTW_N - i);
00695 
00696     fftw_execute(lplan);
00697     fftw_execute(rplan);
00698 
00699     index = 1;
00700 
00701     for (i = 0; (int)i < rects.size(); i++, w += analyzerBarWidth)
00702     {
00703         magL = (log(sq(real(lout[index])) + sq(real(lout[FFTW_N - index]))) - 22.0) *
00704                scaleFactor;
00705         magR = (log(sq(real(rout[index])) + sq(real(rout[FFTW_N - index]))) - 22.0) *
00706                scaleFactor;
00707 
00708         if (magL > size.height() / 2)
00709         {
00710             magL = size.height() / 2;
00711         }
00712         if (magL < magnitudesp[i])
00713         {
00714             tmp = magnitudesp[i] - falloff;
00715             if ( tmp < magL )
00716             {
00717                 tmp = magL;
00718             }
00719             magL = tmp;
00720         }
00721         if (magL < 1.)
00722         {
00723             magL = 1.;
00724         }
00725 
00726         if (magR > size.height() / 2)
00727         {
00728             magR = size.height() / 2;
00729         }
00730         if (magR < magnitudesp[i + scale.range()])
00731         {
00732             tmp = magnitudesp[i + scale.range()] - falloff;
00733             if ( tmp < magR )
00734             {
00735                 tmp = magR;
00736             }
00737             magR = tmp;
00738         }
00739         if (magR < 1.)
00740         {
00741             magR = 1.;
00742         }
00743 
00744         if (magR != 1 || magL != 1)
00745         {
00746             allZero = false;
00747         }
00748 
00749         magnitudesp[i] = magL;
00750         magnitudesp[i + scale.range()] = magR;
00751         rectsp[i].setTop( size.height() / 2 - int( magL ) );
00752         rectsp[i].setBottom( size.height() / 2 + int( magR ) );
00753 
00754         index = scale[i];
00755     }
00756 
00757     return false;
00758 }
00759 
00760 double Spectrum::clamp(double cur, double max, double min)
00761 {
00762     if (cur > max)
00763         cur = max;
00764     if (cur < min)
00765         cur = min;
00766     return cur;
00767 }
00768 
00769 bool Spectrum::draw(QPainter *p, const QColor &back)
00770 {
00771     // This draws on a pixmap owned by MainVisual.
00772     //
00773     // In other words, this is not a Qt Widget, it
00774     // just uses some Qt methods to draw on a pixmap.
00775     // MainVisual then bitblts that onto the screen.
00776 
00777     QRect *rectsp = rects.data();
00778     double r, g, b, per;
00779 
00780     p->fillRect(0, 0, size.width(), size.height(), back);
00781     for (uint i = 0; i < (uint)rects.size(); i++)
00782     {
00783         per = double( rectsp[i].height() - 2 ) / double( size.height() );
00784 
00785         per = clamp(per, 1.0, 0.0);
00786 
00787         r = startColor.red() +
00788             (targetColor.red() - startColor.red()) * (per * per);
00789         g = startColor.green() +
00790             (targetColor.green() - startColor.green()) * (per * per);
00791         b = startColor.blue() +
00792             (targetColor.blue() - startColor.blue()) * (per * per);
00793 
00794         r = clamp(r, 255.0, 0.0);
00795         g = clamp(g, 255.0, 0.0);
00796         b = clamp(b, 255.0, 0.0);
00797 
00798         if(rectsp[i].height() > 4)
00799             p->fillRect(rectsp[i], QColor(int(r), int(g), int(b)));
00800     }
00801 
00802     return true;
00803 }
00804 
00805 static class SpectrumFactory : public VisFactory
00806 {
00807   public:
00808     const QString &name(void) const
00809     {
00810         static QString name = QCoreApplication::translate("Visualizers",
00811                                                           "Spectrum");
00812         return name;
00813     }
00814 
00815     uint plugins(QStringList *list) const
00816     {
00817         *list << name();
00818         return 1;
00819     }
00820 
00821     VisualBase *create(MainVisual *parent, const QString &pluginName) const
00822     {
00823         (void)parent;
00824         (void)pluginName;
00825         return new Spectrum();
00826     }
00827 }SpectrumFactory;
00828 
00830 // Squares
00831 //
00832 // NOTE: This visualiser requires mythplugins to be compiled with --enable-fftw
00833 
00834 Squares::Squares() :
00835     size(0,0), pParent(NULL), fake_height(0), number_of_squares(16)
00836 {
00837     fake_height = number_of_squares * analyzerBarWidth;
00838 }
00839 
00840 Squares::~Squares()
00841 {
00842 }
00843 
00844 void Squares::resize (const QSize &newsize) {
00845     // Trick the spectrum analyzer into calculating 16 rectangles
00846     Spectrum::resize (QSize (fake_height, fake_height));
00847     // We have our own copy, Spectrum has it's own...
00848     size = newsize;
00849 }
00850 
00851 void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h)
00852 {
00853     double r, g, b, per;
00854     int correction = (size.width() % rects.size ()) / 2;
00855     int x = ((i / 2) * w) + correction;
00856     int y;
00857 
00858     if (i % 2 == 0)
00859     {
00860         y = c - h;
00861         per = double(fake_height - rect->top()) / double(fake_height);
00862     }
00863     else
00864     {
00865         y = c;
00866         per = double(rect->bottom()) / double(fake_height);
00867     }
00868 
00869     per = clamp(per, 1.0, 0.0);
00870 
00871     r = startColor.red() +
00872         (targetColor.red() - startColor.red()) * (per * per);
00873     g = startColor.green() +
00874         (targetColor.green() - startColor.green()) * (per * per);
00875     b = startColor.blue() +
00876         (targetColor.blue() - startColor.blue()) * (per * per);
00877 
00878     r = clamp(r, 255.0, 0.0);
00879     g = clamp(g, 255.0, 0.0);
00880     b = clamp(b, 255.0, 0.0);
00881 
00882     p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
00883 }
00884 
00885 bool Squares::draw(QPainter *p, const QColor &back)
00886 {
00887     p->fillRect (0, 0, size.width (), size.height (), back);
00888     int w = size.width () / (rects.size () / 2);
00889     int h = w;
00890     int center = size.height () / 2;
00891 
00892     QRect *rectsp = rects.data();
00893     for (uint i = 0; i < (uint)rects.size(); i++)
00894         drawRect(p, &(rectsp[i]), i, center, w, h);
00895 
00896     return true;
00897 }
00898 
00899 static class SquaresFactory : public VisFactory
00900 {
00901   public:
00902     const QString &name(void) const
00903     {
00904         static QString name = QCoreApplication::translate("Visualizers",
00905                                                           "Squares");
00906         return name;
00907     }
00908 
00909     uint plugins(QStringList *list) const
00910     {
00911         *list << name();
00912         return 1;
00913     }
00914 
00915     VisualBase *create(MainVisual *parent, const QString &pluginName) const
00916     {
00917         (void)parent;
00918         (void)pluginName;
00919         return new Squares();
00920     }
00921 }SquaresFactory;
00922 
00923 #endif // FFTW3_SUPPORT
00924 
00925 Piano::Piano()
00926     : piano_data(NULL), audio_data(NULL)
00927 {
00928     // Setup the "magical" audio coefficients
00929     // required by the Goetzel Algorithm
00930 
00931     LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Initialised"));
00932 
00933     piano_data = (piano_key_data *) malloc(sizeof(piano_key_data) * PIANO_N);
00934     audio_data = (piano_audio *) malloc(sizeof(piano_audio) * PIANO_AUDIO_SIZE);
00935 
00936     double sample_rate = 44100.0;  // TODO : This should be obtained from gPlayer (likely candidate...)
00937 
00938     m_fps = 20; // This is the display frequency.   We're capturing all audio chunks by defining .process_undisplayed() though.
00939 
00940     double concert_A   =   440.0;
00941     double semi_tone   = pow(2.0, 1.0/12.0);
00942 
00943     /* Lowest note on piano is 4 octaves below concert A */
00944     double bottom_A = concert_A / 2.0 / 2.0 / 2.0 / 2.0;
00945 
00946     unsigned int key;
00947     double current_freq = bottom_A, samples_required;
00948     for (key = 0; key < PIANO_N; key++)
00949     {
00950         // This is constant through time
00951         piano_data[key].coeff = (goertzel_data)(2.0 * cos(2.0 * M_PI * current_freq / sample_rate));
00952 
00953         samples_required = sample_rate/current_freq * 20.0; // Want 20 whole cycles of the current waveform at least
00954         if (samples_required > sample_rate/4.0)
00955         {
00956             // For the really low notes, 4 updates a second is good enough...
00957             samples_required = sample_rate/4.0;
00958         }
00959         if (samples_required < sample_rate/(double)m_fps * 0.75)
00960         {   // For the high notes, use as many samples as we need in a display_fps
00961             samples_required = sample_rate/(double)m_fps * 0.75;
00962         }
00963         piano_data[key].samples_process_before_display_update = (int)samples_required;
00964         piano_data[key].is_black_note = false; // Will be put right in .resize()
00965 
00966         current_freq *= semi_tone;
00967     }
00968 
00969     zero_analysis();
00970 
00971     whiteStartColor = QColor(245,245,245);
00972     whiteTargetColor = Qt::red;
00973 
00974     blackStartColor = QColor(10,10,10);
00975     blackTargetColor = Qt::red;
00976 }
00977 
00978 Piano::~Piano()
00979 {
00980     if (piano_data)
00981         free(piano_data);
00982     if (audio_data)
00983         free(audio_data);
00984 }
00985 
00986 void Piano::zero_analysis(void)
00987 {
00988     unsigned int key;
00989     for (key = 0; key < PIANO_N; key++)
00990     {
00991         // These get updated continously, and must be stored between chunks of audio data
00992         piano_data[key].q2 = (goertzel_data)0.0f;
00993         piano_data[key].q1 = (goertzel_data)0.0f;
00994         piano_data[key].magnitude = (goertzel_data)0.0f;
00995         piano_data[key].max_magnitude_seen =
00996             (goertzel_data)(PIANO_RMS_NEGLIGIBLE*PIANO_RMS_NEGLIGIBLE); // This is a guess - will be quickly overwritten
00997 
00998         piano_data[key].samples_processed = 0;
00999     }
01000     offset_processed = 0;
01001 
01002     return;
01003 }
01004 
01005 void Piano::resize(const QSize &newsize)
01006 {
01007     // Just change internal data about the
01008     // size of the pixmap to be drawn (ie. the
01009     // size of the screen) and the logically
01010     // ensuing number of up/down bars to hold
01011     // the audio magnitudes
01012 
01013     size = newsize;
01014 
01015     LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Resized"));
01016 
01017     zero_analysis();
01018 
01019     // There are 88-36=52 white notes on piano keyboard
01020     double key_unit_size = (double)size.width() / 54.0;  // One white key extra spacing, if possible
01021     if (key_unit_size < 10.0) // Keys have to be at least this many pixels wide
01022         key_unit_size = 10.0;
01023 
01024     double white_width_pct = .8;
01025     double black_width_pct = .6;
01026     double black_offset_pct = .05;
01027 
01028     double white_height_pct = 6;
01029     double black_height_pct = 4;
01030 
01031     // This is the starting position of the keyboard (may be beyond LHS)
01032     // - actually position of C below bottom A (will be added to...).  This is 4 octaves below middle C.
01033     double left =  (double)size.width() / 2.0 - (4.0*7.0 + 3.5) * key_unit_size; // The extra 3.5 centers 'F' inthe middle of the screen
01034     double top_of_keys = (double)size.height() / 2.0 - key_unit_size * white_height_pct / 2.0; // Vertically center keys
01035 
01036     double width, height, center, offset;
01037 
01038     rects.resize(PIANO_N);
01039 
01040     unsigned int key;
01041     int note;
01042     bool is_black = false;
01043     for (key = 0; key < PIANO_N; key++)
01044     {
01045         note = ((int)key - 3 + 12) % 12;  // This means that C=0, C#=1, D=2, etc (since lowest note is bottom A)
01046         if (note == 0) // If we're on a 'C', move the left 'cursor' over an octave
01047         {
01048             left += key_unit_size*7.0;
01049         }
01050 
01051         center = 0.0;
01052         offset = 0.0;
01053         is_black = false;
01054 
01055         switch (note)
01056         {
01057             case 0:  center = 0.5; break;
01058             case 1:  center = 1.0; is_black = true; offset = -1; break;
01059             case 2:  center = 1.5; break;
01060             case 3:  center = 2.0; is_black = true; offset = +1; break;
01061             case 4:  center = 2.5; break;
01062             case 5:  center = 3.5; break;
01063             case 6:  center = 4.0; is_black = true; offset = -2; break;
01064             case 7:  center = 4.5; break;
01065             case 8:  center = 5.0; is_black = true; offset = 0; break;
01066             case 9:  center = 5.5; break;
01067             case 10: center = 6.0; is_black = true; offset = 2; break;
01068             case 11: center = 6.5; break;
01069         }
01070         piano_data[key].is_black_note = is_black;
01071 
01072         width = (is_black ? black_width_pct:white_width_pct) * key_unit_size;
01073         height = (is_black? black_height_pct:white_height_pct) * key_unit_size;
01074 
01075         rects[key].setRect(
01076             left + center * key_unit_size //  Basic position of left side of key
01077                 - width / 2.0  // Less half the width
01078                 + (is_black ? (offset * black_offset_pct * key_unit_size):0.0), // And jiggle the positions of the black keys for aethetic reasons
01079             top_of_keys, // top
01080             width, // width
01081             height // height
01082         );
01083     }
01084 
01085     magnitude.resize(PIANO_N);
01086     for (key = 0; key < (uint)magnitude.size(); key++)
01087     {
01088         magnitude[key] = 0.0;
01089     }
01090 
01091     return;
01092 }
01093 
01094 unsigned long Piano::getDesiredSamples(void)
01095 {
01096     // We want all the data! (within reason)
01097     //   typical observed values are 882 -
01098     //   12.5 chunks of data per second from 44100Hz signal : Sampled at 50Hz, lots of 4, see :
01099     //   mythtv/libs/libmyth/audio/audiooutputbase.cpp :: AudioOutputBase::AddData
01100     //   See : mythtv/mythplugins/mythmusic/mythmusic/avfdecoder.cpp "20ms worth"
01101     return (unsigned long) PIANO_AUDIO_SIZE;  // Maximum we can be given
01102 }
01103 
01104 bool Piano::processUndisplayed(VisualNode *node)
01105 {
01106     //LOG(VB_GENERAL, LOG_INFO, QString("Piano : Processing undisplayed node"));
01107     return process_all_types(node, false);
01108 }
01109 
01110 bool Piano::process(VisualNode *node)
01111 {
01112     //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node for DISPLAY"));
01113 //    return process_all_types(node, true);
01114     process_all_types(node, true);
01115     return false;
01116 }
01117 
01118 bool Piano::process_all_types(VisualNode *node, bool this_will_be_displayed)
01119 {
01120     (void) this_will_be_displayed;
01121 
01122     // Take a bunch of data in *node and break it down into piano key spectrum values
01123     // NB: Remember the state data between calls, so as to accumulate more accurate results.
01124     bool allZero = true;
01125 
01126     uint i, n, key;
01127 
01128     goertzel_data q0, q1, q2, coeff;
01129     goertzel_data magnitude2, magnitude_av;
01130     piano_audio short_to_bounded = 32768.0f;
01131     int n_samples;
01132 
01133     if (node)
01134     {
01135         // Detect start of new song (current node more than 10s earlier than already seen)
01136         if (node->offset + 10000 < offset_processed)
01137         {
01138             LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Node offset=%1 too far backwards : NEW SONG").arg(node->offset));
01139             zero_analysis();
01140         }
01141 
01142         // Check whether we've seen this node (more recently than 10secs ago)
01143         if (node->offset <= offset_processed)
01144         {
01145             LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Already seen node offset=%1, returning without processing").arg(node->offset));
01146             return allZero; // Nothing to see here - the server can stop if it wants to
01147         }
01148     }
01149 
01150     if (node)
01151     {
01152         //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node offset=%1, size=%2").arg(node->offset).arg(node->length));
01153         n = node->length;
01154 
01155         if (node->right) // Preprocess the data into a combined middle channel, if we have stereo data
01156         {
01157             for (i = 0; i < n; i++)
01158             {
01159                 audio_data[i] = (piano_audio)(((piano_audio)node->left[i] + (piano_audio)node->right[i]) / 2.0 / short_to_bounded);
01160             }
01161         }
01162         else // This is only one channel of data
01163         {
01164             for (i = 0; i < n; i++)
01165             {
01166                 audio_data[i] = (piano_audio)node->left[i] / short_to_bounded;
01167             }
01168         }
01169     }
01170     else
01171     {
01172         LOG(VB_GENERAL, LOG_DEBUG, QString("Hit an empty node, and returning empty-handed"));
01173         return allZero; // Nothing to see here - the server can stop if it wants to
01174     }
01175 
01176     for (key = 0; key < PIANO_N; key++)
01177     {
01178         coeff = piano_data[key].coeff;
01179 
01180         q2 = piano_data[key].q2;
01181         q1 = piano_data[key].q1;
01182 
01183         for (i = 0; i < n; i++)
01184         {
01185             q0 = coeff * q1 - q2 + audio_data[i];
01186             q2 = q1;
01187             q1 = q0;
01188         }
01189         piano_data[key].q2 = q2;
01190         piano_data[key].q1 = q1;
01191 
01192         piano_data[key].samples_processed += n;
01193 
01194         n_samples = piano_data[key].samples_processed;
01195 
01196         // Only do this update if we've processed enough chunks for this key...
01197         if (n_samples > piano_data[key].samples_process_before_display_update)
01198         {
01199             magnitude2 = q1*q1 + q2*q2 - q1*q2*coeff;
01200 
01201             if (false) // This is RMS of signal
01202             {
01203                 magnitude_av = sqrt(magnitude2)/(goertzel_data)n_samples; // Should be 0<magnitude_av<.5
01204             }
01205             if (true) // This is pure magnitude of signal
01206             {
01207                 magnitude_av = magnitude2/(goertzel_data)n_samples/(goertzel_data)n_samples; // Should be 0<magnitude_av<.25
01208             }
01209 
01210             if (false) // Take logs everywhere, and shift up to [0, ??]
01211             {
01212                 if(magnitude_av > 0.0)
01213                 {
01214                     magnitude_av = log(magnitude_av);
01215                 }
01216                 else
01217                 {
01218                     magnitude_av = PIANO_MIN_VOL;
01219                 }
01220                 magnitude_av -= PIANO_MIN_VOL;
01221 
01222                 if (magnitude_av < 0.0)
01223                 {
01224                     magnitude_av = 0.0;
01225                 }
01226             }
01227 
01228             if (magnitude_av > (goertzel_data)0.01)
01229             {
01230                 allZero = false;
01231             }
01232 
01233             piano_data[key].magnitude = magnitude_av; // Store this for later : We'll do the colours from this...
01234             if ( piano_data[key].max_magnitude_seen < magnitude_av)
01235             {
01236                 piano_data[key].max_magnitude_seen = magnitude_av;
01237             }
01238             LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Updated Key %1 from %2 samples, magnitude=%3")
01239                     .arg(key).arg(n_samples).arg(magnitude_av));
01240 
01241             piano_data[key].samples_processed = 0; // Reset the counts, now that we've set the magnitude...
01242             piano_data[key].q1 = (goertzel_data)0.0;
01243             piano_data[key].q2 = (goertzel_data)0.0;
01244         }
01245     }
01246 
01247     // All done now - record that we've done this offset
01248     offset_processed = node->offset;
01249     return allZero;
01250 }
01251 
01252 double Piano::clamp(double cur, double max, double min)
01253 {
01254     if (cur > max)
01255         cur = max;
01256     if (cur < min)
01257         cur = min;
01258     return cur;
01259 }
01260 
01261 bool Piano::draw(QPainter *p, const QColor &back)
01262 {
01263     // This draws on a pixmap owned by MainVisual.
01264     //
01265     // In other words, this is not a Qt Widget, it
01266     // just uses some Qt methods to draw on a pixmap.
01267     // MainVisual then bitblts that onto the screen.
01268 
01269     QRect *rectsp = &rects[0];
01270     double *magnitudep = &magnitude[0];
01271 
01272     unsigned int key, n = PIANO_N;
01273     double r, g, b, per;
01274 
01275     p->fillRect(0, 0, size.width(), size.height(), back);
01276 
01277     // Protect maximum array length
01278     if(n > (uint)rects.size())
01279         n = (uint)rects.size();
01280 
01281     // Sweep up across the keys, making sure the max_magnitude_seen is at minimum X% of its neighbours
01282     double mag = PIANO_RMS_NEGLIGIBLE;
01283     for (key = 0; key < n; key++)
01284     {
01285         if (piano_data[key].max_magnitude_seen < mag)
01286         {
01287             // Spread the previous value to this key
01288             piano_data[key].max_magnitude_seen = mag;
01289         }
01290         else
01291         {
01292             // This key has seen better peaks, use this for the next one
01293             mag = piano_data[key].max_magnitude_seen;
01294         }
01295         mag *= PIANO_SPECTRUM_SMOOTHING;
01296     }
01297 
01298     // Similarly, down, making sure the max_magnitude_seen is at minimum X% of its neighbours
01299     mag = PIANO_RMS_NEGLIGIBLE;
01300     for (int key_i = n - 1; key_i >= 0; key_i--)
01301     {
01302         key = key_i; // Wow, this is to avoid a zany error for ((unsigned)0)--
01303         if (piano_data[key].max_magnitude_seen < mag)
01304         {
01305             // Spread the previous value to this key
01306             piano_data[key].max_magnitude_seen = mag;
01307         }
01308         else
01309         {
01310             // This key has seen better peaks, use this for the next one
01311             mag = piano_data[key].max_magnitude_seen;
01312         }
01313         mag *= PIANO_SPECTRUM_SMOOTHING;
01314     }
01315 
01316     // Now find the key that has been hit the hardest relative to its experience, and renormalize...
01317     // Set a minimum, to prevent divide-by-zero (and also all-pressed when music very quiet)
01318     double magnitude_max = PIANO_RMS_NEGLIGIBLE;
01319     for (key = 0; key < n; key++)
01320     {
01321         mag = piano_data[key].magnitude / piano_data[key].max_magnitude_seen;
01322         if (magnitude_max < mag)
01323             magnitude_max = mag;
01324 
01325         magnitudep[key] = mag;
01326     }
01327 
01328     // Deal with all the white keys first
01329     for (key = 0; key < n; key++)
01330     {
01331         if (piano_data[key].is_black_note)
01332             continue;
01333 
01334         per = magnitudep[key] / magnitude_max;
01335         per = clamp(per, 1.0, 0.0);        // By construction, this should be unnecessary
01336 
01337         if (per < PIANO_KEYPRESS_TOO_LIGHT)
01338             per = 0.0; // Clamp to zero for lightly detected keys
01339         LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Display key %1, magnitude=%2, seen=%3")
01340                 .arg(key).arg(per*100.0).arg(piano_data[key].max_magnitude_seen));
01341 
01342         r = whiteStartColor.red() + (whiteTargetColor.red() - whiteStartColor.red()) * per;
01343         g = whiteStartColor.green() + (whiteTargetColor.green() - whiteStartColor.green()) * per;
01344         b = whiteStartColor.blue() + (whiteTargetColor.blue() - whiteStartColor.blue()) * per;
01345 
01346         p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
01347     }
01348 
01349     // Then overlay the black keys
01350     for (key = 0; key < n; key++)
01351     {
01352         if (!piano_data[key].is_black_note)
01353             continue;
01354 
01355         per = magnitudep[key]/magnitude_max;
01356         per = clamp(per, 1.0, 0.0);        // By construction, this should be unnecessary
01357 
01358         if (per < PIANO_KEYPRESS_TOO_LIGHT)
01359             per = 0.0; // Clamp to zero for lightly detected keys
01360 
01361         r = blackStartColor.red() + (blackTargetColor.red() - blackStartColor.red()) * per;
01362         g = blackStartColor.green() + (blackTargetColor.green() - blackStartColor.green()) * per;
01363         b = blackStartColor.blue() + (blackTargetColor.blue() - blackStartColor.blue()) * per;
01364 
01365         p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
01366     }
01367 
01368     return true;
01369 }
01370 
01371 static class PianoFactory : public VisFactory
01372 {
01373   public:
01374     const QString &name(void) const
01375     {
01376         static QString name = QCoreApplication::translate("Visualizers",
01377                                                           "Piano");
01378         return name;
01379     }
01380 
01381     uint plugins(QStringList *list) const
01382     {
01383         *list << name();
01384         return 1;
01385     }
01386 
01387     VisualBase *create(MainVisual *parent, const QString &pluginName) const
01388     {
01389         (void)parent;
01390         (void)pluginName;
01391         return new Piano();
01392     }
01393 }PianoFactory;
01394 
01395 AlbumArt::AlbumArt(void) :
01396     m_currentMetadata(NULL),
01397     m_lastCycle(QDateTime::currentDateTime())
01398 {
01399     findFrontCover();
01400     m_fps = 1;
01401 }
01402 
01403 void AlbumArt::findFrontCover(void)
01404 {
01405     if (!gPlayer->getCurrentMetadata())
01406         return;
01407 
01408     // if a front cover image is available show that first
01409     AlbumArtImages *albumArt = gPlayer->getCurrentMetadata()->getAlbumArtImages();
01410     if (albumArt->getImage(IT_FRONTCOVER))
01411         m_currImageType = IT_FRONTCOVER;
01412     else
01413     {
01414         // not available so just show the first image available
01415         if (albumArt->getImageCount() > 0)
01416             m_currImageType = albumArt->getImageAt(0)->imageType;
01417         else
01418             m_currImageType = IT_UNKNOWN;
01419     }
01420 }
01421 
01422 bool AlbumArt::cycleImage(void)
01423 {
01424     if (!gPlayer->getCurrentMetadata())
01425         return false;
01426 
01427     AlbumArtImages *albumArt = gPlayer->getCurrentMetadata()->getAlbumArtImages();
01428     int newType = m_currImageType;
01429 
01430     if (albumArt->getImageCount() > 0)
01431     {
01432         do
01433         {
01434             newType++;
01435             if (newType == IT_LAST)
01436                 newType = IT_UNKNOWN;
01437         } while (!albumArt->getImage((ImageType) newType));
01438     }
01439 
01440     if (newType != m_currImageType)
01441     {
01442         m_currImageType = (ImageType) newType;
01443         m_lastCycle = QDateTime::currentDateTime();
01444         return true;
01445     }
01446 
01447     return false;
01448 }
01449 
01450 AlbumArt::~AlbumArt()
01451 {
01452 }
01453 
01454 void AlbumArt::resize(const QSize &newsize)
01455 {
01456     m_size = newsize;
01457 }
01458 
01459 bool AlbumArt::process(VisualNode *node)
01460 {
01461     (void) node;
01462     return false;
01463 }
01464 
01465 void AlbumArt::handleKeyPress(const QString &action)
01466 {
01467     if (action == "SELECT")
01468     {
01469         if (gPlayer->getCurrentMetadata())
01470         {
01471             AlbumArtImages albumArt(gPlayer->getCurrentMetadata());
01472             int newType = m_currImageType;
01473 
01474             if (albumArt.getImageCount() > 0)
01475             {
01476                 newType++;
01477 
01478                 while (!albumArt.getImage((ImageType) newType))
01479                 {
01480                     newType++;
01481                     if (newType == IT_LAST)
01482                         newType = IT_UNKNOWN;
01483                 }
01484             }
01485 
01486             if (newType != m_currImageType)
01487             {
01488                 m_currImageType = (ImageType) newType;
01489                 // force an update
01490                 m_cursize = QSize(0, 0);
01491             }
01492         }
01493     }
01494 }
01495 
01497 #define ALBUMARTCYCLETIME 10
01498 
01499 bool AlbumArt::needsUpdate()
01500 {
01501     // if the track has changed we need to update the image
01502     if (gPlayer->getCurrentMetadata() && m_currentMetadata != gPlayer->getCurrentMetadata())
01503     {
01504         m_currentMetadata = gPlayer->getCurrentMetadata();
01505         findFrontCover();
01506         return true;
01507     }
01508 
01509     // if it's time to cycle to the next image we need to update the image
01510     if (m_lastCycle.addSecs(ALBUMARTCYCLETIME) < QDateTime::currentDateTime())
01511     {
01512         if (cycleImage())
01513             return true;
01514     }
01515 
01516     return false;
01517 }
01518 
01519 bool AlbumArt::draw(QPainter *p, const QColor &back)
01520 {
01521     if (needsUpdate())
01522     {
01523         QImage art;
01524         QString imageFilename = gPlayer->getCurrentMetadata()->getAlbumArtFile(m_currImageType);
01525         if (!imageFilename.isEmpty())
01526             art.load(imageFilename);
01527 
01528         if (art.isNull())
01529         {
01530             m_cursize = m_size;
01531             m_image = QImage();
01532         }
01533         else
01534         {
01535             m_image = art.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
01536         }
01537     }
01538 
01539     if (m_image.isNull())
01540     {
01541         drawWarning(p, back, m_size, QObject::tr("?"), 100);
01542         return true;
01543     }
01544 
01545     // Paint the image
01546     p->fillRect(0, 0, m_size.width(), m_size.height(), back);
01547     p->drawImage((m_size.width() - m_image.width()) / 2,
01548                  (m_size.height() - m_image.height()) / 2,
01549                  m_image);
01550 
01551     // Store our new size
01552     m_cursize = m_size;
01553 
01554     return true;
01555 }
01556 
01557 static class AlbumArtFactory : public VisFactory
01558 {
01559   public:
01560     const QString &name(void) const
01561     {
01562         static QString name = QCoreApplication::translate("Visualizers",
01563                                                           "AlbumArt");
01564         return name;
01565     }
01566 
01567     uint plugins(QStringList *list) const
01568     {
01569         *list << name();
01570         return 1;
01571     }
01572 
01573     VisualBase *create(MainVisual *parent, const QString &pluginName) const
01574     {
01575         (void) parent;
01576         (void)pluginName;
01577         return new AlbumArt();
01578     }
01579 }AlbumArtFactory;
01580 
01581 Blank::Blank()
01582     : VisualBase(true)
01583 {
01584     m_fps = 1;
01585 }
01586 
01587 Blank::~Blank()
01588 {
01589 }
01590 
01591 void Blank::resize(const QSize &newsize)
01592 {
01593     size = newsize;
01594 }
01595 
01596 
01597 bool Blank::process(VisualNode *node)
01598 {
01599     node = node; // Sometimes I hate -Wall
01600     return false;
01601 }
01602 
01603 bool Blank::draw(QPainter *p, const QColor &back)
01604 {
01605     // Took me hours to work out this algorithm
01606     p->fillRect(0, 0, size.width(), size.height(), back);
01607     return true;
01608 }
01609 
01610 static class BlankFactory : public VisFactory
01611 {
01612   public:
01613     const QString &name(void) const
01614     {
01615         static QString name = QCoreApplication::translate("Visualizers",
01616                                                           "Blank");
01617         return name;
01618     }
01619 
01620     uint plugins(QStringList *list) const
01621     {
01622         *list << name();
01623         return 1;
01624     }
01625 
01626     VisualBase *create(MainVisual *parent, const QString &pluginName) const
01627     {
01628         (void)parent;
01629         (void)pluginName;
01630         return new Blank();
01631     }
01632 }BlankFactory;
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends