MythTV  master
visualize.cpp
Go to the documentation of this file.
1 /*
2  visualize.cpp
3 
4  (c) 2003 Thor Sigvaldason and Isaac Richards
5  VERY closely based on code from mq3 by Brad Hughes
6 
7  Part of the mythTV project
8 
9  music visualizers
10 */
11 
12 // C
13 #include <cmath>
14 
15 // C++
16 #include <iostream>
17 using namespace std;
18 
19 // Qt
20 #include <QCoreApplication>
21 #include <QPainter>
22 #include <QImage>
23 
24 // MythTV
25 #include <mythdbcon.h>
26 #include <mythcontext.h>
27 #include <mythuihelper.h>
28 #include <remotefile.h>
29 #include <musicmetadata.h>
30 
31 // mythmusic
32 #include "mainvisual.h"
33 #include "visualize.h"
34 #include "inlines.h"
35 #include "decoder.h"
36 #include "musicplayer.h"
37 
38 #define FFTW_N 512
39 // static_assert(FFTW_N==SAMPLES_DEFAULT_SIZE)
40 
41 
43 
44 VisualBase::VisualBase(bool screensaverenable)
45  : m_xscreensaverenable(screensaverenable)
46 {
49 }
50 
52 {
53  //
54  // This is only here so
55  // that derived classes
56  // can destruct properly
57  //
60 }
61 
62 
63 void VisualBase::drawWarning(QPainter *p, const QColor &back, const QSize &size, const QString& warning, int fontSize)
64 {
65  p->fillRect(0, 0, size.width(), size.height(), back);
66  p->setPen(Qt::white);
67  QFont font = MythUIHelper::GetMediumFont();
68  font.setPointSizeF(fontSize * (size.width() / 800.0));
69  p->setFont(font);
70 
71  p->drawText(0, 0, size.width(), size.height(), Qt::AlignVCenter | Qt::AlignHCenter | Qt::TextWordWrap, warning);
72 }
73 
75 // LogScale
76 
77 LogScale::LogScale(int maxscale, int maxrange)
78 {
79  setMax(maxscale, maxrange);
80 }
81 
83 {
84  delete [] m_indices;
85 }
86 
87 void LogScale::setMax(int maxscale, int maxrange)
88 {
89  if (maxscale == 0 || maxrange == 0)
90  return;
91 
92  m_s = maxscale;
93  m_r = maxrange;
94 
95  delete [] m_indices;
96 
97  auto domain = (long double) maxscale;
98  auto range = (long double) maxrange;
99  long double x = 1.0;
100  long double dx = 1.0;
101  long double e4 = 1.0E-8;
102 
103  m_indices = new int[maxrange];
104  for (int i = 0; i < maxrange; i++)
105  m_indices[i] = 0;
106 
107  // initialize log scale
108  for (uint i=0; i<10000 && (std::abs(dx) > e4); i++)
109  {
110  double t = std::log((domain + x) / x);
111  double y = (x * t) - range;
112  double yy = t - (domain / (x + domain));
113  dx = y / yy;
114  x -= dx;
115  }
116 
117  double alpha = x;
118  for (int i = 1; i < (int) domain; i++)
119  {
120  int scaled = (int) floor(0.5 + (alpha * log((double(i) + alpha) / alpha)));
121  if (scaled < 1)
122  scaled = 1;
123  if (m_indices[scaled - 1] < i)
124  m_indices[scaled - 1] = i;
125  }
126 }
127 
128 int LogScale::operator[](int index)
129 {
130  return m_indices[index];
131 }
132 
134 // StereoScope
135 
137 {
138  m_fps = 45;
139 }
140 
141 void StereoScope::resize( const QSize &newsize )
142 {
143  m_size = newsize;
144 
145  auto os = m_magnitudes.size();
146  m_magnitudes.resize( m_size.width() * 2 );
147  for ( ; os < m_magnitudes.size(); os++ )
148  m_magnitudes[os] = 0.0;
149 }
150 
152 {
153  bool allZero = true;
154 
155 
156  if (node)
157  {
158  double index = 0;
159  double const step = (double)SAMPLES_DEFAULT_SIZE / m_size.width();
160  for ( int i = 0; i < m_size.width(); i++)
161  {
162  auto indexTo = (unsigned long)(index + step);
163  if (indexTo == (unsigned long)(index))
164  indexTo = (unsigned long)(index + 1);
165 
166  double valL = 0;
167  double valR = 0;
168 #if RUBBERBAND
169  if ( m_rubberband ) {
170  valL = m_magnitudes[ i ];
171  valR = m_magnitudes[ i + m_size.width() ];
172  if (valL < 0.) {
173  valL += m_falloff;
174  if ( valL > 0. )
175  valL = 0.;
176  }
177  else
178  {
179  valL -= m_falloff;
180  if ( valL < 0. )
181  valL = 0.;
182  }
183  if (valR < 0.)
184  {
185  valR += m_falloff;
186  if ( valR > 0. )
187  valR = 0.;
188  }
189  else
190  {
191  valR -= m_falloff;
192  if ( valR < 0. )
193  valR = 0.;
194  }
195  }
196 #endif
197  for (auto s = (unsigned long)index; s < indexTo && s < node->m_length; s++)
198  {
199  double adjHeight = static_cast<double>(m_size.height()) / 4.0;
200  double tmpL = ( ( node->m_left ? static_cast<double>(node->m_left[s]) : 0.) *
201  adjHeight ) / 32768.0;
202  double tmpR = ( ( node->m_right ? static_cast<double>(node->m_right[s]) : 0.) *
203  adjHeight ) / 32768.0;
204  if (tmpL > 0)
205  valL = (tmpL > valL) ? tmpL : valL;
206  else
207  valL = (tmpL < valL) ? tmpL : valL;
208  if (tmpR > 0)
209  valR = (tmpR > valR) ? tmpR : valR;
210  else
211  valR = (tmpR < valR) ? tmpR : valR;
212  }
213 
214  if (valL != 0. || valR != 0.)
215  allZero = false;
216 
217  m_magnitudes[ i ] = valL;
218  m_magnitudes[ i + m_size.width() ] = valR;
219 
220  index = index + step;
221  }
222 #if RUBBERBAND
223  }
224  else if (m_rubberband)
225  {
226  for ( int i = 0; i < m_size.width(); i++)
227  {
228  double valL = m_magnitudes[ i ];
229  if (valL < 0) {
230  valL += 2;
231  if (valL > 0.)
232  valL = 0.;
233  } else {
234  valL -= 2;
235  if (valL < 0.)
236  valL = 0.;
237  }
238 
239  double valR = m_magnitudes[ i + m_size.width() ];
240  if (valR < 0.) {
241  valR += m_falloff;
242  if (valR > 0.)
243  valR = 0.;
244  }
245  else
246  {
247  valR -= m_falloff;
248  if (valR < 0.)
249  valR = 0.;
250  }
251 
252  if (valL != 0. || valR != 0.)
253  allZero = false;
254 
255  m_magnitudes[ i ] = valL;
256  m_magnitudes[ i + m_size.width() ] = valR;
257  }
258 #endif
259  }
260  else
261  {
262  for ( int i = 0; (unsigned) i < m_magnitudes.size(); i++ )
263  m_magnitudes[ i ] = 0.;
264  }
265 
266  return allZero;
267 }
268 
269 bool StereoScope::draw( QPainter *p, const QColor &back )
270 {
271  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
272  for ( int i = 1; i < m_size.width(); i++ )
273  {
274 #if TWOCOLOUR
275  double r, g, b, per;
276 
277  // left
278  per = ( static_cast<double>(m_magnitudes[i]) * 2.0 ) /
279  ( static_cast<double>(m_size.height()) / 4.0 );
280  if (per < 0.0)
281  per = -per;
282  if (per > 1.0)
283  per = 1.0;
284  else if (per < 0.0)
285  per = 0.0;
286 
287  r = m_startColor.red() + (m_targetColor.red() -
288  m_startColor.red()) * (per * per);
289  g = m_startColor.green() + (m_targetColor.green() -
290  m_startColor.green()) * (per * per);
291  b = m_startColor.blue() + (m_targetColor.blue() -
292  m_startColor.blue()) * (per * per);
293 
294  if (r > 255.0)
295  r = 255.0;
296  else if (r < 0.0)
297  r = 0;
298 
299  if (g > 255.0)
300  g = 255.0;
301  else if (g < 0.0)
302  g = 0;
303 
304  if (b > 255.0)
305  b = 255.0;
306  else if (b < 0.0)
307  b = 0;
308 
309  p->setPen( QColor( int(r), int(g), int(b) ) );
310 #else
311  p->setPen(Qt::red);
312 #endif
313  double adjHeight = static_cast<double>(m_size.height()) / 4.0;
314  p->drawLine( i - 1,
315  (int)(adjHeight + m_magnitudes[i - 1]),
316  i,
317  (int)(adjHeight + m_magnitudes[i]));
318 
319 #if TWOCOLOUR
320  // right
321  per = ( static_cast<double>(m_magnitudes[ i + m_size.width() ]) * 2 ) /
322  adjHeight;
323  if (per < 0.0)
324  per = -per;
325  if (per > 1.0)
326  per = 1.0;
327  else if (per < 0.0)
328  per = 0.0;
329 
330  r = m_startColor.red() + (m_targetColor.red() -
331  m_startColor.red()) * (per * per);
332  g = m_startColor.green() + (m_targetColor.green() -
333  m_startColor.green()) * (per * per);
334  b = m_startColor.blue() + (m_targetColor.blue() -
335  m_startColor.blue()) * (per * per);
336 
337  if (r > 255.0)
338  r = 255.0;
339  else if (r < 0.0)
340  r = 0;
341 
342  if (g > 255.0)
343  g = 255.0;
344  else if (g < 0.0)
345  g = 0;
346 
347  if (b > 255.0)
348  b = 255.0;
349  else if (b < 0.0)
350  b = 0;
351 
352  p->setPen( QColor( int(r), int(g), int(b) ) );
353 #else
354  p->setPen(Qt::red);
355 #endif
356  adjHeight = static_cast<double>(m_size.height()) * 3.0 / 4.0;
357  p->drawLine( i - 1,
358  (int)(adjHeight + m_magnitudes[i + m_size.width() - 1]),
359  i,
360  (int)(adjHeight + m_magnitudes[i + m_size.width()]));
361  }
362 
363  return true;
364 }
365 
367 // MonoScope
368 
370 {
371  bool allZero = true;
372 
373  if (node)
374  {
375  double index = 0;
376  double const step = (double)SAMPLES_DEFAULT_SIZE / m_size.width();
377  for (int i = 0; i < m_size.width(); i++)
378  {
379  auto indexTo = (unsigned long)(index + step);
380  if (indexTo == (unsigned long)index)
381  indexTo = (unsigned long)(index + 1);
382 
383  double val = 0;
384 #if RUBBERBAND
385  if ( m_rubberband )
386  {
387  val = m_magnitudes[ i ];
388  if (val < 0.)
389  {
390  val += m_falloff;
391  if ( val > 0. )
392  {
393  val = 0.;
394  }
395  }
396  else
397  {
398  val -= m_falloff;
399  if ( val < 0. )
400  {
401  val = 0.;
402  }
403  }
404  }
405 #endif
406  for (auto s = (unsigned long)index; s < indexTo && s < node->m_length; s++)
407  {
408  double tmp = ( static_cast<double>(node->m_left[s]) +
409  (node->m_right ? static_cast<double>(node->m_right[s]) : 0.0) *
410  ( static_cast<double>(m_size.height()) / 2.0 ) ) / 65536.0;
411  if (tmp > 0)
412  {
413  val = (tmp > val) ? tmp : val;
414  }
415  else
416  {
417  val = (tmp < val) ? tmp : val;
418  }
419  }
420 
421  if ( val != 0. )
422  {
423  allZero = false;
424  }
425  m_magnitudes[ i ] = val;
426  index = index + step;
427  }
428  }
429 #if RUBBERBAND
430  else if (m_rubberband)
431  {
432  for (int i = 0; i < m_size.width(); i++) {
433  double val = m_magnitudes[ i ];
434  if (val < 0) {
435  val += 2;
436  if (val > 0.)
437  val = 0.;
438  } else {
439  val -= 2;
440  if (val < 0.)
441  val = 0.;
442  }
443 
444  if ( val != 0. )
445  allZero = false;
446  m_magnitudes[ i ] = val;
447  }
448  }
449 #endif
450  else
451  {
452  for (int i = 0; i < m_size.width(); i++ )
453  m_magnitudes[ i ] = 0.;
454  }
455 
456  return allZero;
457 }
458 
459 bool MonoScope::draw( QPainter *p, const QColor &back )
460 {
461  p->fillRect( 0, 0, m_size.width(), m_size.height(), back );
462  for ( int i = 1; i < m_size.width(); i++ ) {
463 #if TWOCOLOUR
464  double r, g, b, per;
465 
466  per = double( m_magnitudes[ i ] ) /
467  double( m_size.height() / 4 );
468  if (per < 0.0)
469  per = -per;
470  if (per > 1.0)
471  per = 1.0;
472  else if (per < 0.0)
473  per = 0.0;
474 
475  r = m_startColor.red() + (m_targetColor.red() -
476  m_startColor.red()) * (per * per);
477  g = m_startColor.green() + (m_targetColor.green() -
478  m_startColor.green()) * (per * per);
479  b = m_startColor.blue() + (m_targetColor.blue() -
480  m_startColor.blue()) * (per * per);
481 
482  if (r > 255.0)
483  r = 255.0;
484  else if (r < 0.0)
485  r = 0;
486 
487  if (g > 255.0)
488  g = 255.0;
489  else if (g < 0.0)
490  g = 0;
491 
492  if (b > 255.0)
493  b = 255.0;
494  else if (b < 0.0)
495  b = 0;
496 
497  p->setPen(QColor(int(r), int(g), int(b)));
498 #else
499  p->setPen(Qt::red);
500 #endif
501  double adjHeight = static_cast<double>(m_size.height()) / 2.0;
502  p->drawLine( i - 1,
503  (int)(adjHeight + m_magnitudes[ i - 1 ]),
504  i,
505  (int)(adjHeight + m_magnitudes[ i ] ));
506  }
507 
508  return true;
509 }
510 
512 // StereoScopeFactory
513 
514 static class StereoScopeFactory : public VisFactory
515 {
516  public:
517  const QString &name(void) const override // VisFactory
518  {
519  static QString s_name = QCoreApplication::translate("Visualizers",
520  "StereoScope");
521  return s_name;
522  }
523 
524  uint plugins(QStringList *list) const override // VisFactory
525  {
526  *list << name();
527  return 1;
528  }
529 
530  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
531  {
532  return new StereoScope();
533  }
535 
536 
538 // MonoScopeFactory
539 
540 static class MonoScopeFactory : public VisFactory
541 {
542  public:
543  const QString &name(void) const override // VisFactory
544  {
545  static QString s_name = QCoreApplication::translate("Visualizers",
546  "MonoScope");
547  return s_name;
548  }
549 
550  uint plugins(QStringList *list) const override // VisFactory
551  {
552  *list << name();
553  return 1;
554  }
555 
556  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
557  {
558  return new MonoScope();
559  }
561 
563 // Spectrum
564 //
565 // NOTE: This visualiser requires mythplugins to be compiled with --enable-fft
566 
567 #if FFTW3_SUPPORT
568 Spectrum::Spectrum()
569 {
570  LOG(VB_GENERAL, LOG_INFO, QString("Spectrum : Being Initialised"));
571 
572  m_fps = 15;
573 
574  m_lin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N);
575  m_rin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N);
576  m_lout = (myth_fftw_complex*) av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1));
577  m_rout = (myth_fftw_complex*) av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1));
578 
579  m_lplan = fftw_plan_dft_r2c_1d(FFTW_N, m_lin, (myth_fftw_complex_cast*)m_lout, FFTW_MEASURE);
580  m_rplan = fftw_plan_dft_r2c_1d(FFTW_N, m_rin, (myth_fftw_complex_cast*)m_rout, FFTW_MEASURE);
581 }
582 
583 Spectrum::~Spectrum()
584 {
585  if (m_lin)
586  av_free(m_lin);
587  if (m_rin)
588  av_free(m_rin);
589  if (m_lout)
590  av_free(m_lout);
591  if (m_rout)
592  av_free(m_rout);
593 
594  fftw_destroy_plan(m_lplan);
595  fftw_destroy_plan(m_rplan);
596 }
597 
598 void Spectrum::resize(const QSize &newsize)
599 {
600  // Just change internal data about the
601  // size of the pixmap to be drawn (ie. the
602  // size of the screen) and the logically
603  // ensuing number of up/down bars to hold
604  // the audio magnitudes
605 
606  m_size = newsize;
607 
608  m_analyzerBarWidth = m_size.width() / 64;
609 
610  if (m_analyzerBarWidth < 6)
611  m_analyzerBarWidth = 6;
612 
613  m_scale.setMax(192, m_size.width() / m_analyzerBarWidth);
614 
615  m_rects.resize( m_scale.range() );
616  int w = 0;
617  // NOLINTNEXTLINE(modernize-loop-convert)
618  for (uint i = 0; i < (uint)m_rects.size(); i++, w += m_analyzerBarWidth)
619  {
620  m_rects[i].setRect(w, m_size.height() / 2, m_analyzerBarWidth - 1, 1);
621  }
622 
623  m_magnitudes.resize( m_scale.range() * 2 );
624  // NOLINTNEXTLINE(modernize-loop-convert)
625  for (uint os = m_magnitudes.size(); os < (uint)m_magnitudes.size(); os++)
626  {
627  m_magnitudes[os] = 0.0;
628  }
629 
630  m_scaleFactor = ( static_cast<double>(m_size.height()) / 2.0 ) /
631  log( static_cast<double>(FFTW_N) );
632 }
633 
634 template<typename T> T sq(T a) { return a*a; };
635 
636 bool Spectrum::process(VisualNode *node)
637 {
638  // Take a bunch of data in *node
639  // and break it down into spectrum
640  // values
641  bool allZero = true;
642 
643  uint i = 0;
644  long w = 0;
645  QRect *rectsp = m_rects.data();
646  double *magnitudesp = m_magnitudes.data();
647 
648  if (node)
649  {
650  i = node->m_length;
651  if (i > FFTW_N)
652  i = FFTW_N;
653  fast_real_set_from_short(m_lin, node->m_left, i);
654  if (node->m_right)
655  fast_real_set_from_short(m_rin, node->m_right, i);
656  }
657 
658  fast_reals_set(m_lin + i, m_rin + i, 0, FFTW_N - i);
659 
660  fftw_execute(m_lplan);
661  fftw_execute(m_rplan);
662 
663  long index = 1;
664 
665  for (i = 0; (int)i < m_rects.size(); i++, w += m_analyzerBarWidth)
666  {
667  // The 1D output is Hermitian symmetric (Yk = Yn-k) so Yn = Y0 etc.
668  // The dft_r2c_1d plan doesn't output these redundant values
669  // and furthermore they're not allocated in the ctor
670  double tmp = 2 * sq(real(m_lout[index])); // + sq(real(m_lout[FFTW_N - index]));
671  double magL = (tmp > 1.) ? (log(tmp) - 22.0) * m_scaleFactor : 0.;
672 
673  tmp = 2 * sq(real(m_rout[index])); // + sq(real(m_rout[FFTW_N - index]));
674  double magR = (tmp > 1.) ? (log(tmp) - 22.0) * m_scaleFactor : 0.;
675 
676 
677  double adjHeight = static_cast<double>(m_size.height()) / 2.0;
678  if (magL > adjHeight)
679  {
680  magL = adjHeight;
681  }
682  if (magL < magnitudesp[i])
683  {
684  tmp = magnitudesp[i] - m_falloff;
685  if ( tmp < magL )
686  {
687  tmp = magL;
688  }
689  magL = tmp;
690  }
691  if (magL < 1.)
692  {
693  magL = 1.;
694  }
695 
696  if (magR > adjHeight)
697  {
698  magR = adjHeight;
699  }
700  if (magR < magnitudesp[i + m_scale.range()])
701  {
702  tmp = magnitudesp[i + m_scale.range()] - m_falloff;
703  if ( tmp < magR )
704  {
705  tmp = magR;
706  }
707  magR = tmp;
708  }
709  if (magR < 1.)
710  {
711  magR = 1.;
712  }
713 
714  if (magR != 1 || magL != 1)
715  {
716  allZero = false;
717  }
718 
719  magnitudesp[i] = magL;
720  magnitudesp[i + m_scale.range()] = magR;
721  rectsp[i].setTop( m_size.height() / 2 - int( magL ) );
722  rectsp[i].setBottom( m_size.height() / 2 + int( magR ) );
723 
724  index = m_scale[i];
725  }
726 
727  Q_UNUSED(allZero);
728  return false;
729 }
730 
731 double Spectrum::clamp(double cur, double max, double min)
732 {
733  if (cur > max)
734  cur = max;
735  if (cur < min)
736  cur = min;
737  return cur;
738 }
739 
740 bool Spectrum::draw(QPainter *p, const QColor &back)
741 {
742  // This draws on a pixmap owned by MainVisual.
743  //
744  // In other words, this is not a Qt Widget, it
745  // just uses some Qt methods to draw on a pixmap.
746  // MainVisual then bitblts that onto the screen.
747 
748  QRect *rectsp = m_rects.data();
749 
750  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
751  for (uint i = 0; i < (uint)m_rects.size(); i++)
752  {
753  double per = double( rectsp[i].height() - 2 ) / double( m_size.height() );
754 
755  per = clamp(per, 1.0, 0.0);
756 
757  double r = m_startColor.red() +
758  (m_targetColor.red() - m_startColor.red()) * (per * per);
759  double g = m_startColor.green() +
760  (m_targetColor.green() - m_startColor.green()) * (per * per);
761  double b = m_startColor.blue() +
762  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
763 
764  r = clamp(r, 255.0, 0.0);
765  g = clamp(g, 255.0, 0.0);
766  b = clamp(b, 255.0, 0.0);
767 
768  if(rectsp[i].height() > 4)
769  p->fillRect(rectsp[i], QColor(int(r), int(g), int(b)));
770  }
771 
772  return true;
773 }
774 
775 static class SpectrumFactory : public VisFactory
776 {
777  public:
778  const QString &name(void) const override // VisFactory
779  {
780  static QString s_name = QCoreApplication::translate("Visualizers",
781  "Spectrum");
782  return s_name;
783  }
784 
785  uint plugins(QStringList *list) const override // VisFactory
786  {
787  *list << name();
788  return 1;
789  }
790 
791  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
792  {
793  return new Spectrum();
794  }
795 }SpectrumFactory;
796 
798 // Squares
799 //
800 // NOTE: This visualiser requires mythplugins to be compiled with --enable-fftw
801 
802 Squares::Squares()
803 {
804  m_fakeHeight = m_numberOfSquares * m_analyzerBarWidth;
805 }
806 
807 void Squares::resize (const QSize &newsize) {
808  // Trick the spectrum analyzer into calculating 16 rectangles
809  Spectrum::resize (QSize (m_fakeHeight, m_fakeHeight));
810  // We have our own copy, Spectrum has it's own...
811  m_actualSize = newsize;
812 }
813 
814 void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h)
815 {
816  double per = NAN;
817  int correction = (m_actualSize.width() % m_rects.size ()) / 2;
818  int x = ((i / 2) * w) + correction;
819  int y = 0;
820 
821  if (i % 2 == 0)
822  {
823  y = c - h;
824  per = double(m_fakeHeight - rect->top()) / double(m_fakeHeight);
825  }
826  else
827  {
828  y = c;
829  per = double(rect->bottom()) / double(m_fakeHeight);
830  }
831 
832  per = clamp(per, 1.0, 0.0);
833 
834  double r = m_startColor.red() +
835  (m_targetColor.red() - m_startColor.red()) * (per * per);
836  double g = m_startColor.green() +
837  (m_targetColor.green() - m_startColor.green()) * (per * per);
838  double b = m_startColor.blue() +
839  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
840 
841  r = clamp(r, 255.0, 0.0);
842  g = clamp(g, 255.0, 0.0);
843  b = clamp(b, 255.0, 0.0);
844 
845  p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
846 }
847 
848 bool Squares::draw(QPainter *p, const QColor &back)
849 {
850  p->fillRect (0, 0, m_actualSize.width(), m_actualSize.height(), back);
851  int w = m_actualSize.width() / (m_rects.size() / 2);
852  int h = w;
853  int center = m_actualSize.height() / 2;
854 
855  QRect *rectsp = m_rects.data();
856  for (uint i = 0; i < (uint)m_rects.size(); i++)
857  drawRect(p, &(rectsp[i]), i, center, w, h);
858 
859  return true;
860 }
861 
862 static class SquaresFactory : public VisFactory
863 {
864  public:
865  const QString &name(void) const override // VisFactory
866  {
867  static QString s_name = QCoreApplication::translate("Visualizers",
868  "Squares");
869  return s_name;
870  }
871 
872  uint plugins(QStringList *list) const override // VisFactory
873  {
874  *list << name();
875  return 1;
876  }
877 
878  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
879  {
880  return new Squares();
881  }
882 }SquaresFactory;
883 
884 #endif // FFTW3_SUPPORT
885 
887 {
888  // Setup the "magical" audio coefficients
889  // required by the Goetzel Algorithm
890 
891  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Initialised"));
892 
893  m_piano_data = (piano_key_data *) malloc(sizeof(piano_key_data) * PIANO_N);
894  m_audio_data = (piano_audio *) malloc(sizeof(piano_audio) * PIANO_AUDIO_SIZE);
895 
896  double sample_rate = 44100.0; // TODO : This should be obtained from gPlayer (likely candidate...)
897 
898  m_fps = 20; // This is the display frequency. We're capturing all audio chunks by defining .process_undisplayed() though.
899 
900  double concert_A = 440.0;
901  double semi_tone = pow(2.0, 1.0/12.0);
902 
903  /* Lowest note on piano is 4 octaves below concert A */
904  double bottom_A = concert_A / 2.0 / 2.0 / 2.0 / 2.0;
905 
906  double current_freq = bottom_A;
907  for (uint key = 0; key < PIANO_N; key++)
908  {
909  // This is constant through time
910  m_piano_data[key].coeff = (goertzel_data)(2.0 * cos(2.0 * M_PI * current_freq / sample_rate));
911 
912  // Want 20 whole cycles of the current waveform at least
913  double samples_required = sample_rate/current_freq * 20.0;
914  if (samples_required > sample_rate/4.0)
915  {
916  // For the really low notes, 4 updates a second is good enough...
917  samples_required = sample_rate/4.0;
918  }
919  if (samples_required < sample_rate/(double)m_fps * 0.75)
920  { // For the high notes, use as many samples as we need in a display_fps
921  samples_required = sample_rate/(double)m_fps * 0.75;
922  }
923  m_piano_data[key].samples_process_before_display_update = (int)samples_required;
924  m_piano_data[key].is_black_note = false; // Will be put right in .resize()
925 
926  current_freq *= semi_tone;
927  }
928 
929  zero_analysis();
930 }
931 
933 {
934  if (m_piano_data)
935  free(m_piano_data);
936  if (m_audio_data)
937  free(m_audio_data);
938 }
939 
941 {
942  for (uint key = 0; key < PIANO_N; key++)
943  {
944  // These get updated continously, and must be stored between chunks of audio data
945  m_piano_data[key].q2 = 0.0F;
946  m_piano_data[key].q1 = 0.0F;
947  m_piano_data[key].magnitude = 0.0F;
949  (goertzel_data)(PIANO_RMS_NEGLIGIBLE*PIANO_RMS_NEGLIGIBLE); // This is a guess - will be quickly overwritten
950 
952  }
953  m_offset_processed = 0;
954 }
955 
956 void Piano::resize(const QSize &newsize)
957 {
958  // Just change internal data about the
959  // size of the pixmap to be drawn (ie. the
960  // size of the screen) and the logically
961  // ensuing number of up/down bars to hold
962  // the audio magnitudes
963 
964  m_size = newsize;
965 
966  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Resized"));
967 
968  zero_analysis();
969 
970  // There are 88-36=52 white notes on piano keyboard
971  double key_unit_size = (double)m_size.width() / 54.0; // One white key extra spacing, if possible
972  if (key_unit_size < 10.0) // Keys have to be at least this many pixels wide
973  key_unit_size = 10.0;
974 
975  double white_width_pct = .8;
976  double black_width_pct = .6;
977  double black_offset_pct = .05;
978 
979  double white_height_pct = 6;
980  double black_height_pct = 4;
981 
982  // This is the starting position of the keyboard (may be beyond LHS)
983  // - actually position of C below bottom A (will be added to...). This is 4 octaves below middle C.
984  double left = (double)m_size.width() / 2.0 - (4.0*7.0 + 3.5) * key_unit_size; // The extra 3.5 centers 'F' inthe middle of the screen
985  double top_of_keys = (double)m_size.height() / 2.0 - key_unit_size * white_height_pct / 2.0; // Vertically center keys
986 
987  m_rects.resize(PIANO_N);
988 
989  for (uint key = 0; key < PIANO_N; key++)
990  {
991  int note = ((int)key - 3 + 12) % 12; // This means that C=0, C#=1, D=2, etc (since lowest note is bottom A)
992  if (note == 0) // If we're on a 'C', move the left 'cursor' over an octave
993  {
994  left += key_unit_size*7.0;
995  }
996 
997  double center = 0.0;
998  double offset = 0.0;
999  bool is_black = false;
1000 
1001  switch (note)
1002  {
1003  case 0: center = 0.5; break;
1004  case 1: center = 1.0; is_black = true; offset = -1; break;
1005  case 2: center = 1.5; break;
1006  case 3: center = 2.0; is_black = true; offset = +1; break;
1007  case 4: center = 2.5; break;
1008  case 5: center = 3.5; break;
1009  case 6: center = 4.0; is_black = true; offset = -2; break;
1010  case 7: center = 4.5; break;
1011  case 8: center = 5.0; is_black = true; offset = 0; break;
1012  case 9: center = 5.5; break;
1013  case 10: center = 6.0; is_black = true; offset = 2; break;
1014  case 11: center = 6.5; break;
1015  }
1016  m_piano_data[key].is_black_note = is_black;
1017 
1018  double width = (is_black ? black_width_pct:white_width_pct) * key_unit_size;
1019  double height = (is_black? black_height_pct:white_height_pct) * key_unit_size;
1020 
1021  m_rects[key].setRect(
1022  left + center * key_unit_size // Basic position of left side of key
1023  - width / 2.0 // Less half the width
1024  + (is_black ? (offset * black_offset_pct * key_unit_size):0.0), // And jiggle the positions of the black keys for aethetic reasons
1025  top_of_keys, // top
1026  width, // width
1027  height // height
1028  );
1029  }
1030 
1031  m_magnitude.resize(PIANO_N);
1032  for (double & key : m_magnitude)
1033  key = 0.0;
1034 }
1035 
1036 unsigned long Piano::getDesiredSamples(void)
1037 {
1038  // We want all the data! (within reason)
1039  // typical observed values are 882 -
1040  // 12.5 chunks of data per second from 44100Hz signal : Sampled at 50Hz, lots of 4, see :
1041  // mythtv/libs/libmyth/audio/audiooutputbase.cpp :: AudioOutputBase::AddData
1042  // See : mythtv/mythplugins/mythmusic/mythmusic/avfdecoder.cpp "20ms worth"
1043  return (unsigned long) PIANO_AUDIO_SIZE; // Maximum we can be given
1044 }
1045 
1047 {
1048  //LOG(VB_GENERAL, LOG_INFO, QString("Piano : Processing undisplayed node"));
1049  return process_all_types(node, false);
1050 }
1051 
1053 {
1054  //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node for DISPLAY"));
1055 // return process_all_types(node, true);
1056  process_all_types(node, true);
1057  return false;
1058 }
1059 
1060 bool Piano::process_all_types(VisualNode *node, bool /*this_will_be_displayed*/)
1061 {
1062  // Take a bunch of data in *node and break it down into piano key spectrum values
1063  // NB: Remember the state data between calls, so as to accumulate more accurate results.
1064  bool allZero = true;
1065  uint n = 0;
1066 
1067  if (node)
1068  {
1069  piano_audio short_to_bounded = 32768.0F;
1070 
1071  // Detect start of new song (current node more than 10s earlier than already seen)
1072  if (node->m_offset + 10000 < m_offset_processed)
1073  {
1074  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Node offset=%1 too far backwards : NEW SONG").arg(node->m_offset));
1075  zero_analysis();
1076  }
1077 
1078  // Check whether we've seen this node (more recently than 10secs ago)
1079  if (node->m_offset <= m_offset_processed)
1080  {
1081  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Already seen node offset=%1, returning without processing").arg(node->m_offset));
1082  return allZero; // Nothing to see here - the server can stop if it wants to
1083  }
1084 
1085  //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node offset=%1, size=%2").arg(node->m_offset).arg(node->m_length));
1086  n = node->m_length;
1087 
1088  if (node->m_right) // Preprocess the data into a combined middle channel, if we have stereo data
1089  {
1090  for (uint i = 0; i < n; i++)
1091  {
1092  m_audio_data[i] = ((piano_audio)node->m_left[i] + (piano_audio)node->m_right[i]) / 2.0F / short_to_bounded;
1093  }
1094  }
1095  else // This is only one channel of data
1096  {
1097  for (uint i = 0; i < n; i++)
1098  {
1099  m_audio_data[i] = (piano_audio)node->m_left[i] / short_to_bounded;
1100  }
1101  }
1102  }
1103  else
1104  {
1105  LOG(VB_GENERAL, LOG_DEBUG, QString("Hit an empty node, and returning empty-handed"));
1106  return allZero; // Nothing to see here - the server can stop if it wants to
1107  }
1108 
1109  for (uint key = 0; key < PIANO_N; key++)
1110  {
1111  goertzel_data coeff = m_piano_data[key].coeff;
1112  goertzel_data q2 = m_piano_data[key].q2;
1113  goertzel_data q1 = m_piano_data[key].q1;
1114 
1115  for (uint i = 0; i < n; i++)
1116  {
1117  goertzel_data q0 = coeff * q1 - q2 + m_audio_data[i];
1118  q2 = q1;
1119  q1 = q0;
1120  }
1121  m_piano_data[key].q2 = q2;
1122  m_piano_data[key].q1 = q1;
1123 
1124  m_piano_data[key].samples_processed += n;
1125 
1126  int n_samples = m_piano_data[key].samples_processed;
1127 
1128  // Only do this update if we've processed enough chunks for this key...
1129  if (n_samples > m_piano_data[key].samples_process_before_display_update)
1130  {
1131  goertzel_data magnitude2 = q1*q1 + q2*q2 - q1*q2*coeff;
1132 
1133 #if 0
1134  // This is RMS of signal
1135  goertzel_data magnitude_av =
1136  sqrt(magnitude2)/(goertzel_data)n_samples; // Should be 0<magnitude_av<.5
1137 #else
1138  // This is pure magnitude of signal
1139  goertzel_data magnitude_av =
1140  magnitude2/(goertzel_data)n_samples/(goertzel_data)n_samples; // Should be 0<magnitude_av<.25
1141 #endif
1142 
1143 #if 0
1144  // Take logs everywhere, and shift up to [0, ??]
1145  if(magnitude_av > 0.0F)
1146  {
1147  magnitude_av = log(magnitude_av);
1148  }
1149  else
1150  {
1151  magnitude_av = PIANO_MIN_VOL;
1152  }
1153  magnitude_av -= PIANO_MIN_VOL;
1154 
1155  if (magnitude_av < 0.0F)
1156  {
1157  magnitude_av = 0.0;
1158  }
1159 #endif
1160 
1161  if (magnitude_av > (goertzel_data)0.01)
1162  {
1163  allZero = false;
1164  }
1165 
1166  m_piano_data[key].magnitude = magnitude_av; // Store this for later : We'll do the colours from this...
1167  if ( m_piano_data[key].max_magnitude_seen < magnitude_av)
1168  {
1169  m_piano_data[key].max_magnitude_seen = magnitude_av;
1170  }
1171  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Updated Key %1 from %2 samples, magnitude=%3")
1172  .arg(key).arg(n_samples).arg(magnitude_av));
1173 
1174  m_piano_data[key].samples_processed = 0; // Reset the counts, now that we've set the magnitude...
1175  m_piano_data[key].q1 = (goertzel_data)0.0;
1176  m_piano_data[key].q2 = (goertzel_data)0.0;
1177  }
1178  }
1179 
1180  if (node)
1181  {
1182  // All done now - record that we've done this offset
1183  m_offset_processed = node->m_offset;
1184  }
1185 
1186  return allZero;
1187 }
1188 
1189 double Piano::clamp(double cur, double max, double min)
1190 {
1191  if (cur > max)
1192  cur = max;
1193  if (cur < min)
1194  cur = min;
1195  return cur;
1196 }
1197 
1198 bool Piano::draw(QPainter *p, const QColor &back)
1199 {
1200  // This draws on a pixmap owned by MainVisual.
1201  //
1202  // In other words, this is not a Qt Widget, it
1203  // just uses some Qt methods to draw on a pixmap.
1204  // MainVisual then bitblts that onto the screen.
1205 
1206  QRect *rectsp = &m_rects[0];
1207  double *magnitudep = &m_magnitude[0];
1208 
1209  unsigned int n = PIANO_N;
1210 
1211  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
1212 
1213  // Protect maximum array length
1214  if(n > (uint)m_rects.size())
1215  n = (uint)m_rects.size();
1216 
1217  // Sweep up across the keys, making sure the max_magnitude_seen is at minimum X% of its neighbours
1218  double mag = PIANO_RMS_NEGLIGIBLE;
1219  for (uint key = 0; key < n; key++)
1220  {
1221  if (m_piano_data[key].max_magnitude_seen < static_cast<float>(mag))
1222  {
1223  // Spread the previous value to this key
1224  m_piano_data[key].max_magnitude_seen = mag;
1225  }
1226  else
1227  {
1228  // This key has seen better peaks, use this for the next one
1229  mag = m_piano_data[key].max_magnitude_seen;
1230  }
1231  mag *= PIANO_SPECTRUM_SMOOTHING;
1232  }
1233 
1234  // Similarly, down, making sure the max_magnitude_seen is at minimum X% of its neighbours
1235  mag = PIANO_RMS_NEGLIGIBLE;
1236  for (int key_i = n - 1; key_i >= 0; key_i--)
1237  {
1238  uint key = key_i; // Wow, this is to avoid a zany error for ((unsigned)0)--
1239  if (m_piano_data[key].max_magnitude_seen < static_cast<float>(mag))
1240  {
1241  // Spread the previous value to this key
1242  m_piano_data[key].max_magnitude_seen = mag;
1243  }
1244  else
1245  {
1246  // This key has seen better peaks, use this for the next one
1247  mag = m_piano_data[key].max_magnitude_seen;
1248  }
1249  mag *= PIANO_SPECTRUM_SMOOTHING;
1250  }
1251 
1252  // Now find the key that has been hit the hardest relative to its experience, and renormalize...
1253  // Set a minimum, to prevent divide-by-zero (and also all-pressed when music very quiet)
1254  double magnitude_max = PIANO_RMS_NEGLIGIBLE;
1255  for (uint key = 0; key < n; key++)
1256  {
1258  if (magnitude_max < mag)
1259  magnitude_max = mag;
1260 
1261  magnitudep[key] = mag;
1262  }
1263 
1264  // Deal with all the white keys first
1265  for (uint key = 0; key < n; key++)
1266  {
1267  if (m_piano_data[key].is_black_note)
1268  continue;
1269 
1270  double per = magnitudep[key] / magnitude_max;
1271  per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
1272 
1273  if (per < PIANO_KEYPRESS_TOO_LIGHT)
1274  per = 0.0; // Clamp to zero for lightly detected keys
1275  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Display key %1, magnitude=%2, seen=%3")
1276  .arg(key).arg(per*100.0).arg(m_piano_data[key].max_magnitude_seen));
1277 
1278  double r = m_whiteStartColor.red() + (m_whiteTargetColor.red() - m_whiteStartColor.red()) * per;
1279  double g = m_whiteStartColor.green() + (m_whiteTargetColor.green() - m_whiteStartColor.green()) * per;
1280  double b = m_whiteStartColor.blue() + (m_whiteTargetColor.blue() - m_whiteStartColor.blue()) * per;
1281 
1282  p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
1283  }
1284 
1285  // Then overlay the black keys
1286  for (uint key = 0; key < n; key++)
1287  {
1288  if (!m_piano_data[key].is_black_note)
1289  continue;
1290 
1291  double per = magnitudep[key]/magnitude_max;
1292  per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
1293 
1294  if (per < PIANO_KEYPRESS_TOO_LIGHT)
1295  per = 0.0; // Clamp to zero for lightly detected keys
1296 
1297  double r = m_blackStartColor.red() + (m_blackTargetColor.red() - m_blackStartColor.red()) * per;
1298  double g = m_blackStartColor.green() + (m_blackTargetColor.green() - m_blackStartColor.green()) * per;
1299  double b = m_blackStartColor.blue() + (m_blackTargetColor.blue() - m_blackStartColor.blue()) * per;
1300 
1301  p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
1302  }
1303 
1304  return true;
1305 }
1306 
1307 static class PianoFactory : public VisFactory
1308 {
1309  public:
1310  const QString &name(void) const override // VisFactory
1311  {
1312  static QString s_name = QCoreApplication::translate("Visualizers",
1313  "Piano");
1314  return s_name;
1315  }
1316 
1317  uint plugins(QStringList *list) const override // VisFactory
1318  {
1319  *list << name();
1320  return 1;
1321  }
1322 
1323  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1324  {
1325  return new Piano();
1326  }
1327 }PianoFactory;
1328 
1330  m_lastCycle(QDateTime::currentDateTime())
1331 {
1332  findFrontCover();
1333  m_fps = 1;
1334 }
1335 
1337 {
1338  if (!gPlayer->getCurrentMetadata())
1339  return;
1340 
1341  // if a front cover image is available show that first
1343  if (albumArt->getImage(IT_FRONTCOVER))
1345  else
1346  {
1347  // not available so just show the first image available
1348  if (albumArt->getImageCount() > 0)
1349  m_currImageType = albumArt->getImageAt(0)->m_imageType;
1350  else
1352  }
1353 }
1354 
1356 {
1357  if (!gPlayer->getCurrentMetadata())
1358  return false;
1359 
1361  int newType = m_currImageType;
1362 
1363  // If we only have one image there is nothing to cycle
1364  if (albumArt->getImageCount() > 1)
1365  {
1366  do
1367  {
1368  newType++;
1369  if (newType == IT_LAST)
1370  newType = IT_UNKNOWN;
1371  } while (!albumArt->getImage((ImageType) newType));
1372  }
1373 
1374  if (newType != m_currImageType)
1375  {
1376  m_currImageType = (ImageType) newType;
1377  m_lastCycle = QDateTime::currentDateTime();
1378  return true;
1379  }
1380 
1381  return false;
1382 }
1383 
1384 void AlbumArt::resize(const QSize &newsize)
1385 {
1386  m_size = newsize;
1387 }
1388 
1390 {
1391  return false;
1392 }
1393 
1394 void AlbumArt::handleKeyPress(const QString &action)
1395 {
1396  if (action == "SELECT")
1397  {
1398  if (gPlayer->getCurrentMetadata())
1399  {
1401  int newType = m_currImageType;
1402 
1403  if (albumArt.getImageCount() > 0)
1404  {
1405  newType++;
1406 
1407  while (!albumArt.getImage((ImageType) newType))
1408  {
1409  newType++;
1410  if (newType == IT_LAST)
1411  newType = IT_UNKNOWN;
1412  }
1413  }
1414 
1415  if (newType != m_currImageType)
1416  {
1417  m_currImageType = (ImageType) newType;
1418  // force an update
1419  m_cursize = QSize(0, 0);
1420  }
1421  }
1422  }
1423 }
1424 
1426 #define ALBUMARTCYCLETIME 10
1427 
1429 {
1430  // if the track has changed we need to update the image
1432  {
1434  findFrontCover();
1435  return true;
1436  }
1437 
1438  // if it's time to cycle to the next image we need to update the image
1439  if (m_lastCycle.addSecs(ALBUMARTCYCLETIME) < QDateTime::currentDateTime())
1440  {
1441  if (cycleImage())
1442  return true;
1443  }
1444 
1445  return false;
1446 }
1447 
1448 bool AlbumArt::draw(QPainter *p, const QColor &back)
1449 {
1450  if (needsUpdate())
1451  {
1452  QImage art;
1453  QString imageFilename = gPlayer->getCurrentMetadata()->getAlbumArtFile(m_currImageType);
1454 
1455  if (imageFilename.startsWith("myth://"))
1456  {
1457  auto *rf = new RemoteFile(imageFilename, false, false, 0);
1458 
1459  QByteArray data;
1460  bool ret = rf->SaveAs(data);
1461 
1462  delete rf;
1463 
1464  if (ret)
1465  art.loadFromData(data);
1466  }
1467  else
1468  if (!imageFilename.isEmpty())
1469  art.load(imageFilename);
1470 
1471  if (art.isNull())
1472  {
1473  m_cursize = m_size;
1474  m_image = QImage();
1475  }
1476  else
1477  {
1478  m_image = art.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1479  }
1480  }
1481 
1482  if (m_image.isNull())
1483  {
1484  drawWarning(p, back, m_size, tr("?"), 100);
1485  return true;
1486  }
1487 
1488  // Paint the image
1489  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
1490  p->drawImage((m_size.width() - m_image.width()) / 2,
1491  (m_size.height() - m_image.height()) / 2,
1492  m_image);
1493 
1494  // Store our new size
1495  m_cursize = m_size;
1496 
1497  return true;
1498 }
1499 
1500 static class AlbumArtFactory : public VisFactory
1501 {
1502  public:
1503  const QString &name(void) const override // VisFactory
1504  {
1505  static QString s_name = QCoreApplication::translate("Visualizers",
1506  "AlbumArt");
1507  return s_name;
1508  }
1509 
1510  uint plugins(QStringList *list) const override // VisFactory
1511  {
1512  *list << name();
1513  return 1;
1514  }
1515 
1516  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1517  {
1518  return new AlbumArt();
1519  }
1521 
1523  : VisualBase(true)
1524 {
1525  m_fps = 1;
1526 }
1527 
1528 void Blank::resize(const QSize &newsize)
1529 {
1530  m_size = newsize;
1531 }
1532 
1533 
1535 {
1536  return false;
1537 }
1538 
1539 bool Blank::draw(QPainter *p, const QColor &back)
1540 {
1541  // Took me hours to work out this algorithm
1542  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
1543  return true;
1544 }
1545 
1546 static class BlankFactory : public VisFactory
1547 {
1548  public:
1549  const QString &name(void) const override // VisFactory
1550  {
1551  static QString s_name = QCoreApplication::translate("Visualizers",
1552  "Blank");
1553  return s_name;
1554  }
1555 
1556  uint plugins(QStringList *list) const override // VisFactory
1557  {
1558  *list << name();
1559  return 1;
1560  }
1561 
1562  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1563  {
1564  return new Blank();
1565  }
1566 }BlankFactory;
piano_audio * m_audio_data
Definition: visualize.h:293
int operator[](int index) const
unsigned long getDesiredSamples(void) override
Definition: visualize.cpp:1036
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1556
bool const m_rubberband
Definition: visualize.h:136
bool process_all_types(VisualNode *node, bool this_will_be_displayed)
Definition: visualize.cpp:1060
MusicPlayer * gPlayer
Definition: musicplayer.cpp:35
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
#define PIANO_RMS_NEGLIGIBLE
Definition: visualize.h:243
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1539
void resize(const QSize &size) override
Definition: visualize.cpp:1384
MusicMetadata * getCurrentMetadata(void)
get the metadata for the current track in the playlist
goertzel_data max_magnitude_seen
Definition: visualize.h:250
bool process(VisualNode *node) override
Definition: visualize.cpp:151
long m_offset
Definition: videovisual.h:37
static QFont GetMediumFont(void)
uint getImageCount()
virtual const QString & name(void) const =0
float sq(float a)
Definition: mythmiscutil.h:57
#define PIANO_N
Definition: visualize.h:238
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1448
uint plugins(QStringList *list) const override
Definition: visualize.cpp:524
AlbumArtImages * getAlbumArtImages(void)
#define PIANO_SPECTRUM_SMOOTHING
Definition: visualize.h:244
void setMax(int maxscale, int maxrange)
void zero_analysis(void)
Definition: visualize.cpp:940
goertzel_data coeff
Definition: visualize.h:249
bool process(VisualNode *node=nullptr) override
Definition: visualize.cpp:1389
virtual ~VisualBase(void)
Definition: visualize.cpp:51
void resize(const QSize &size) override
Definition: visualize.cpp:141
QSize m_size
Definition: visualize.h:339
static guint32 * tmp
Definition: goom_core.c:35
vector< double > m_magnitude
Definition: visualize.h:295
short * m_right
Definition: videovisual.h:35
PianoFactory PianoFactory
QColor m_blackTargetColor
Definition: visualize.h:285
bool draw(QPainter *p, const QColor &back) override
Definition: visualize.cpp:269
goertzel_data q1
Definition: visualize.h:249
ImageType m_imageType
Definition: musicmetadata.h:51
#define PIANO_KEYPRESS_TOO_LIGHT
Definition: visualize.h:246
bool m_xscreensaverenable
Definition: visualize.h:98
bool needsUpdate(void)
Definition: visualize.cpp:1428
#define PIANO_MIN_VOL
Definition: visualize.h:245
static VisFactory * g_pVisFactories
Definition: visualize.h:112
QSize m_size
Definition: visualize.h:135
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1323
int samples_process_before_display_update
Definition: visualize.h:256
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:556
QColor m_startColor
Definition: visualize.h:132
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1510
QColor m_targetColor
Definition: visualize.h:133
void DoDisableScreensaver(void)
bool process(VisualNode *node) override
Definition: visualize.cpp:369
LogScale(int maxscale=0, int maxrange=0)
QColor m_whiteStartColor
Definition: visualize.h:282
QString getAlbumArtFile(void)
const QString & name(void) const override
Definition: visualize.cpp:543
vector< double > m_magnitudes
Definition: visualize.h:134
void DoRestoreScreensaver(void)
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1317
def log(debug, txt)
Definition: utilities.py:7
unsigned int uint
Definition: compat.h:140
ImageType m_currImageType
Definition: visualize.h:318
MusicMetadata * m_currentMetadata
Definition: visualize.h:321
uint plugins(QStringList *list) const override
Definition: visualize.cpp:550
VisualBase(bool screensaverenable=false)
Definition: visualize.cpp:44
MonoScopeFactory MonoScopeFactory
double const m_falloff
Definition: visualize.h:137
QSize m_cursize
Definition: visualize.h:317
const QString & name(void) const override
Definition: visualize.cpp:517
void * av_malloc(unsigned int size)
MythUIHelper * GetMythUI()
float clamp(float val, float minimum, float maximum)
Definition: mythmiscutil.h:41
int * m_indices
const QString & name(void) const override
Definition: visualize.cpp:1503
goertzel_data q2
Definition: visualize.h:249
#define FFTW_N
Definition: visualize.cpp:38
void resize(const QSize &size) override
Definition: visualize.cpp:956
AlbumArtImage * getImageAt(uint index)
static void drawWarning(QPainter *p, const QColor &back, const QSize &size, const QString &warning, int fontsize=28)
Definition: visualize.cpp:63
#define M_PI
Definition: goom_tools.h:5
const QString & name(void) const override
Definition: visualize.cpp:1549
void resize(const QSize &size) override
Definition: visualize.cpp:1528
QDateTime m_lastCycle
Definition: visualize.h:322
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
#define PIANO_AUDIO_SIZE
Definition: visualize.h:237
AlbumArtImage * getImage(ImageType type)
#define ALBUMARTCYCLETIME
this is the time an image is shown in the albumart visualizer
Definition: visualize.cpp:1426
bool process(VisualNode *node) override
Definition: visualize.cpp:1052
long m_length
Definition: videovisual.h:36
bool process(VisualNode *node=nullptr) override
Definition: visualize.cpp:1534
vector< QRect > m_rects
Definition: visualize.h:287
bool cycleImage(void)
Definition: visualize.cpp:1355
goertzel_data magnitude
Definition: visualize.h:249
BlankFactory BlankFactory
QColor m_blackStartColor
Definition: visualize.h:284
static double clamp(double cur, double max, double min)
Definition: visualize.cpp:1189
void findFrontCover(void)
Definition: visualize.cpp:1336
void handleKeyPress(const QString &action) override
Definition: visualize.cpp:1394
AlbumArtFactory AlbumArtFactory
ImageType
Definition: musicmetadata.h:28
#define piano_audio
Definition: visualize.h:240
void av_free(void *ptr)
QSize m_size
Definition: visualize.h:288
virtual uint plugins(QStringList *list) const =0
StereoScopeFactory StereoScopeFactory
QColor m_whiteTargetColor
Definition: visualize.h:283
static guint32 * back
Definition: goom_core.c:34
AlbumArt(void)
Definition: visualize.cpp:1329
bool draw(QPainter *p, const QColor &back) override
Definition: visualize.cpp:459
virtual VisualBase * create(MainVisual *parent, const QString &pluginName) const =0
QSize m_size
Definition: visualize.h:316
const QString & name(void) const override
Definition: visualize.cpp:1310
#define SAMPLES_DEFAULT_SIZE
Definition: visualize.h:46
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1562
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1516
int m_fps
Definition: visualize.h:97
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1198
short * m_left
Definition: videovisual.h:34
piano_key_data * m_piano_data
Definition: visualize.h:292
~Piano() override
Definition: visualize.cpp:932
unsigned long m_offset_processed
Definition: visualize.h:290
QImage m_image
Definition: visualize.h:319
int range() const
#define goertzel_data
Definition: visualize.h:241
bool processUndisplayed(VisualNode *node) override
Definition: visualize.cpp:1046
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:530