|
MythTV
0.26-pre
|
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;
1.7.6.1