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 #include <iostream>
15 
16 // Qt
17 #include <QApplication>
18 #include <QCoreApplication>
19 #include <QImage>
20 #include <QPainter>
21 #include <QDir>
22 
23 // MythTV
24 #include <libmyth/mythcontext.h>
25 #include <libmythbase/mythdbcon.h>
26 #include <libmythbase/remotefile.h>
28 #include <libmythbase/mythdirs.h>
31 #include <libmythui/mythuihelper.h>
32 
33 // mythmusic
34 #include "decoder.h"
35 #include "inlines.h"
36 #include "mainvisual.h"
37 #include "musicplayer.h"
38 #include "visualize.h"
39 
41 
42 VisualBase::VisualBase(bool screensaverenable)
43  : m_xscreensaverenable(screensaverenable)
44 {
47 }
48 
50 {
51  //
52  // This is only here so
53  // that derived classes
54  // can destruct properly
55  //
58 }
59 
60 
61 void VisualBase::drawWarning(QPainter *p, const QColor &back, const QSize size, const QString& warning, int fontSize)
62 {
63  p->fillRect(0, 0, size.width(), size.height(), back);
64  p->setPen(Qt::white);
65 
66  // Taken from removed MythUIHelper::GetMediumFont
67  QFont font = QApplication::font();
68 
69 #ifdef _WIN32
70  // logicalDpiY not supported in Windows.
71  int logicalDpiY = 100;
72  HDC hdc = GetDC(nullptr);
73  if (hdc)
74  {
75  logicalDpiY = GetDeviceCaps(hdc, LOGPIXELSY);
76  ReleaseDC(nullptr, hdc);
77  }
78 #else
79  int logicalDpiY = GetMythMainWindow()->logicalDpiY();
80 #endif
81 
82  // adjust for screen resolution relative to 100 dpi
83  float floatSize = (16 * 100.0F) / logicalDpiY;
84  // adjust for myth GUI size relative to 800x600
85  float dummy = 0.0;
86  float hmult = 0.0;
87  GetMythMainWindow()->GetScalingFactors(hmult, dummy);
88  floatSize = floatSize * hmult;
89  // round and set
90  font.setPointSize(lroundf(floatSize));
91  font.setWeight(QFont::Bold);
92  font.setPointSizeF(fontSize * (size.width() / 800.0));
93  p->setFont(font);
94 
95  p->drawText(0, 0, size.width(), size.height(), Qt::AlignVCenter | Qt::AlignHCenter | Qt::TextWordWrap, warning);
96 }
97 
99 // LogScale
100 
101 LogScale::LogScale(int maxscale, int maxrange)
102 {
103  setMax(maxscale, maxrange);
104 }
105 
106 void LogScale::setMax(int maxscale, int maxrange)
107 {
108  if (maxscale == 0 || maxrange == 0)
109  return;
110 
111  m_scale = maxscale;
112  m_range = maxrange;
113 
114  auto domain = (long double) maxscale;
115  auto range = (long double) maxrange;
116  long double x = 1.0;
117  long double dx = 1.0;
118  long double e4 = 1.0E-8;
119 
120  m_indices.clear();
121  m_indices.resize(maxrange, 0);
122 
123  // initialize log scale
124  for (uint i=0; i<10000 && (std::abs(dx) > e4); i++)
125  {
126  double t = std::log((domain + x) / x);
127  double y = (x * t) - range;
128  double yy = t - (domain / (x + domain));
129  dx = y / yy;
130  x -= dx;
131  }
132 
133  double alpha = x;
134  for (int i = 1; i < (int) domain; i++)
135  {
136  int scaled = (int) floor(0.5 + (alpha * log((double(i) + alpha) / alpha)));
137  if (scaled < 1)
138  scaled = 1;
139  if (m_indices[scaled - 1] < i)
140  m_indices[scaled - 1] = i;
141  }
142 }
143 
144 int LogScale::operator[](int index)
145 {
146  return m_indices[index];
147 }
148 
150 // MelScale, example: (8192 fft, 1920 pixels, 22050 hz)
151 
152 MelScale::MelScale(int maxscale, int maxrange, int maxfreq)
153 {
154  setMax(maxscale, maxrange, maxfreq);
155 }
156 
157 void MelScale::setMax(int maxscale, int maxrange, int maxfreq)
158 {
159  if (maxscale == 0 || maxrange == 0 || maxfreq == 0)
160  return;
161 
162  m_scale = maxscale;
163  m_range = maxrange;
164 
165  m_indices.clear();
166  m_indices.resize(maxrange, 0);
167 
168  double maxmel = hz2mel(maxfreq);
169  double hzperbin = (double) maxfreq / (double) maxscale;
170 
171  for (int i = 0; i < maxrange; i++)
172  {
173  double mel = maxmel * i / maxrange;
174  double hz = mel2hz(mel);
175  int bin = int(hz / hzperbin);
176  m_indices[i] = bin;
177  // LOG(VB_PLAYBACK, LOG_INFO, QString("Mel maxmel=%1, hzperbin=%2, hz=%3, i=%4, bin=%5")
178  // .arg(maxmel).arg(hzperbin).arg(hz).arg(i).arg(bin));
179  }
180 }
181 
182 int MelScale::operator[](int index)
183 {
184  return m_indices[index];
185 }
186 
188 // StereoScope
189 
191 {
192  m_fps = 45;
193 }
194 
195 void StereoScope::resize( const QSize &newsize )
196 {
197  m_size = newsize;
198 
199  auto os = m_magnitudes.size();
200  m_magnitudes.resize( m_size.width() * 2_UZ );
201  for ( ; os < m_magnitudes.size(); os++ )
202  m_magnitudes[os] = 0.0;
203 }
204 
206 {
207  bool allZero = true;
208 
209 
210  if (node)
211  {
212  double index = 0;
213  double const step = (double)SAMPLES_DEFAULT_SIZE / m_size.width();
214  for ( int i = 0; i < m_size.width(); i++)
215  {
216  auto indexTo = (unsigned long)(index + step);
217  if (indexTo == (unsigned long)(index))
218  indexTo = (unsigned long)(index + 1);
219 
220  double valL = 0;
221  double valR = 0;
222 #if RUBBERBAND
223  if ( m_rubberband ) {
224  valL = m_magnitudes[ i ];
225  valR = m_magnitudes[ i + m_size.width() ];
226  if (valL < 0.) {
227  valL += m_falloff;
228  if ( valL > 0. )
229  valL = 0.;
230  }
231  else
232  {
233  valL -= m_falloff;
234  if ( valL < 0. )
235  valL = 0.;
236  }
237  if (valR < 0.)
238  {
239  valR += m_falloff;
240  if ( valR > 0. )
241  valR = 0.;
242  }
243  else
244  {
245  valR -= m_falloff;
246  if ( valR < 0. )
247  valR = 0.;
248  }
249  }
250 #endif
251  for (auto s = (unsigned long)index; s < indexTo && s < node->m_length; s++)
252  {
253  double adjHeight = static_cast<double>(m_size.height()) / 4.0;
254  double tmpL = ( ( node->m_left ? static_cast<double>(node->m_left[s]) : 0.) *
255  adjHeight ) / 32768.0;
256  double tmpR = ( ( node->m_right ? static_cast<double>(node->m_right[s]) : 0.) *
257  adjHeight ) / 32768.0;
258  if (tmpL > 0)
259  valL = (tmpL > valL) ? tmpL : valL;
260  else
261  valL = (tmpL < valL) ? tmpL : valL;
262  if (tmpR > 0)
263  valR = (tmpR > valR) ? tmpR : valR;
264  else
265  valR = (tmpR < valR) ? tmpR : valR;
266  }
267 
268  if (valL != 0. || valR != 0.)
269  allZero = false;
270 
271  m_magnitudes[ i ] = valL;
272  m_magnitudes[ i + m_size.width() ] = valR;
273 
274  index = index + step;
275  }
276 #if RUBBERBAND
277  }
278  else if (m_rubberband)
279  {
280  for ( int i = 0; i < m_size.width(); i++)
281  {
282  double valL = m_magnitudes[ i ];
283  if (valL < 0) {
284  valL += 2;
285  if (valL > 0.)
286  valL = 0.;
287  } else {
288  valL -= 2;
289  if (valL < 0.)
290  valL = 0.;
291  }
292 
293  double valR = m_magnitudes[ i + m_size.width() ];
294  if (valR < 0.) {
295  valR += m_falloff;
296  if (valR > 0.)
297  valR = 0.;
298  }
299  else
300  {
301  valR -= m_falloff;
302  if (valR < 0.)
303  valR = 0.;
304  }
305 
306  if (valL != 0. || valR != 0.)
307  allZero = false;
308 
309  m_magnitudes[ i ] = valL;
310  m_magnitudes[ i + m_size.width() ] = valR;
311  }
312 #endif
313  }
314  else
315  {
316  for ( int i = 0; (unsigned) i < m_magnitudes.size(); i++ )
317  m_magnitudes[ i ] = 0.;
318  }
319 
320  return allZero;
321 }
322 
323 bool StereoScope::draw( QPainter *p, const QColor &back )
324 {
325  if (back != Qt::green) // hack!!! for WaveForm
326  {
327  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
328  }
329  for ( int i = 1; i < m_size.width(); i++ )
330  {
331 #if TWOCOLOUR
332  // left
333  double per = ( static_cast<double>(m_magnitudes[i]) * 2.0 ) /
334  ( static_cast<double>(m_size.height()) / 4.0 );
335  if (per < 0.0)
336  per = -per;
337  if (per > 1.0)
338  per = 1.0;
339  else if (per < 0.0)
340  per = 0.0;
341 
342  double r = m_startColor.red() +
343  (m_targetColor.red() - m_startColor.red()) * (per * per);
344  double g = m_startColor.green() +
345  (m_targetColor.green() - m_startColor.green()) * (per * per);
346  double b = m_startColor.blue() +
347  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
348 
349  if (r > 255.0)
350  r = 255.0;
351  else if (r < 0.0)
352  r = 0;
353 
354  if (g > 255.0)
355  g = 255.0;
356  else if (g < 0.0)
357  g = 0;
358 
359  if (b > 255.0)
360  b = 255.0;
361  else if (b < 0.0)
362  b = 0;
363 
364  p->setPen( QColor( int(r), int(g), int(b) ) );
365 #else
366  p->setPen(Qt::red);
367 #endif
368  double adjHeight = static_cast<double>(m_size.height()) / 4.0;
369  p->drawLine( i - 1,
370  (int)(adjHeight - m_magnitudes[i - 1]),
371  i,
372  (int)(adjHeight - m_magnitudes[i]));
373 
374 #if TWOCOLOUR
375  // right
376  per = ( static_cast<double>(m_magnitudes[ i + m_size.width() ]) * 2 ) /
377  adjHeight;
378  if (per < 0.0)
379  per = -per;
380  if (per > 1.0)
381  per = 1.0;
382  else if (per < 0.0)
383  per = 0.0;
384 
385  r = m_startColor.red() + (m_targetColor.red() -
386  m_startColor.red()) * (per * per);
387  g = m_startColor.green() + (m_targetColor.green() -
388  m_startColor.green()) * (per * per);
389  b = m_startColor.blue() + (m_targetColor.blue() -
390  m_startColor.blue()) * (per * per);
391 
392  if (r > 255.0)
393  r = 255.0;
394  else if (r < 0.0)
395  r = 0;
396 
397  if (g > 255.0)
398  g = 255.0;
399  else if (g < 0.0)
400  g = 0;
401 
402  if (b > 255.0)
403  b = 255.0;
404  else if (b < 0.0)
405  b = 0;
406 
407  p->setPen( QColor( int(r), int(g), int(b) ) );
408 #else
409  p->setPen(Qt::red);
410 #endif
411  adjHeight = static_cast<double>(m_size.height()) * 3.0 / 4.0;
412  p->drawLine( i - 1,
413  (int)(adjHeight - m_magnitudes[i + m_size.width() - 1]),
414  i,
415  (int)(adjHeight - m_magnitudes[i + m_size.width()]));
416  }
417 
418  return true;
419 }
420 
422 // MonoScope
423 
425 {
426  bool allZero = true;
427 
428  if (node)
429  {
430  double index = 0;
431  double const step = (double)SAMPLES_DEFAULT_SIZE / m_size.width();
432  for (int i = 0; i < m_size.width(); i++)
433  {
434  auto indexTo = (unsigned long)(index + step);
435  if (indexTo == (unsigned long)index)
436  indexTo = (unsigned long)(index + 1);
437 
438  double val = 0;
439 #if RUBBERBAND
440  if ( m_rubberband )
441  {
442  val = m_magnitudes[ i ];
443  if (val < 0.)
444  {
445  val += m_falloff;
446  if ( val > 0. )
447  {
448  val = 0.;
449  }
450  }
451  else
452  {
453  val -= m_falloff;
454  if ( val < 0. )
455  {
456  val = 0.;
457  }
458  }
459  }
460 #endif
461  for (auto s = (unsigned long)index; s < indexTo && s < node->m_length; s++)
462  {
463  double tmp = ( static_cast<double>(node->m_left[s]) +
464  (node->m_right ? static_cast<double>(node->m_right[s])
465  : static_cast<double>(node->m_left[s])) *
466  ( static_cast<double>(m_size.height()) / 2.0 ) ) / 65536.0;
467  if (tmp > 0)
468  {
469  val = (tmp > val) ? tmp : val;
470  }
471  else
472  {
473  val = (tmp < val) ? tmp : val;
474  }
475  }
476 
477  if ( val != 0. )
478  {
479  allZero = false;
480  }
481  m_magnitudes[ i ] = val;
482  index = index + step;
483  }
484  }
485 #if RUBBERBAND
486  else if (m_rubberband)
487  {
488  for (int i = 0; i < m_size.width(); i++) {
489  double val = m_magnitudes[ i ];
490  if (val < 0) {
491  val += 2;
492  if (val > 0.)
493  val = 0.;
494  } else {
495  val -= 2;
496  if (val < 0.)
497  val = 0.;
498  }
499 
500  if ( val != 0. )
501  allZero = false;
502  m_magnitudes[ i ] = val;
503  }
504  }
505 #endif
506  else
507  {
508  for (int i = 0; i < m_size.width(); i++ )
509  m_magnitudes[ i ] = 0.;
510  }
511 
512  return allZero;
513 }
514 
515 bool MonoScope::draw( QPainter *p, const QColor &back )
516 {
517  if (back != Qt::green) // hack!!! for WaveForm
518  {
519  p->fillRect( 0, 0, m_size.width(), m_size.height(), back );
520  }
521  for ( int i = 1; i < m_size.width(); i++ ) {
522 #if TWOCOLOUR
523  double per = ( static_cast<double>(m_magnitudes[i]) * 2.0 ) /
524  ( static_cast<double>(m_size.height()) / 4.0 );
525  if (per < 0.0)
526  per = -per;
527  if (per > 1.0)
528  per = 1.0;
529  else if (per < 0.0)
530  per = 0.0;
531 
532  double r = m_startColor.red() +
533  (m_targetColor.red() - m_startColor.red()) * (per * per);
534  double g = m_startColor.green() +
535  (m_targetColor.green() - m_startColor.green()) * (per * per);
536  double b = m_startColor.blue() +
537  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
538 
539  if (r > 255.0)
540  r = 255.0;
541  else if (r < 0.0)
542  r = 0;
543 
544  if (g > 255.0)
545  g = 255.0;
546  else if (g < 0.0)
547  g = 0;
548 
549  if (b > 255.0)
550  b = 255.0;
551  else if (b < 0.0)
552  b = 0;
553 
554  p->setPen(QColor(int(r), int(g), int(b)));
555 #else
556  p->setPen(Qt::red);
557 #endif
558  double adjHeight = static_cast<double>(m_size.height()) / 2.0;
559  p->drawLine( i - 1,
560  (int)(adjHeight - m_magnitudes[ i - 1 ]),
561  i,
562  (int)(adjHeight - m_magnitudes[ i ] ));
563  }
564 
565  return true;
566 }
567 
569 // WaveForm - see whole track - by twitham@sbcglobal.net, 2023/01
570 
571 // As/of v34, switching from small preview to fullscreen destroys and
572 // recreates this object. So I use this static to survive which works
573 // only becasue there is 1 instance per process. If mythfrontend ever
574 // decides to display more than one visual at a time, it should also
575 // be fixed to not destroy the instance but rather to resize it
576 // dynamically. At that time, this could return to an m_image class
577 // member. But more of this file might also need refactored to enable
578 // long-life resizable visuals.
579 
580 QImage WaveForm::s_image {nullptr}; // picture of full track
581 
583 {
584  saveload(nullptr);
585  LOG(VB_PLAYBACK, LOG_INFO, QString("WF going down"));
586 }
587 
588 // cache current track, if any, before loading cache of given track
590 {
591  QString cache = GetConfDir() + "/MythMusic/WaveForm";
592  QString filename;
594  if (m_currentMetadata) // cache work in progress for next time
595  {
596  QDir dir(cache);
597  if (!dir.exists())
598  {
599  dir.mkpath(cache);
600  }
601  filename = QString("%1/%2.png").arg(cache)
602  .arg(m_stream ? 0 : m_currentMetadata->ID());
603  LOG(VB_PLAYBACK, LOG_INFO, QString("WF saving to %1").arg(filename));
604  if (!s_image.save(filename))
605  {
606  LOG(VB_GENERAL, LOG_ERR,
607  QString("WF saving to %1 failed: " + ENO).arg(filename));
608  }
609  }
610  m_currentMetadata = meta;
611  if (meta) // load previous work from cache
612  {
613  filename = QString("%1/%2.png").arg(cache).arg(m_stream ? 0 : meta->ID());
614  LOG(VB_PLAYBACK, LOG_INFO, QString("WF loading from %1").arg(filename));
615  if (!s_image.load(filename))
616  {
617  LOG(VB_GENERAL, LOG_WARNING,
618  QString("WF loading %1 failed, recreating").arg(filename));
619  }
620  // 60 seconds skips pixels with < 44100 streams like 22050,
621  // but this is now compensated for by drawing wider "pixels"
622  m_duration = m_stream ? 60000 : meta->Length().count(); // millisecs
623  }
624  if (s_image.isNull())
625  {
626  s_image = QImage(m_wfsize.width(), m_wfsize.height(), QImage::Format_RGB32);
627  s_image.fill(qRgb(0, 0, 0));
628  }
629  m_minl = 0; // drop last pixel, prepare for first
630  m_maxl = 0;
631  m_sqrl = 0;
632  m_minr = 0;
633  m_maxr = 0;
634  m_sqrr = 0;
635  m_position = 0;
636  m_lastx = m_wfsize.width();
637  m_font = QApplication::font();
638  // m_font.setPointSize(14);
639  m_font.setPixelSize(m_size.height() / 60); // small to be mostly unnoticed
640 }
641 
642 unsigned long WaveForm::getDesiredSamples(void)
643 {
644  // could be an adjustable class member, but this hard code works well
645  return kWFAudioSize; // maximum samples per update, may get less
646 }
647 
649 {
650  // After 2023/01 bugfix below, processUndisplayed processes this
651  // node again after this process! If that is ever changed in
652  // mainvisual.cpp, then this would need adjusted.
653 
654  // StereoScope overlay must process only the displayed nodes
655  StereoScope::process(node);
656  return false; // update even when silent
657 }
658 
660 {
661  // In 2023/01, v32 had a bug in mainvisual.cpp that this never
662  // received any nodes. Once I fixed that:
663 
664  // < m_vis->processUndisplayed(node);
665  // > m_vis->processUndisplayed(m_nodes.first());
666 
667  // now this receives *all* nodes. So we don't need any processing
668  // in ::process above as that would double process.
669 
671  if (meta && meta != m_currentMetadata)
672  {
673  saveload(meta);
674  }
675  if (node && !s_image.isNull())
676  {
677  m_offset = node->m_offset.count() % m_duration; // for ::draw below
678  m_right = node->m_right;
679  uint n = node->m_length;
680  // LOG(VB_PLAYBACK, LOG_DEBUG,
681  // QString("WF process %1 samples at %2, display=%3").
682  // arg(n).arg(m_offset).arg(displayed));
683 
684 // TODO: interpolate timestamps to process correct samples per pixel
685 // rather than fitting all we get in 1 or more pixels
686 
687  for (uint i = 0; i < n; i++) // find min/max and sum of squares
688  {
689  short int val = node->m_left[i];
690  if (val > m_maxl) m_maxl = val;
691  if (val < m_minl) m_minl = val;
692  m_sqrl += static_cast<long>(val) * static_cast<long>(val);
693  if (m_right)
694  {
695  val = node->m_right[i];
696  if (val > m_maxr) m_maxr = val;
697  if (val < m_minr) m_minr = val;
698  m_sqrr += static_cast<long>(val) * static_cast<long>(val);
699  }
700  m_position++;
701  }
702  uint xx = m_wfsize.width() * m_offset / m_duration;
703  if (xx != m_lastx) // draw one finished line of min/max/rms
704  {
705  if (m_lastx > xx - 1 || // REW seek or right to left edge wrap
706  m_lastx < xx - 5) // FFWD seek
707  {
708  m_lastx = xx - 1;
709  }
710  int h = m_wfsize.height() / 4; // amplitude above or below zero
711  int y = m_wfsize.height() / 4; // left zero line
712  int yr = m_wfsize.height() * 3 / 4; // right zero line
713  if (!m_right)
714  { // mono - drop full waveform below StereoScope
715  y = yr;
716  }
717  // This "loop" runs only once except for short tracks or
718  // low sample rates that need some of the virtual "lines"
719  // to be drawn wider with more actual lines. I'd rather
720  // duplicate the vertical lines than draw rectangles since
721  // lines are the more common case. -twitham
722 
723  QPainter painter(&s_image);
724  for (uint x = m_lastx + 1; x <= xx; x++)
725  {
726  // LOG(VB_PLAYBACK, LOG_DEBUG,
727  // QString("WF painting at %1,%2/%3").arg(x).arg(y).arg(yr));
728 
729  // clear prior content of this column
730  if (m_stream) // clear 5 seconds of future, with wrap
731  {
732  painter.fillRect(x, 0, 32 * 5,
733  m_wfsize.height(), Qt::black);
734  painter.fillRect(x - m_wfsize.width(), 0, 32 * 5,
735  m_wfsize.height(), Qt::black);
736  } else { // preserve the future, if any
737  painter.fillRect(x, 0, 1,
738  m_wfsize.height(), Qt::black);
739  }
740 
741  // Audacity uses 50,50,200 and 100,100,220 - I'm going
742  // darker to better contrast the StereoScope overlay
743  painter.setPen(qRgb(25, 25, 150)); // peak-to-peak
744  painter.drawLine(x, y - h * m_maxl / 32768,
745  x, y - h * m_minl / 32768);
746  if (m_right)
747  {
748  painter.drawLine(x, yr - h * m_maxr / 32768,
749  x, yr - h * m_minr / 32768);
750  }
751  painter.setPen(qRgb(150, 25, 25)); // RMS
752  int rmsl = sqrt(m_sqrl / m_position) * y / 32768;
753  painter.drawLine(x, y - rmsl, x, y + rmsl);
754  if (m_right)
755  {
756  int rmsr = sqrt(m_sqrr / m_position) * y / 32768;
757  painter.drawLine(x, yr - rmsr, x, yr + rmsr);
758  painter.drawLine(x, m_wfsize.height() / 2, // L / R delta
759  x, m_wfsize.height() / 2 - rmsl + rmsr);
760  }
761  }
762  m_minl = 0; // reset metrics for next line
763  m_maxl = 0;
764  m_sqrl = 0;
765  m_minr = 0;
766  m_maxr = 0;
767  m_sqrr = 0;
768  m_position = 0;
769  m_lastx = xx;
770  }
771  }
772  return false;
773 }
774 
775 bool WaveForm::draw( QPainter *p, const QColor &back )
776 {
777  p->fillRect(0, 0, 0, 0, back); // no clearing, here to suppress warning
778  if (!s_image.isNull())
779  { // background, updated by ::process above
780  p->drawImage(0, 0, s_image.scaled(m_size,
781  Qt::IgnoreAspectRatio,
782  Qt::SmoothTransformation));
783  }
784 
785  StereoScope::draw(p, Qt::green); // green == no clearing!
786 
787  p->fillRect(m_size.width() * m_offset / m_duration, 0,
788  1, m_size.height(), Qt::darkGray);
789 
790  if (m_showtext && m_size.width() > 500) // metrics in corners
791  {
792  p->setPen(Qt::darkGray);
793  p->setFont(m_font);
794  QRect text(5, 5, m_size.width() - 10, m_size.height() - 10);
795  p->drawText(text, Qt::AlignTop | Qt::AlignLeft,
796  QString("%1:%2")
797  .arg(m_offset / 1000 / 60)
798  .arg(m_offset / 1000 % 60, 2, 10, QChar('0')));
799  p->drawText(text, Qt::AlignTop | Qt::AlignHCenter,
800  QString("%1%")
801  .arg(100.0 * m_offset / m_duration, 0, 'f', 0));
802  p->drawText(text, Qt::AlignTop | Qt::AlignRight,
803  QString("%1:%2")
804  .arg(m_duration / 1000 / 60)
805  .arg(m_duration / 1000 % 60, 2, 10, QChar('0')));
806  p->drawText(text, Qt::AlignBottom | Qt::AlignLeft,
807  QString("%1 lines/s")
808  .arg(1000.0 * m_wfsize.width() / m_duration, 0, 'f', 1));
809  p->drawText(text, Qt::AlignBottom | Qt::AlignRight,
810  QString("%1 ms/line")
811  .arg(1.0 * m_duration / m_wfsize.width(), 0, 'f', 1));
812  }
813  return true;
814 }
815 
816 void WaveForm::handleKeyPress(const QString &action)
817 {
818  LOG(VB_PLAYBACK, LOG_INFO, QString("WF keypress = %1").arg(action));
819 
820  // I'd like to toggle overlay text upon certain key hit, but
821  // mythfrontend doesn't appear to call this. Bug?
822  if (action == "SELECT")
823  {
825  }
826 }
827 
829 // StereoScopeFactory
830 
831 static class StereoScopeFactory : public VisFactory
832 {
833  public:
834  const QString &name(void) const override // VisFactory
835  {
836  static QString s_name = QCoreApplication::translate("Visualizers",
837  "StereoScope");
838  return s_name;
839  }
840 
841  uint plugins(QStringList *list) const override // VisFactory
842  {
843  *list << name();
844  return 1;
845  }
846 
847  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
848  {
849  return new StereoScope();
850  }
852 
853 
855 // MonoScopeFactory
856 
857 static class MonoScopeFactory : public VisFactory
858 {
859  public:
860  const QString &name(void) const override // VisFactory
861  {
862  static QString s_name = QCoreApplication::translate("Visualizers",
863  "MonoScope");
864  return s_name;
865  }
866 
867  uint plugins(QStringList *list) const override // VisFactory
868  {
869  *list << name();
870  return 1;
871  }
872 
873  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
874  {
875  return new MonoScope();
876  }
878 
880 // WaveFormFactory
881 
882 static class WaveFormFactory : public VisFactory
883 {
884  public:
885  const QString &name(void) const override // VisFactory
886  {
887  static QString s_name = QCoreApplication::translate("Visualizers",
888  "WaveForm");
889  return s_name;
890  }
891 
892  uint plugins(QStringList *list) const override // VisFactory
893  {
894  *list << name();
895  return 1;
896  }
897 
899  MainVisual */*parent*/, const QString &/*pluginName*/) const override
900  {
901  return new WaveForm();
902  }
904 
906 // Spectrogram - by twitham@sbcglobal.net, 2023/05
907 //
908 // A spectrogram needs the entire sound signal. A wide sliding window
909 // is needed to detect low frequencies, 2^14 = 16384 works great. But
910 // then the detected frequencies can linger up to 16384/44100=372
911 // milliseconds! We solve this by shrinking the signal amplitude over
912 // time within the window. This way all frequencies are still there
913 // but only recent time has high amplitude. This nicely displays even
914 // quick sounds of a few milliseconds.
915 
916 // Static class members survive size changes for continuous display.
917 // See the comment about s_image in WaveForm
918 QImage Spectrogram::s_image {nullptr}; // picture of spectrogram
919 int Spectrogram::s_offset {0}; // position on screen
920 
922 {
923  LOG(VB_GENERAL, LOG_INFO,
924  QString("Spectrogram : Being Initialised, history=%1").arg(hist));
925 
926  m_history = hist; // historical spectrogram?, else spectrum only
927 
928  m_fps = 40; // getting 1152 samples / 44100 = 38.28125 fps
929 
930  if (s_image.isNull()) // static histogram survives resize/restart
931  {
932  s_image = QImage(
933  m_sgsize.width(), m_sgsize.height(), QImage::Format_RGB32);
934  s_image.fill(Qt::black);
935  }
936  if (m_history)
937  {
938  m_image = &s_image;
939  }
940  else
941  {
942  m_image = new QImage(
943  m_sgsize.width(), m_sgsize.height(), QImage::Format_RGB32);
944  m_image->fill(Qt::black);
945  }
946 
947  m_dftL = static_cast<FFTSample*>(av_malloc(sizeof(FFTSample) * m_fftlen));
948  m_dftR = static_cast<FFTSample*>(av_malloc(sizeof(FFTSample) * m_fftlen));
949 
950  m_rdftContext = av_rdft_init(std::log2(m_fftlen), DFT_R2C);
951 
952  // hack!!! Should 44100 sample rate be queried or measured?
953  // Likely close enough for most audio recordings...
955  m_sgsize.height() / 2 : m_sgsize.width(), 44100/2);
956  m_sigL.resize(m_fftlen);
957  m_sigR.resize(m_fftlen);
958 
959  // TODO: promote this to a separate ColorSpectrum class
960 
961  // cache a continuous color spectrum ([0-1535] = 256 colors * 6 ramps):
962  // 0000ff blue from here, G of RGB ramps UP to:
963  // 00ffff cyan then B ramps down to:
964  // 00ff00 green then R ramps UP to:
965  // ffff00 yellow then G ramps down to:
966  // ff0000 red then B ramps UP to:
967  // ff00ff magenta then R ramps down to:
968  // 0000ff blue we end where we started!
969  static constexpr int UP { 2 };
970  static constexpr int DN { 3 };
971  static const std::array<int,6> red { 0, 0, UP, 1, 1, DN }; // 0=OFF, 1=ON
972  static const std::array<int,6> green { UP, 1, 1, DN, 0, 0 };
973  static const std::array<int,6> blue { 1, DN, 0, 0, UP, 1 };
974  for (int i = 0; i < 6; i++) // for 6 color transitions...
975  {
976  int r = red[i]; // 0=OFF, 1=ON, UP, or DN
977  int g = green[i];
978  int b = blue[i];
979  for (int u = 0; u < 256; u++) { // u ramps up
980  int d = 256 - u; // d ramps down
981  m_red[ i * 256 + u] = r == UP ? u : r == DN ? d : r * 255;
982  m_green[i * 256 + u] = g == UP ? u : g == DN ? d : g * 255;
983  m_blue[ i * 256 + u] = b == UP ? u : b == DN ? d : b * 255;
984  }
985  }
986 }
987 
989 {
990  av_freep(&m_dftL);
991  av_freep(&m_dftR);
992  av_rdft_end(m_rdftContext);
993 }
994 
995 void Spectrogram::resize(const QSize &newsize)
996 {
997  m_size = newsize;
998 }
999 
1000 // this moved up from Spectrum so both can use it
1001 template<typename T> T sq(T a) { return a*a; };
1002 
1004 {
1005  // maximum samples per update, may get less
1006  return (unsigned long)kSGAudioSize;
1007 }
1008 
1010 {
1011  // TODO: label certain frequencies instead: 50, 100, 200, 500,
1012  // 1000, 2000, 5000, 10000
1013 
1014  // QPainter painter(m_image);
1015  // painter.setPen(Qt::cyan);
1016  // QFont font = QApplication::font();
1017  // font.setPixelSize(14);
1018  // painter.setFont(font);
1019  // if (m_history)
1020  // {
1021  // for (auto h = m_sgsize.height(); h > 0; h -= m_sgsize.height() / 2)
1022  // {
1023  // for (auto i = 0; i < m_sgsize.height() / 2; i += 20)
1024  // {
1025  // painter.drawText(0, h - i,
1026  // QString("...%1.%2.%3...").arg(i).arg(m_scale[i])
1027  // .arg(m_scale[i] * 22050 / 8192)); // hack!!!
1028  // }
1029  // }
1030  // } else {
1031  // painter.rotate(90);
1032  // for (auto i = 0; i < m_sgsize.width(); i += 20)
1033  // {
1034  // painter.drawText(0, -1 * i,
1035  // QString("...%1.%2.%3...").arg(i).arg(m_scale[i])
1036  // .arg(m_scale[i] * 22050 / 8192)); // hack!!!
1037  // }
1038  // }
1039  return false;
1040 }
1041 
1043 {
1044  // as of v33, this processes *all* samples, see the comments in WaveForm
1045 
1046  int w = m_sgsize.width(); // drawing size
1047  int h = m_sgsize.height();
1048  if (m_image->isNull())
1049  {
1050  m_image = new QImage(w, h, QImage::Format_RGB32);
1051  m_image->fill(Qt::black);
1052  s_offset = 0;
1053  }
1054  if (node) // shift previous samples left, then append new node
1055  {
1056  // LOG(VB_PLAYBACK, LOG_DEBUG, QString("SG got %1 samples").arg(node->m_length));
1057  int i = std::min((int)(node->m_length), m_fftlen);
1058  int start = m_fftlen - i;
1059  float mult = 0.8F; // decay older sound by this much
1060  for (int k = 0; k < start; k++)
1061  { // prior set ramps from mult to 1.0
1062  if (k > start - i && start > i)
1063  {
1064  mult = mult + (1 - mult) *
1065  (1 - (float)(start - k) / (float)(start - i));
1066  }
1067  m_sigL[k] = mult * m_sigL[i + k];
1068  m_sigR[k] = mult * m_sigR[i + k];
1069  }
1070  for (int k = 0; k < i; k++) // append current samples
1071  {
1072  m_sigL[start + k] = node->m_left[k] / 32768.; // +/- 1 peak-to-peak
1073  if (node->m_right)
1074  m_sigR[start + k] = node->m_right[k] / 32768.;
1075  }
1076  int end = m_fftlen / 40; // ramp window ends down to zero crossing
1077  for (int k = 0; k < m_fftlen; k++)
1078  {
1079  mult = k < end ? (float)k / (float)end
1080  : k > m_fftlen - end ?
1081  (float)(m_fftlen - k) / (float)end : 1;
1082  m_dftL[k] = m_sigL[k] * mult;
1083  m_dftR[k] = m_sigR[k] * mult;
1084  }
1085  }
1086  av_rdft_calc(m_rdftContext, m_dftL); // run the real FFT!
1087  av_rdft_calc(m_rdftContext, m_dftR);
1088 
1089  QPainter painter(m_image);
1090  painter.setPen(Qt::black); // clear prior content
1091  if (m_history)
1092  {
1093  painter.fillRect(s_offset, 0, 256, h, Qt::black);
1094  painter.fillRect(s_offset - w, 0, 256, h, Qt::black);
1095  } else {
1096  painter.fillRect(0, 0, w, h, Qt::black);
1097  }
1098 
1099  int index = 1; // frequency index of this pixel
1100  int prev = 0; // frequency index of previous pixel
1101  float gain = 5.0; // compensate for window function loss
1102  for (int i = 1; i < (m_history ? h / 2 : w); i++)
1103  { // for each pixel of the spectrogram...
1104  float left = 0;
1105  float right = 0;
1106  float tmp = 0;
1107  int count = 0;
1108  for (auto j = prev + 1; j <= index; j++) // log scale!
1109  { // for the freqency bins of this pixel, find peak or mean
1110  tmp = sq(m_dftL[2 * j]) + sq(m_dftL[2 * j + 1]);
1111  left = m_binpeak ? (tmp > left ? tmp : left ) : left + tmp;
1112  tmp = sq(m_dftR[2 * j]) + sq(m_dftR[2 * j + 1]);
1113  right = m_binpeak ? (tmp > right ? tmp : right) : right + tmp;
1114  count++;
1115  }
1116  if (!m_binpeak && count > 0)
1117  { // mean of the frequency bins
1118  left /= count;
1119  right /= count;
1120  }
1121  // linear magnitude: sqrt(sq(real) + sq(im));
1122  // left = sqrt(left);
1123  // right = sqrt(right);
1124  // power spectrum (dBm): 10 * log10(sq(real) + sq(im));
1125  left = 10 * log10(left);
1126  right = 10 * log10(right);
1127 
1128  // float bw = 1. / (16384. / 44100.);
1129  // float freq = bw * index;
1130  // LOG(VB_PLAYBACK, LOG_DEBUG, // verbose - never use in production!!!
1131  // QString("SG i=%1, index=%2 (%3) %4 hz \tleft=%5,\tright=%6")
1132  // .arg(i).arg(index).arg(count).arg(freq).arg(left).arg(right));
1133 
1134  left *= gain;
1135  int mag = clamp(left, 255, 0);
1136  int z = (int)(mag * 6);
1137  left > 255 ? painter.setPen(Qt::white) :
1138  painter.setPen(qRgb(m_red[z], m_green[z], m_blue[z]));
1139  if (m_history)
1140  {
1141  h = m_sgsize.height() / 2;
1142  painter.drawLine(s_offset, h - i, s_offset + mag, h - i);
1143  painter.drawLine(s_offset - w, h - i, s_offset - w + mag, h - i);
1144  left > 255 ? painter.setPen(Qt::yellow) :
1145  painter.setPen(qRgb(mag, mag, mag));
1146  painter.drawPoint(s_offset, h - i);
1147  } else {
1148  painter.drawLine(i, h / 2, i, h / 2 - h / 2 * mag / 256);
1149  }
1150 
1151  right *= gain; // copy of above, s/left/right/g
1152  mag = clamp(right, 255, 0);
1153  z = (int)(mag * 6);
1154  right > 255 ? painter.setPen(Qt::white) :
1155  painter.setPen(qRgb(m_red[z], m_green[z], m_blue[z]));
1156  if (m_history)
1157  {
1158  h = m_sgsize.height();
1159  painter.drawLine(s_offset, h - i, s_offset + mag, h - i);
1160  painter.drawLine(s_offset - w, h - i, s_offset - w + mag, h - i);
1161  right > 255 ? painter.setPen(Qt::yellow) :
1162  painter.setPen(qRgb(mag, mag, mag));
1163  painter.drawPoint(s_offset, h - i);
1164  } else {
1165  painter.drawLine(i, h / 2, i, h / 2 + h / 2 * mag / 256);
1166  }
1167 
1168  prev = index; // next pixel is FFT bins from here
1169  index = m_scale[i]; // to the next bin by LOG scale
1170  prev = std::min(prev, index - 1);
1171  }
1172  if (m_history && ++s_offset >= w)
1173  {
1174  s_offset = 0;
1175  }
1176  return false;
1177 }
1178 
1179 double Spectrogram::clamp(double cur, double max, double min)
1180 {
1181  if (isnan(cur)) return 0;
1182  if (cur > max) cur = max;
1183  if (cur < min) cur = min;
1184  return cur;
1185 }
1186 
1187 bool Spectrogram::draw(QPainter *p, const QColor &back)
1188 {
1189  p->fillRect(0, 0, 0, 0, back); // no clearing, here to suppress warning
1190  if (!m_image->isNull())
1191  { // background, updated by ::process above
1192  p->drawImage(0, 0, m_image->scaled(m_size,
1193  Qt::IgnoreAspectRatio,
1194  Qt::SmoothTransformation));
1195  }
1196  // // DEBUG: see the whole signal going into the FFT:
1197  // if (m_size.height() > 1000) {
1198  // p->setPen(Qt::yellow);
1199  // float h = m_size.height() / 10;
1200  // for (int j = 0; j < m_fftlen; j++) {
1201  // p->drawPoint(j % m_size.width(), int(j / m_size.width()) * h + h - h * m_sigL[j]);
1202  // }
1203  // }
1204  return true;
1205 }
1206 
1208 {
1209  LOG(VB_PLAYBACK, LOG_INFO, QString("SG keypress = %1").arg(action));
1210 
1211  // I'd like to tweak options upon certain key hit, but
1212  // mythfrontend doesn't appear to call this. Bug?
1213  if (action == "SELECT")
1214  {
1215  LOG(VB_PLAYBACK, LOG_INFO, QString("SG keypress SELECT hit"));
1216  }
1217 }
1218 
1219 // passing true to the above constructor gives the spectrum with full history:
1220 
1221 static class SpectrogramFactory : public VisFactory
1222 {
1223  public:
1224  const QString &name(void) const override // VisFactory
1225  {
1226  static QString s_name = QCoreApplication::translate("Visualizers",
1227  "Spectrogram");
1228  return s_name;
1229  }
1230 
1231  uint plugins(QStringList *list) const override // VisFactory
1232  {
1233  *list << name();
1234  return 1;
1235  }
1236 
1237  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1238  {
1239  return new Spectrogram(true); // history
1240  }
1242 
1243 // passing false to the above constructor gives the spectrum with no history:
1244 
1245 static class SpectrumDetailFactory : public VisFactory
1246 {
1247  public:
1248  const QString &name(void) const override // VisFactory
1249  {
1250  static QString s_name = QCoreApplication::translate("Visualizers",
1251  "Spectrum");
1252  return s_name;
1253  }
1254 
1255  uint plugins(QStringList *list) const override // VisFactory
1256  {
1257  *list << name();
1258  return 1;
1259  }
1260 
1261  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1262  {
1263  return new Spectrogram(false); // no history
1264  }
1266 
1268 // Spectrum
1269 //
1270 
1272 {
1273  LOG(VB_GENERAL, LOG_INFO, QString("Spectrum : Being Initialised"));
1274 
1275  m_fps = 40; // getting 1152 samples / 44100 = 38.28125 fps
1276 
1277  m_dftL = static_cast<FFTSample*>(av_malloc(sizeof(FFTSample) * m_fftlen));
1278  m_dftR = static_cast<FFTSample*>(av_malloc(sizeof(FFTSample) * m_fftlen));
1279 
1280  m_rdftContext = av_rdft_init(std::log2(m_fftlen), DFT_R2C);
1281 }
1282 
1284 {
1285  av_freep(&m_dftL);
1286  av_freep(&m_dftR);
1287  av_rdft_end(m_rdftContext);
1288 }
1289 
1290 void Spectrum::resize(const QSize &newsize)
1291 {
1292  // Just change internal data about the
1293  // size of the pixmap to be drawn (ie. the
1294  // size of the screen) and the logically
1295  // ensuing number of up/down bars to hold
1296  // the audio magnitudes
1297 
1298  m_size = newsize;
1299 
1300  m_analyzerBarWidth = m_size.width() / 128;
1301 
1302  if (m_analyzerBarWidth < 6)
1303  m_analyzerBarWidth = 6;
1304 
1305  m_scale.setMax(m_fftlen/2, m_size.width() / m_analyzerBarWidth, 44100/2);
1306  m_sigL.resize(m_fftlen);
1307  m_sigR.resize(m_fftlen);
1308 
1309  m_rectsL.resize( m_scale.range() );
1310  m_rectsR.resize( m_scale.range() );
1311  int w = 0;
1312  // NOLINTNEXTLINE(modernize-loop-convert)
1313  for (uint i = 0; i < (uint)m_rectsL.size(); i++, w += m_analyzerBarWidth)
1314  {
1315  m_rectsL[i].setRect(w, m_size.height() / 2, m_analyzerBarWidth - 1, 1);
1316  m_rectsR[i].setRect(w, m_size.height() / 2, m_analyzerBarWidth - 1, 1);
1317  }
1318 
1319  m_magnitudes.resize( m_scale.range() * 2 );
1320  // NOLINTNEXTLINE(modernize-loop-convert)
1321  for (uint os = m_magnitudes.size(); os < (uint)m_magnitudes.size(); os++)
1322  {
1323  m_magnitudes[os] = 0.0;
1324  }
1325 
1326  m_scaleFactor = m_size.height() / 2. / 42.;
1327 }
1328 
1329 // this moved up to Spectrogram so both can use it
1330 // template<typename T> T sq(T a) { return a*a; };
1331 
1333 {
1334  return false;
1335 }
1336 
1338 {
1339  // copied from Spectrogram for better FFT window
1340 
1341  if (node) // shift previous samples left, then append new node
1342  {
1343  // LOG(VB_PLAYBACK, LOG_DEBUG, QString("SG got %1 samples").arg(node->m_length));
1344  int i = std::min((int)(node->m_length), m_fftlen);
1345  int start = m_fftlen - i;
1346  float mult = 0.8F; // decay older sound by this much
1347  for (int k = 0; k < start; k++)
1348  { // prior set ramps from mult to 1.0
1349  if (k > start - i && start > i)
1350  {
1351  mult = mult + (1 - mult) *
1352  (1 - (float)(start - k) / (float)(start - i));
1353  }
1354  m_sigL[k] = mult * m_sigL[i + k];
1355  m_sigR[k] = mult * m_sigR[i + k];
1356  }
1357  for (int k = 0; k < i; k++) // append current samples
1358  {
1359  m_sigL[start + k] = node->m_left[k] / 32768.; // +/- 1 peak-to-peak
1360  if (node->m_right)
1361  m_sigR[start + k] = node->m_right[k] / 32768.;
1362  }
1363  int end = m_fftlen / 40; // ramp window ends down to zero crossing
1364  for (int k = 0; k < m_fftlen; k++)
1365  {
1366  mult = k < end ? k / end : k > m_fftlen - end ?
1367  (m_fftlen - k) / end : 1;
1368  m_dftL[k] = m_sigL[k] * mult;
1369  m_dftR[k] = m_sigR[k] * mult;
1370  }
1371  }
1372  av_rdft_calc(m_rdftContext, m_dftL); // run the real FFT!
1373  av_rdft_calc(m_rdftContext, m_dftR);
1374 
1375  long w = 0;
1376  QRect *rectspL = m_rectsL.data();
1377  QRect *rectspR = m_rectsR.data();
1378  float *magnitudesp = m_magnitudes.data();
1379 
1380  int index = 1; // frequency index of this pixel
1381  int prev = 0; // frequency index of previous pixel
1382  float adjHeight = m_size.height() / 2.0;
1383 
1384  for (int i = 0; (int)i < m_rectsL.size(); i++, w += m_analyzerBarWidth)
1385  {
1386  float magL = 0; // modified from Spectrogram
1387  float magR = 0;
1388  float tmp = 0;
1389  for (auto j = prev + 1; j <= index; j++) // log scale!
1390  { // for the freqency bins of this pixel, find peak or mean
1391  tmp = sq(m_dftL[2 * j]) + sq(m_dftL[2 * j + 1]);
1392  magL = tmp > magL ? tmp : magL;
1393  tmp = sq(m_dftR[2 * j]) + sq(m_dftR[2 * j + 1]);
1394  magR = tmp > magR ? tmp : magR;
1395  }
1396  magL = 10 * log10(magL) * m_scaleFactor;
1397  magR = 10 * log10(magR) * m_scaleFactor;
1398 
1399  if (magL > adjHeight)
1400  {
1401  magL = adjHeight;
1402  }
1403  if (magL < magnitudesp[i])
1404  {
1405  tmp = magnitudesp[i] - m_falloff;
1406  if ( tmp < magL )
1407  {
1408  tmp = magL;
1409  }
1410  magL = tmp;
1411  }
1412  if (magL < 1)
1413  {
1414  magL = 1;
1415  }
1416 
1417  if (magR > adjHeight)
1418  {
1419  magR = adjHeight;
1420  }
1421  if (magR < magnitudesp[i + m_scale.range()])
1422  {
1423  tmp = magnitudesp[i + m_scale.range()] - m_falloff;
1424  if ( tmp < magR )
1425  {
1426  tmp = magR;
1427  }
1428  magR = tmp;
1429  }
1430  if (magR < 1)
1431  {
1432  magR = 1;
1433  }
1434 
1435  magnitudesp[i] = magL;
1436  magnitudesp[i + m_scale.range()] = magR;
1437  rectspL[i].setTop( m_size.height() / 2 - int( magL ) );
1438  rectspR[i].setBottom( m_size.height() / 2 + int( magR ) );
1439 
1440  prev = index; // next pixel is FFT bins from here
1441  index = m_scale[i]; // to the next bin by LOG scale
1442  (prev < index) || (prev = index -1);
1443  }
1444 
1445  return false;
1446 }
1447 
1448 double Spectrum::clamp(double cur, double max, double min)
1449 {
1450  if (cur > max)
1451  cur = max;
1452  if (cur < min)
1453  cur = min;
1454  return cur;
1455 }
1456 
1457 bool Spectrum::draw(QPainter *p, const QColor &back)
1458 {
1459  // This draws on a pixmap owned by MainVisual.
1460  //
1461  // In other words, this is not a Qt Widget, it
1462  // just uses some Qt methods to draw on a pixmap.
1463  // MainVisual then bitblts that onto the screen.
1464 
1465  QRect *rectspL = m_rectsL.data();
1466  QRect *rectspR = m_rectsR.data();
1467 
1468  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
1469  for (uint i = 0; i < (uint)m_rectsL.size(); i++)
1470  {
1471  double per = ( rectspL[i].height() - 2. ) / (m_size.height() / 2.);
1472 
1473  per = clamp(per, 1.0, 0.0);
1474 
1475  double r = m_startColor.red() +
1476  (m_targetColor.red() - m_startColor.red()) * (per * per);
1477  double g = m_startColor.green() +
1478  (m_targetColor.green() - m_startColor.green()) * (per * per);
1479  double b = m_startColor.blue() +
1480  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
1481 
1482  r = clamp(r, 255.0, 0.0);
1483  g = clamp(g, 255.0, 0.0);
1484  b = clamp(b, 255.0, 0.0);
1485 
1486  if(rectspL[i].height() > 4)
1487  p->fillRect(rectspL[i], QColor(int(r), int(g), int(b)));
1488 
1489  per = ( rectspR[i].height() - 2. ) / (m_size.height() / 2.);
1490 
1491  per = clamp(per, 1.0, 0.0);
1492 
1493  r = m_startColor.red() +
1494  (m_targetColor.red() - m_startColor.red()) * (per * per);
1495  g = m_startColor.green() +
1496  (m_targetColor.green() - m_startColor.green()) * (per * per);
1497  b = m_startColor.blue() +
1498  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
1499 
1500  r = clamp(r, 255.0, 0.0);
1501  g = clamp(g, 255.0, 0.0);
1502  b = clamp(b, 255.0, 0.0);
1503 
1504  if(rectspR[i].height() > 4)
1505  p->fillRect(rectspR[i], QColor(int(r), int(g), int(b)));
1506  }
1507 
1508  return true;
1509 }
1510 
1511 static class SpectrumFactory : public VisFactory
1512 {
1513  public:
1514  const QString &name(void) const override // VisFactory
1515  {
1516  static QString s_name = QCoreApplication::translate("Visualizers",
1517  "SpectrumBars");
1518  return s_name;
1519  }
1520 
1521  uint plugins(QStringList *list) const override // VisFactory
1522  {
1523  *list << name();
1524  return 1;
1525  }
1526 
1527  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1528  {
1529  return new Spectrum();
1530  }
1532 
1534 // Squares
1535 //
1536 
1538 {
1540 }
1541 
1542 void Squares::resize (const QSize &newsize) {
1543  // Trick the spectrum analyzer into calculating 16 rectangles
1545  // We have our own copy, Spectrum has it's own...
1546  m_actualSize = newsize;
1547 }
1548 
1549 void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h)
1550 {
1551  double per = NAN;
1552  int correction = (m_actualSize.width() % m_rectsL.size ());
1553  int x = ((i / 2) * w) + correction;
1554  int y = 0;
1555 
1556  if (i % 2 == 0)
1557  {
1558  y = c - h;
1559  per = double(m_fakeHeight - rect->top()) / double(m_fakeHeight);
1560  }
1561  else
1562  {
1563  y = c;
1564  per = double(rect->bottom()) / double(m_fakeHeight);
1565  }
1566 
1567  per = clamp(per, 1.0, 0.0);
1568 
1569  double r = m_startColor.red() +
1570  (m_targetColor.red() - m_startColor.red()) * (per * per);
1571  double g = m_startColor.green() +
1572  (m_targetColor.green() - m_startColor.green()) * (per * per);
1573  double b = m_startColor.blue() +
1574  (m_targetColor.blue() - m_startColor.blue()) * (per * per);
1575 
1576  r = clamp(r, 255.0, 0.0);
1577  g = clamp(g, 255.0, 0.0);
1578  b = clamp(b, 255.0, 0.0);
1579 
1580  p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
1581 }
1582 
1583 bool Squares::draw(QPainter *p, const QColor &back)
1584 {
1585  p->fillRect (0, 0, m_actualSize.width(), m_actualSize.height(), back);
1586  int w = m_actualSize.width() / (m_rectsL.size() / 2);
1587  int h = w;
1588  int center = m_actualSize.height() / 2;
1589 
1590  QRect *rectsp = m_rectsL.data();
1591  for (uint i = 0; i < (uint)m_rectsL.size() * 2; i += 2)
1592  drawRect(p, &(rectsp[i]), i, center, w, h);
1593  rectsp = m_rectsR.data();
1594  for (uint i = 1; i < (uint)m_rectsR.size() * 2 + 1; i += 2)
1595  drawRect(p, &(rectsp[i]), i, center, w, h);
1596 
1597  return true;
1598 }
1599 
1600 static class SquaresFactory : public VisFactory
1601 {
1602  public:
1603  const QString &name(void) const override // VisFactory
1604  {
1605  static QString s_name = QCoreApplication::translate("Visualizers",
1606  "Squares");
1607  return s_name;
1608  }
1609 
1610  uint plugins(QStringList *list) const override // VisFactory
1611  {
1612  *list << name();
1613  return 1;
1614  }
1615 
1616  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
1617  {
1618  return new Squares();
1619  }
1621 
1622 
1624 {
1625  // Setup the "magical" audio coefficients
1626  // required by the Goetzel Algorithm
1627 
1628  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Initialised"));
1629 
1630  m_pianoData = (piano_key_data *) malloc(sizeof(piano_key_data) * kPianoNumKeys);
1631  m_audioData = (piano_audio *) malloc(sizeof(piano_audio) * kPianoAudioSize);
1632 
1633  double sample_rate = 44100.0; // TODO : This should be obtained from gPlayer (likely candidate...)
1634 
1635  m_fps = 20; // This is the display frequency. We're capturing all audio chunks by defining .process_undisplayed() though.
1636 
1637  double concert_A = 440.0;
1638  double semi_tone = pow(2.0, 1.0/12.0);
1639 
1640  /* Lowest note on piano is 4 octaves below concert A */
1641  double bottom_A = concert_A / 2.0 / 2.0 / 2.0 / 2.0;
1642 
1643  double current_freq = bottom_A;
1644  for (uint key = 0; key < kPianoNumKeys; key++)
1645  {
1646  // This is constant through time
1647  m_pianoData[key].coeff = (goertzel_data)(2.0 * cos(2.0 * M_PI * current_freq / sample_rate));
1648 
1649  // Want 20 whole cycles of the current waveform at least
1650  double samples_required = sample_rate/current_freq * 20.0;
1651  if (samples_required > sample_rate/4.0)
1652  {
1653  // For the really low notes, 4 updates a second is good enough...
1654  samples_required = sample_rate/4.0;
1655  }
1656  if (samples_required < sample_rate/(double)m_fps * 0.75)
1657  { // For the high notes, use as many samples as we need in a display_fps
1658  samples_required = sample_rate/(double)m_fps * 0.75;
1659  }
1660  m_pianoData[key].samples_process_before_display_update = (int)samples_required;
1661  m_pianoData[key].is_black_note = false; // Will be put right in .resize()
1662 
1663  current_freq *= semi_tone;
1664  }
1665 
1666  zero_analysis();
1667 }
1668 
1670 {
1671  if (m_pianoData)
1672  free(m_pianoData);
1673  if (m_audioData)
1674  free(m_audioData);
1675 }
1676 
1678 {
1679  for (uint key = 0; key < kPianoNumKeys; key++)
1680  {
1681  // These get updated continously, and must be stored between chunks of audio data
1682  m_pianoData[key].q2 = 0.0F;
1683  m_pianoData[key].q1 = 0.0F;
1684  m_pianoData[key].magnitude = 0.0F;
1686  (goertzel_data)(kPianoRmsNegligible * kPianoRmsNegligible); // This is a guess - will be quickly overwritten
1687 
1688  m_pianoData[key].samples_processed = 0;
1689  }
1690  m_offsetProcessed = 0ms;
1691 }
1692 
1693 void Piano::resize(const QSize &newsize)
1694 {
1695  // Just change internal data about the
1696  // size of the pixmap to be drawn (ie. the
1697  // size of the screen) and the logically
1698  // ensuing number of up/down bars to hold
1699  // the audio magnitudes
1700 
1701  m_size = newsize;
1702 
1703  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Resized"));
1704 
1705  zero_analysis();
1706 
1707  // There are 88-36=52 white notes on piano keyboard
1708  double key_unit_size = (double)m_size.width() / 54.0; // One white key extra spacing, if possible
1709  if (key_unit_size < 10.0) // Keys have to be at least this many pixels wide
1710  key_unit_size = 10.0;
1711 
1712  double white_width_pct = .8;
1713  double black_width_pct = .6;
1714  double black_offset_pct = .05;
1715 
1716  double white_height_pct = 6;
1717  double black_height_pct = 4;
1718 
1719  // This is the starting position of the keyboard (may be beyond LHS)
1720  // - actually position of C below bottom A (will be added to...). This is 4 octaves below middle C.
1721  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
1722  double top_of_keys = (double)m_size.height() / 2.0 - key_unit_size * white_height_pct / 2.0; // Vertically center keys
1723 
1724  m_rects.resize(kPianoNumKeys);
1725 
1726  for (uint key = 0; key < kPianoNumKeys; key++)
1727  {
1728  int note = ((int)key - 3 + 12) % 12; // This means that C=0, C#=1, D=2, etc (since lowest note is bottom A)
1729  if (note == 0) // If we're on a 'C', move the left 'cursor' over an octave
1730  {
1731  left += key_unit_size*7.0;
1732  }
1733 
1734  double center = 0.0;
1735  double offset = 0.0;
1736  bool is_black = false;
1737 
1738  switch (note)
1739  {
1740  case 0: center = 0.5; break;
1741  case 1: center = 1.0; is_black = true; offset = -1; break;
1742  case 2: center = 1.5; break;
1743  case 3: center = 2.0; is_black = true; offset = +1; break;
1744  case 4: center = 2.5; break;
1745  case 5: center = 3.5; break;
1746  case 6: center = 4.0; is_black = true; offset = -2; break;
1747  case 7: center = 4.5; break;
1748  case 8: center = 5.0; is_black = true; offset = 0; break;
1749  case 9: center = 5.5; break;
1750  case 10: center = 6.0; is_black = true; offset = 2; break;
1751  case 11: center = 6.5; break;
1752  }
1753  m_pianoData[key].is_black_note = is_black;
1754 
1755  double width = (is_black ? black_width_pct:white_width_pct) * key_unit_size;
1756  double height = (is_black? black_height_pct:white_height_pct) * key_unit_size;
1757 
1758  m_rects[key].setRect(
1759  left + center * key_unit_size // Basic position of left side of key
1760  - width / 2.0 // Less half the width
1761  + (is_black ? (offset * black_offset_pct * key_unit_size):0.0), // And jiggle the positions of the black keys for aethetic reasons
1762  top_of_keys, // top
1763  width, // width
1764  height // height
1765  );
1766  }
1767 
1768  m_magnitude.resize(kPianoNumKeys);
1769  for (double & key : m_magnitude)
1770  key = 0.0;
1771 }
1772 
1773 unsigned long Piano::getDesiredSamples(void)
1774 {
1775  // We want all the data! (within reason)
1776  // typical observed values are 882 -
1777  // 12.5 chunks of data per second from 44100Hz signal : Sampled at 50Hz, lots of 4, see :
1778  // mythtv/libs/libmyth/audio/audiooutputbase.cpp :: AudioOutputBase::AddData
1779  // See : mythtv/mythplugins/mythmusic/mythmusic/avfdecoder.cpp "20ms worth"
1780  return (unsigned long) kPianoAudioSize; // Maximum we can be given
1781 }
1782 
1784 {
1785  //LOG(VB_GENERAL, LOG_INFO, QString("Piano : Processing undisplayed node"));
1786  return process_all_types(node, false);
1787 }
1788 
1790 {
1791  //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node for DISPLAY"));
1792 
1793  // See WaveForm::process* above
1794  // return process_all_types(node, true);
1795 
1796  return false;
1797 }
1798 
1799 bool Piano::process_all_types(VisualNode *node, bool /*this_will_be_displayed*/)
1800 {
1801  // Take a bunch of data in *node and break it down into piano key spectrum values
1802  // NB: Remember the state data between calls, so as to accumulate more accurate results.
1803  bool allZero = true;
1804  uint n = 0;
1805 
1806  if (node)
1807  {
1808  piano_audio short_to_bounded = 32768.0F;
1809 
1810  // Detect start of new song (current node more than 10s earlier than already seen)
1811  if (node->m_offset + 10s < m_offsetProcessed)
1812  {
1813  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Node offset=%1 too far backwards : NEW SONG").arg(node->m_offset.count()));
1814  zero_analysis();
1815  }
1816 
1817  // Check whether we've seen this node (more recently than 10secs ago)
1818  if (node->m_offset <= m_offsetProcessed)
1819  {
1820  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Already seen node offset=%1, returning without processing").arg(node->m_offset.count()));
1821  return allZero; // Nothing to see here - the server can stop if it wants to
1822  }
1823 
1824  //LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node offset=%1, size=%2").arg(node->m_offset).arg(node->m_length));
1825  n = node->m_length;
1826 
1827  if (node->m_right) // Preprocess the data into a combined middle channel, if we have stereo data
1828  {
1829  for (uint i = 0; i < n; i++)
1830  {
1831  m_audioData[i] = ((piano_audio)node->m_left[i] + (piano_audio)node->m_right[i]) / 2.0F / short_to_bounded;
1832  }
1833  }
1834  else // This is only one channel of data
1835  {
1836  for (uint i = 0; i < n; i++)
1837  {
1838  m_audioData[i] = (piano_audio)node->m_left[i] / short_to_bounded;
1839  }
1840  }
1841  }
1842  else
1843  {
1844  LOG(VB_GENERAL, LOG_DEBUG, QString("Hit an empty node, and returning empty-handed"));
1845  return allZero; // Nothing to see here - the server can stop if it wants to
1846  }
1847 
1848  for (uint key = 0; key < kPianoNumKeys; key++)
1849  {
1850  goertzel_data coeff = m_pianoData[key].coeff;
1851  goertzel_data q2 = m_pianoData[key].q2;
1852  goertzel_data q1 = m_pianoData[key].q1;
1853 
1854  for (uint i = 0; i < n; i++)
1855  {
1856  goertzel_data q0 = coeff * q1 - q2 + m_audioData[i];
1857  q2 = q1;
1858  q1 = q0;
1859  }
1860  m_pianoData[key].q2 = q2;
1861  m_pianoData[key].q1 = q1;
1862 
1863  m_pianoData[key].samples_processed += n;
1864 
1865  int n_samples = m_pianoData[key].samples_processed;
1866 
1867  // Only do this update if we've processed enough chunks for this key...
1868  if (n_samples > m_pianoData[key].samples_process_before_display_update)
1869  {
1870  goertzel_data magnitude2 = q1*q1 + q2*q2 - q1*q2*coeff;
1871 
1872 #if 0
1873  // This is RMS of signal
1874  goertzel_data magnitude_av =
1875  sqrt(magnitude2)/(goertzel_data)n_samples; // Should be 0<magnitude_av<.5
1876 #else
1877  // This is pure magnitude of signal
1878  goertzel_data magnitude_av =
1879  magnitude2/(goertzel_data)n_samples/(goertzel_data)n_samples; // Should be 0<magnitude_av<.25
1880 #endif
1881 
1882 #if 0
1883  // Take logs everywhere, and shift up to [0, ??]
1884  if(magnitude_av > 0.0F)
1885  {
1886  magnitude_av = log(magnitude_av);
1887  }
1888  else
1889  {
1890  magnitude_av = kPianoMinVol;
1891  }
1892  magnitude_av -= kPianoMinVol;
1893 
1894  if (magnitude_av < 0.0F)
1895  {
1896  magnitude_av = 0.0;
1897  }
1898 #endif
1899 
1900  if (magnitude_av > (goertzel_data)0.01)
1901  {
1902  allZero = false;
1903  }
1904 
1905  m_pianoData[key].magnitude = magnitude_av; // Store this for later : We'll do the colours from this...
1906  if ( m_pianoData[key].max_magnitude_seen < magnitude_av)
1907  {
1908  m_pianoData[key].max_magnitude_seen = magnitude_av;
1909  }
1910  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Updated Key %1 from %2 samples, magnitude=%3")
1911  .arg(key).arg(n_samples).arg(magnitude_av));
1912 
1913  m_pianoData[key].samples_processed = 0; // Reset the counts, now that we've set the magnitude...
1914  m_pianoData[key].q1 = (goertzel_data)0.0;
1915  m_pianoData[key].q2 = (goertzel_data)0.0;
1916  }
1917  }
1918 
1919  if (node)
1920  {
1921  // All done now - record that we've done this offset
1922  m_offsetProcessed = node->m_offset;
1923  }
1924 
1925  return allZero;
1926 }
1927 
1928 double Piano::clamp(double cur, double max, double min)
1929 {
1930  if (cur > max)
1931  cur = max;
1932  if (cur < min)
1933  cur = min;
1934  return cur;
1935 }
1936 
1937 bool Piano::draw(QPainter *p, const QColor &back)
1938 {
1939  // This draws on a pixmap owned by MainVisual.
1940  //
1941  // In other words, this is not a Qt Widget, it
1942  // just uses some Qt methods to draw on a pixmap.
1943  // MainVisual then bitblts that onto the screen.
1944 
1945  QRect *rectsp = (m_rects).data();
1946  double *magnitudep = (m_magnitude).data();
1947 
1948  unsigned int n = kPianoNumKeys;
1949 
1950  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
1951 
1952  // Protect maximum array length
1953  if(n > (uint)m_rects.size())
1954  n = (uint)m_rects.size();
1955 
1956  // Sweep up across the keys, making sure the max_magnitude_seen is at minimum X% of its neighbours
1957  double mag = kPianoRmsNegligible;
1958  for (uint key = 0; key < n; key++)
1959  {
1960  if (m_pianoData[key].max_magnitude_seen < static_cast<float>(mag))
1961  {
1962  // Spread the previous value to this key
1963  m_pianoData[key].max_magnitude_seen = mag;
1964  }
1965  else
1966  {
1967  // This key has seen better peaks, use this for the next one
1968  mag = m_pianoData[key].max_magnitude_seen;
1969  }
1970  mag *= kPianoSpectrumSmoothing;
1971  }
1972 
1973  // Similarly, down, making sure the max_magnitude_seen is at minimum X% of its neighbours
1974  mag = kPianoRmsNegligible;
1975  for (int key_i = n - 1; key_i >= 0; key_i--)
1976  {
1977  uint key = key_i; // Wow, this is to avoid a zany error for ((unsigned)0)--
1978  if (m_pianoData[key].max_magnitude_seen < static_cast<float>(mag))
1979  {
1980  // Spread the previous value to this key
1981  m_pianoData[key].max_magnitude_seen = mag;
1982  }
1983  else
1984  {
1985  // This key has seen better peaks, use this for the next one
1986  mag = m_pianoData[key].max_magnitude_seen;
1987  }
1988  mag *= kPianoSpectrumSmoothing;
1989  }
1990 
1991  // Now find the key that has been hit the hardest relative to its experience, and renormalize...
1992  // Set a minimum, to prevent divide-by-zero (and also all-pressed when music very quiet)
1993  double magnitude_max = kPianoRmsNegligible;
1994  for (uint key = 0; key < n; key++)
1995  {
1997  if (magnitude_max < mag)
1998  magnitude_max = mag;
1999 
2000  magnitudep[key] = mag;
2001  }
2002 
2003  // Deal with all the white keys first
2004  for (uint key = 0; key < n; key++)
2005  {
2006  if (m_pianoData[key].is_black_note)
2007  continue;
2008 
2009  double per = magnitudep[key] / magnitude_max;
2010  per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
2011 
2012  if (per < kPianoKeypressTooLight)
2013  per = 0.0; // Clamp to zero for lightly detected keys
2014  LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Display key %1, magnitude=%2, seen=%3")
2015  .arg(key).arg(per*100.0).arg(m_pianoData[key].max_magnitude_seen));
2016 
2017  double r = m_whiteStartColor.red() + (m_whiteTargetColor.red() - m_whiteStartColor.red()) * per;
2018  double g = m_whiteStartColor.green() + (m_whiteTargetColor.green() - m_whiteStartColor.green()) * per;
2019  double b = m_whiteStartColor.blue() + (m_whiteTargetColor.blue() - m_whiteStartColor.blue()) * per;
2020 
2021  p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
2022  }
2023 
2024  // Then overlay the black keys
2025  for (uint key = 0; key < n; key++)
2026  {
2027  if (!m_pianoData[key].is_black_note)
2028  continue;
2029 
2030  double per = magnitudep[key]/magnitude_max;
2031  per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
2032 
2033  if (per < kPianoKeypressTooLight)
2034  per = 0.0; // Clamp to zero for lightly detected keys
2035 
2036  double r = m_blackStartColor.red() + (m_blackTargetColor.red() - m_blackStartColor.red()) * per;
2037  double g = m_blackStartColor.green() + (m_blackTargetColor.green() - m_blackStartColor.green()) * per;
2038  double b = m_blackStartColor.blue() + (m_blackTargetColor.blue() - m_blackStartColor.blue()) * per;
2039 
2040  p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
2041  }
2042 
2043  return true;
2044 }
2045 
2046 static class PianoFactory : public VisFactory
2047 {
2048  public:
2049  const QString &name(void) const override // VisFactory
2050  {
2051  static QString s_name = QCoreApplication::translate("Visualizers",
2052  "Piano");
2053  return s_name;
2054  }
2055 
2056  uint plugins(QStringList *list) const override // VisFactory
2057  {
2058  *list << name();
2059  return 1;
2060  }
2061 
2062  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
2063  {
2064  return new Piano();
2065  }
2066 }PianoFactory;
2067 
2069  m_lastCycle(QDateTime::currentDateTime())
2070 {
2071  findFrontCover();
2072  m_fps = 1;
2073 }
2074 
2076 {
2077  if (!gPlayer->getCurrentMetadata())
2078  return;
2079 
2080  // if a front cover image is available show that first
2082  if (albumArt->getImage(IT_FRONTCOVER))
2084  else
2085  {
2086  // not available so just show the first image available
2087  if (albumArt->getImageCount() > 0)
2088  m_currImageType = albumArt->getImageAt(0)->m_imageType;
2089  else
2091  }
2092 }
2093 
2095 {
2096  if (!gPlayer->getCurrentMetadata())
2097  return false;
2098 
2100  int newType = m_currImageType;
2101 
2102  // If we only have one image there is nothing to cycle
2103  if (albumArt->getImageCount() > 1)
2104  {
2105  do
2106  {
2107  newType++;
2108  if (newType == IT_LAST)
2109  newType = IT_UNKNOWN;
2110  } while (!albumArt->getImage((ImageType) newType));
2111  }
2112 
2113  if (newType != m_currImageType)
2114  {
2115  m_currImageType = (ImageType) newType;
2116  m_lastCycle = QDateTime::currentDateTime();
2117  return true;
2118  }
2119 
2120  return false;
2121 }
2122 
2123 void AlbumArt::resize(const QSize &newsize)
2124 {
2125  m_size = newsize;
2126 }
2127 
2129 {
2130  return false;
2131 }
2132 
2133 void AlbumArt::handleKeyPress(const QString &action)
2134 {
2135  if (action == "SELECT")
2136  {
2137  if (gPlayer->getCurrentMetadata())
2138  {
2140  int newType = m_currImageType;
2141 
2142  if (albumArt.getImageCount() > 0)
2143  {
2144  newType++;
2145 
2146  while (!albumArt.getImage((ImageType) newType))
2147  {
2148  newType++;
2149  if (newType == IT_LAST)
2150  newType = IT_UNKNOWN;
2151  }
2152  }
2153 
2154  if (newType != m_currImageType)
2155  {
2156  m_currImageType = (ImageType) newType;
2157  // force an update
2158  m_cursize = QSize(0, 0);
2159  }
2160  }
2161  }
2162 }
2163 
2165 static constexpr qint64 ALBUMARTCYCLETIME { 10 };
2166 
2168 {
2169  // if the track has changed we need to update the image
2171  {
2173  findFrontCover();
2174  return true;
2175  }
2176 
2177  // if it's time to cycle to the next image we need to update the image
2178  if (m_lastCycle.addSecs(ALBUMARTCYCLETIME) < QDateTime::currentDateTime())
2179  {
2180  if (cycleImage())
2181  return true;
2182  }
2183 
2184  return false;
2185 }
2186 
2187 bool AlbumArt::draw(QPainter *p, const QColor &back)
2188 {
2189  if (needsUpdate())
2190  {
2191  QImage art;
2192  QString imageFilename = gPlayer->getCurrentMetadata()->getAlbumArtFile(m_currImageType);
2193 
2194  if (imageFilename.startsWith("myth://"))
2195  {
2196  auto *rf = new RemoteFile(imageFilename, false, false, 0ms);
2197 
2198  QByteArray data;
2199  bool ret = rf->SaveAs(data);
2200 
2201  delete rf;
2202 
2203  if (ret)
2204  art.loadFromData(data);
2205  }
2206  else
2207  if (!imageFilename.isEmpty())
2208  art.load(imageFilename);
2209 
2210  if (art.isNull())
2211  {
2212  m_cursize = m_size;
2213  m_image = QImage();
2214  }
2215  else
2216  {
2217  m_image = art.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
2218  }
2219  }
2220 
2221  if (m_image.isNull())
2222  {
2223  drawWarning(p, back, m_size, tr("?"), 100);
2224  return true;
2225  }
2226 
2227  // Paint the image
2228  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
2229  p->drawImage((m_size.width() - m_image.width()) / 2,
2230  (m_size.height() - m_image.height()) / 2,
2231  m_image);
2232 
2233  // Store our new size
2234  m_cursize = m_size;
2235 
2236  return true;
2237 }
2238 
2239 static class AlbumArtFactory : public VisFactory
2240 {
2241  public:
2242  const QString &name(void) const override // VisFactory
2243  {
2244  static QString s_name = QCoreApplication::translate("Visualizers",
2245  "AlbumArt");
2246  return s_name;
2247  }
2248 
2249  uint plugins(QStringList *list) const override // VisFactory
2250  {
2251  *list << name();
2252  return 1;
2253  }
2254 
2255  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
2256  {
2257  return new AlbumArt();
2258  }
2260 
2262  : VisualBase(true)
2263 {
2264  m_fps = 1;
2265 }
2266 
2267 void Blank::resize(const QSize &newsize)
2268 {
2269  m_size = newsize;
2270 }
2271 
2272 
2274 {
2275  return false;
2276 }
2277 
2278 bool Blank::draw(QPainter *p, const QColor &back)
2279 {
2280  // Took me hours to work out this algorithm
2281  p->fillRect(0, 0, m_size.width(), m_size.height(), back);
2282  return true;
2283 }
2284 
2285 static class BlankFactory : public VisFactory
2286 {
2287  public:
2288  const QString &name(void) const override // VisFactory
2289  {
2290  static QString s_name = QCoreApplication::translate("Visualizers",
2291  "Blank");
2292  return s_name;
2293  }
2294 
2295  uint plugins(QStringList *list) const override // VisFactory
2296  {
2297  *list << name();
2298  return 1;
2299  }
2300 
2301  VisualBase *create(MainVisual */*parent*/, const QString &/*pluginName*/) const override // VisFactory
2302  {
2303  return new Blank();
2304  }
2305 }BlankFactory;
Spectrogram::m_sigL
QVector< float > m_sigL
Definition: visualize.h:245
Squares::Squares
Squares()
Definition: visualize.cpp:1537
Piano::piano_key_data::samples_processed
int samples_processed
Definition: visualize.h:339
Blank::Blank
Blank()
Definition: visualize.cpp:2261
Spectrogram::getDesiredSamples
unsigned long getDesiredSamples(void) override
Definition: visualize.cpp:1003
Spectrum::m_rectsR
QVector< QRect > m_rectsR
Definition: visualize.h:279
Spectrogram::m_sgsize
QSize m_sgsize
Definition: visualize.h:241
Spectrogram::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1187
MelScale::m_indices
std::vector< int > m_indices
Definition: visualize.h:211
gPlayer
MusicPlayer * gPlayer
Definition: musicplayer.cpp:37
StereoScopeFactory::name
const QString & name(void) const override
Definition: visualize.cpp:834
AlbumArtImage::m_imageType
ImageType m_imageType
Definition: musicmetadata.h:51
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
MelScale::operator[]
int operator[](int index)
Definition: visualize.cpp:182
Spectrum::~Spectrum
~Spectrum() override
Definition: visualize.cpp:1283
SpectrumFactory::name
const QString & name(void) const override
Definition: visualize.cpp:1514
Piano::piano_key_data
Definition: visualize.h:332
Piano::m_magnitude
std::vector< double > m_magnitude
Definition: visualize.h:378
Piano::piano_key_data::samples_process_before_display_update
int samples_process_before_display_update
Definition: visualize.h:340
Spectrum::m_dftR
FFTSample * m_dftR
Definition: visualize.h:294
PianoFactory
Definition: visualize.cpp:2046
AlbumArtFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:2255
Spectrum::processUndisplayed
bool processUndisplayed(VisualNode *node) override
Definition: visualize.cpp:1337
LogScale::m_range
int m_range
Definition: visualize.h:194
StereoScopeFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:841
AlbumArt::findFrontCover
void findFrontCover(void)
Definition: visualize.cpp:2075
PianoFactory::name
const QString & name(void) const override
Definition: visualize.cpp:2049
VisFactory::g_pVisFactories
static VisFactory * g_pVisFactories
Definition: visualize.h:103
Piano
Definition: visualize.h:315
AlbumArt::resize
void resize(const QSize &size) override
Definition: visualize.cpp:2123
WaveForm::m_right
short * m_right
Definition: visualize.h:162
Piano::kPianoNumKeys
static constexpr unsigned int kPianoNumKeys
Definition: visualize.h:322
Piano::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1937
back
static guint32 * back
Definition: goom_core.cpp:25
Spectrogram::s_image
static QImage s_image
Definition: visualize.h:235
Spectrum::m_scaleFactor
float m_scaleFactor
Definition: visualize.h:286
VisualNode
Definition: videovisual.h:25
Piano::processUndisplayed
bool processUndisplayed(VisualNode *node) override
Definition: visualize.cpp:1783
Piano::zero_analysis
void zero_analysis(void)
Definition: visualize.cpp:1677
Piano::getDesiredSamples
unsigned long getDesiredSamples(void) override
Definition: visualize.cpp:1773
WaveForm::m_sqrl
unsigned long m_sqrl
Definition: visualize.h:171
MelScale::range
int range() const
Definition: visualize.h:203
Spectrum::process
bool process(VisualNode *node) override
Definition: visualize.cpp:1332
Spectrogram::process
bool process(VisualNode *node) override
Definition: visualize.cpp:1009
MusicMetadata::getAlbumArtImages
AlbumArtImages * getAlbumArtImages(void)
Definition: musicmetadata.cpp:1358
Spectrogram::m_red
std::array< int, 256 *6 > m_red
Definition: visualize.h:250
StereoScope
Definition: visualize.h:111
AlbumArt::m_image
QImage m_image
Definition: visualize.h:402
Spectrum::m_sigR
QVector< float > m_sigR
Definition: visualize.h:292
Spectrogram::clamp
static double clamp(double cur, double max, double min)
Definition: visualize.cpp:1179
SpectrumDetailFactory
Definition: visualize.cpp:1245
WaveForm::saveload
void saveload(MusicMetadata *meta)
Definition: visualize.cpp:589
LogScale::operator[]
int operator[](int index) const
Definition: videovisualdefs.h:55
MelScale::setMax
void setMax(int maxscale, int maxrange, int maxfreq)
Definition: visualize.cpp:157
WaveForm::draw
bool draw(QPainter *p, const QColor &back) override
Definition: visualize.cpp:775
WaveForm::handleKeyPress
void handleKeyPress(const QString &action) override
Definition: visualize.cpp:816
SpectrogramFactory
Definition: visualize.cpp:1221
SpectrogramFactory
SpectrogramFactory SpectrogramFactory
WaveForm::m_minr
short int m_minr
Definition: visualize.h:172
MonoScopeFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:867
mythdbcon.h
MythMainWindow::RestoreScreensaver
static void RestoreScreensaver()
Definition: mythmainwindow.cpp:577
AlbumArtFactory
Definition: visualize.cpp:2239
SAMPLES_DEFAULT_SIZE
static constexpr uint16_t SAMPLES_DEFAULT_SIZE
Definition: visualize.h:36
AlbumArt
Definition: visualize.h:381
MusicMetadata::ID
IdType ID() const
Definition: musicmetadata.h:218
RemoteFile
Definition: remotefile.h:17
StereoScope::m_rubberband
const bool m_rubberband
Definition: visualize.h:127
VisualBase
Definition: visualize.h:62
LogScale::m_indices
std::vector< int > m_indices
Definition: videovisualdefs.h:62
Blank::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:2278
Spectrum::m_scale
MelScale m_scale
Definition: visualize.h:282
BlankFactory
BlankFactory BlankFactory
StereoScope::resize
void resize(const QSize &size) override
Definition: visualize.cpp:195
StereoScope::m_falloff
const double m_falloff
Definition: visualize.h:128
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
Spectrogram::m_scale
MelScale m_scale
Definition: visualize.h:243
Spectrum::Spectrum
Spectrum()
Definition: visualize.cpp:1271
Blank::m_size
QSize m_size
Definition: visualize.h:418
MusicMetadata
Definition: musicmetadata.h:80
SquaresFactory::name
const QString & name(void) const override
Definition: visualize.cpp:1603
SpectrumDetailFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1255
MusicMetadata::Length
std::chrono::milliseconds Length() const
Definition: musicmetadata.h:205
StereoScope::process
bool process(VisualNode *node) override
Definition: visualize.cpp:205
Spectrogram::resize
void resize(const QSize &size) override
Definition: visualize.cpp:995
LogScale::LogScale
LogScale(int maxscale=0, int maxrange=0)
Definition: videovisualdefs.h:9
mythdirs.h
StereoScope::m_size
QSize m_size
Definition: visualize.h:126
WaveForm::m_offset
unsigned long m_offset
Definition: visualize.h:161
Spectrum::m_falloff
float m_falloff
Definition: visualize.h:287
WaveForm::m_showtext
bool m_showtext
Definition: visualize.h:164
MonoScopeFactory
MonoScopeFactory MonoScopeFactory
Piano::m_offsetProcessed
std::chrono::milliseconds m_offsetProcessed
Definition: visualize.h:373
MusicPlayer::getPlayMode
PlayMode getPlayMode(void)
Definition: musicplayer.h:77
WaveForm::m_font
QFont m_font
Definition: visualize.h:163
MonoScope
Definition: visualize.h:131
VisualNode::m_left
short * m_left
Definition: videovisual.h:37
VisualBase::VisualBase
VisualBase(bool screensaverenable=false)
Definition: visualize.cpp:42
AlbumArt::m_currImageType
ImageType m_currImageType
Definition: visualize.h:401
WaveForm::kWFAudioSize
static constexpr unsigned long kWFAudioSize
Definition: visualize.h:145
true
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:95
AlbumArtImages::getImage
AlbumArtImage * getImage(ImageType type)
Definition: musicmetadata.cpp:2100
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
WaveForm::m_position
unsigned int m_position
Definition: visualize.h:168
SpectrogramFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1237
Spectrum::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1457
Spectrum::m_fftlen
int m_fftlen
Definition: visualize.h:290
WaveForm::m_duration
unsigned long m_duration
Definition: visualize.h:166
MusicPlayer::getCurrentMetadata
MusicMetadata * getCurrentMetadata(void)
get the metadata for the current track in the playlist
Definition: musicplayer.cpp:1170
MythMainWindow::DisableScreensaver
static void DisableScreensaver()
Definition: mythmainwindow.cpp:583
Piano::kPianoKeypressTooLight
static constexpr double kPianoKeypressTooLight
Definition: visualize.h:330
VisualNode::m_offset
std::chrono::milliseconds m_offset
Definition: videovisual.h:40
MonoScope::process
bool process(VisualNode *node) override
Definition: visualize.cpp:424
Spectrum::m_analyzerBarWidth
int m_analyzerBarWidth
Definition: visualize.h:288
Piano::m_whiteStartColor
QColor m_whiteStartColor
Definition: visualize.h:365
SpectrumFactory
Definition: visualize.cpp:1511
AlbumArt::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:2187
AlbumArtImages::getImageCount
uint getImageCount()
Definition: musicmetadata.h:530
Spectrogram::s_offset
static int s_offset
Definition: visualize.h:236
Piano::m_blackTargetColor
QColor m_blackTargetColor
Definition: visualize.h:368
Spectrum::m_magnitudes
QVector< float > m_magnitudes
Definition: visualize.h:280
Squares
Definition: visualize.h:298
PianoFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:2056
LogScale::m_scale
int m_scale
Definition: visualize.h:193
StereoScope::draw
bool draw(QPainter *p, const QColor &back) override
Definition: visualize.cpp:323
Piano::kPianoAudioSize
static constexpr unsigned long kPianoAudioSize
Definition: visualize.h:321
Squares::drawRect
void drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h)
Definition: visualize.cpp:1549
WaveFormFactory
Definition: visualize.cpp:882
Spectrogram::m_size
QSize m_size
Definition: visualize.h:242
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
WaveForm::m_stream
bool m_stream
Definition: visualize.h:175
Piano::kPianoSpectrumSmoothing
static constexpr double kPianoSpectrumSmoothing
Definition: visualize.h:328
StereoScopeFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:847
remotefile.h
hardwareprofile.config.p
p
Definition: config.py:33
VisualNode::m_right
short * m_right
Definition: videovisual.h:38
hardwareprofile.i18n.t
t
Definition: i18n.py:36
LogScale::range
int range() const
Definition: videovisualdefs.h:15
Spectrum::m_dftL
FFTSample * m_dftL
Definition: visualize.h:293
MusicMetadata::getAlbumArtFile
QString getAlbumArtFile(void)
Definition: musicmetadata.cpp:1255
Spectrum::m_rdftContext
RDFTContext * m_rdftContext
Definition: visualize.h:295
AlbumArt::cycleImage
bool cycleImage(void)
Definition: visualize.cpp:2094
Spectrum::m_startColor
QColor m_startColor
Definition: visualize.h:276
VisFactory
Definition: visualize.h:92
AlbumArt::m_cursize
QSize m_cursize
Definition: visualize.h:400
LogScale::setMax
void setMax(int maxscale, int maxrange)
Definition: videovisualdefs.h:17
SpectrumDetailFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1261
AlbumArtImages::getImageAt
AlbumArtImage * getImageAt(uint index)
Definition: musicmetadata.cpp:2132
Spectrogram::m_blue
std::array< int, 256 *6 > m_blue
Definition: visualize.h:252
Spectrum::m_sigL
QVector< float > m_sigL
Definition: visualize.h:291
Piano::piano_key_data::q2
goertzel_data q2
Definition: visualize.h:333
Spectrogram::m_sigR
QVector< float > m_sigR
Definition: visualize.h:246
AlbumArt::needsUpdate
bool needsUpdate(void)
Definition: visualize.cpp:2167
AlbumArt::process
bool process(VisualNode *node=nullptr) override
Definition: visualize.cpp:2128
hardwareprofile.smolt.long
long
Definition: smolt.py:76
StereoScope::m_targetColor
QColor m_targetColor
Definition: visualize.h:124
MelScale::hz2mel
static double hz2mel(double hz)
Definition: visualize.h:206
Spectrogram::Spectrogram
Spectrogram(bool hist)
Definition: visualize.cpp:921
Squares::m_fakeHeight
int m_fakeHeight
Definition: visualize.h:311
VisualBase::~VisualBase
virtual ~VisualBase(void)
Definition: visualize.cpp:49
goertzel_data
#define goertzel_data
Definition: visualize.h:325
WaveFormFactory::name
const QString & name(void) const override
Definition: visualize.cpp:885
Spectrogram::m_fftlen
int m_fftlen
Definition: visualize.h:244
Piano::resize
void resize(const QSize &size) override
Definition: visualize.cpp:1693
SpectrogramFactory::name
const QString & name(void) const override
Definition: visualize.cpp:1224
inlines.h
MelScale::m_range
int m_range
Definition: visualize.h:213
sizetliteral.h
visualize.h
StereoScope::m_magnitudes
std::vector< double > m_magnitudes
Definition: visualize.h:125
WaveForm
Definition: visualize.h:143
uint
unsigned int uint
Definition: compat.h:81
WaveForm::m_lastx
unsigned int m_lastx
Definition: visualize.h:167
ALBUMARTCYCLETIME
static constexpr qint64 ALBUMARTCYCLETIME
this is the time an image is shown in the albumart visualizer
Definition: visualize.cpp:2165
WaveForm::m_currentMetadata
MusicMetadata * m_currentMetadata
Definition: visualize.h:165
SquaresFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1616
AlbumArt::m_currentMetadata
MusicMetadata * m_currentMetadata
Definition: visualize.h:404
WaveForm::getDesiredSamples
unsigned long getDesiredSamples(void) override
Definition: visualize.cpp:642
AlbumArtFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:2249
WaveForm::s_image
static QImage s_image
Definition: visualize.h:146
SpectrumFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:1527
MelScale::MelScale
MelScale(int maxscale=0, int maxrange=0, int maxfreq=0)
Definition: visualize.cpp:152
Spectrogram::m_dftL
FFTSample * m_dftL
Definition: visualize.h:247
StereoScope::m_startColor
QColor m_startColor
Definition: visualize.h:123
Spectrogram::m_history
bool m_history
Definition: visualize.h:254
WaveFormFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:898
WaveForm::m_wfsize
QSize m_wfsize
Definition: visualize.h:160
Spectrogram::m_image
QImage * m_image
Definition: visualize.h:240
Spectrogram
Definition: visualize.h:218
Piano::piano_key_data::magnitude
goertzel_data magnitude
Definition: visualize.h:333
Spectrogram::handleKeyPress
void handleKeyPress(const QString &action) override
Definition: visualize.cpp:1207
SquaresFactory
Definition: visualize.cpp:1600
sq
T sq(T a)
Definition: visualize.cpp:1001
SquaresFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1610
SpectrogramFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1231
WaveFormFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:892
Spectrogram::m_dftR
FFTSample * m_dftR
Definition: visualize.h:248
Squares::m_actualSize
QSize m_actualSize
Definition: visualize.h:310
AlbumArt::m_lastCycle
QDateTime m_lastCycle
Definition: visualize.h:405
WaveForm::m_minl
short int m_minl
Definition: visualize.h:169
StereoScopeFactory
StereoScopeFactory StereoScopeFactory
Piano::kPianoRmsNegligible
static constexpr double kPianoRmsNegligible
Definition: visualize.h:327
mythuihelper.h
MainVisual
Definition: mainvisual.h:34
WaveForm::m_maxl
short int m_maxl
Definition: visualize.h:170
Piano::m_audioData
piano_audio * m_audioData
Definition: visualize.h:376
IT_UNKNOWN
@ IT_UNKNOWN
Definition: musicmetadata.h:30
AlbumArt::handleKeyPress
void handleKeyPress(const QString &action) override
Definition: visualize.cpp:2133
VisualNode::m_length
long m_length
Definition: videovisual.h:39
StereoScope::StereoScope
StereoScope()
Definition: visualize.cpp:190
WaveForm::m_sqrr
unsigned long m_sqrr
Definition: visualize.h:174
VisualBase::m_fps
int m_fps
Definition: visualize.h:88
Piano::piano_key_data::coeff
goertzel_data coeff
Definition: visualize.h:333
MonoScope::draw
bool draw(QPainter *p, const QColor &back) override
Definition: visualize.cpp:515
Piano::m_pianoData
piano_key_data * m_pianoData
Definition: visualize.h:375
Spectrum::resize
void resize(const QSize &size) override
Definition: visualize.cpp:1290
AlbumArtImages
Definition: musicmetadata.h:520
MonoScopeFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:873
Squares::draw
bool draw(QPainter *p, const QColor &back=Qt::black) override
Definition: visualize.cpp:1583
Spectrogram::m_binpeak
bool m_binpeak
Definition: visualize.h:253
PianoFactory
PianoFactory PianoFactory
Piano::piano_key_data::is_black_note
bool is_black_note
Definition: visualize.h:342
WaveForm::processUndisplayed
bool processUndisplayed(VisualNode *node) override
Definition: visualize.cpp:659
SquaresFactory
SquaresFactory SquaresFactory
Spectrogram::processUndisplayed
bool processUndisplayed(VisualNode *node) override
Definition: visualize.cpp:1042
BlankFactory::name
const QString & name(void) const override
Definition: visualize.cpp:2288
Piano::m_size
QSize m_size
Definition: visualize.h:371
AlbumArt::AlbumArt
AlbumArt(void)
Definition: visualize.cpp:2068
VisualBase::m_xscreensaverenable
bool m_xscreensaverenable
Definition: visualize.h:89
Squares::resize
void resize(const QSize &newsize) override
Definition: visualize.cpp:1542
AlbumArtFactory::name
const QString & name(void) const override
Definition: visualize.cpp:2242
WaveForm::m_maxr
short int m_maxr
Definition: visualize.h:173
SpectrumDetailFactory::name
const QString & name(void) const override
Definition: visualize.cpp:1248
WaveFormFactory
WaveFormFactory WaveFormFactory
IT_LAST
@ IT_LAST
Definition: musicmetadata.h:36
Piano::kPianoMinVol
static constexpr goertzel_data kPianoMinVol
Definition: visualize.h:329
Blank::resize
void resize(const QSize &size) override
Definition: visualize.cpp:2267
common.utilities.log
def log(debug, txt)
Definition: utilities.py:5
MonoScopeFactory::name
const QString & name(void) const override
Definition: visualize.cpp:860
Piano::m_whiteTargetColor
QColor m_whiteTargetColor
Definition: visualize.h:366
mythcontext.h
Spectrogram::m_rdftContext
RDFTContext * m_rdftContext
Definition: visualize.h:249
AlbumArtFactory
AlbumArtFactory AlbumArtFactory
mainvisual.h
Spectrum::m_rectsL
QVector< QRect > m_rectsL
Definition: visualize.h:278
Piano::process
bool process(VisualNode *node) override
Definition: visualize.cpp:1789
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
build_compdb.action
action
Definition: build_compdb.py:9
BlankFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:2301
MusicPlayer::PLAYMODE_RADIO
@ PLAYMODE_RADIO
Definition: musicplayer.h:73
Blank
Definition: visualize.h:408
Spectrum
Definition: visualize.h:257
IT_FRONTCOVER
@ IT_FRONTCOVER
Definition: musicmetadata.h:31
Blank::process
bool process(VisualNode *node=nullptr) override
Definition: visualize.cpp:2273
WaveForm::process
bool process(VisualNode *node) override
Definition: visualize.cpp:648
SpectrumFactory
SpectrumFactory SpectrumFactory
Piano::Piano
Piano()
Definition: visualize.cpp:1623
Spectrogram::~Spectrogram
~Spectrogram() override
Definition: visualize.cpp:988
SpectrumDetailFactory
SpectrumDetailFactory SpectrumDetailFactory
StereoScopeFactory
Definition: visualize.cpp:831
Spectrogram::m_green
std::array< int, 256 *6 > m_green
Definition: visualize.h:251
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
MelScale::mel2hz
static double mel2hz(double mel)
Definition: visualize.h:207
Piano::clamp
static double clamp(double cur, double max, double min)
Definition: visualize.cpp:1928
MonoScopeFactory
Definition: visualize.cpp:857
Piano::~Piano
~Piano() override
Definition: visualize.cpp:1669
Piano::piano_key_data::q1
goertzel_data q1
Definition: visualize.h:333
Piano::m_blackStartColor
QColor m_blackStartColor
Definition: visualize.h:367
build_compdb.filename
filename
Definition: build_compdb.py:21
MythUIScreenBounds::GetScalingFactors
void GetScalingFactors(float &Horizontal, float &Vertical) const
Definition: mythuiscreenbounds.cpp:237
mythmainwindow.h
Squares::m_numberOfSquares
int m_numberOfSquares
Definition: visualize.h:312
Spectrum::clamp
static double clamp(double cur, double max, double min)
Definition: visualize.cpp:1448
AlbumArt::m_size
QSize m_size
Definition: visualize.h:399
WaveForm::~WaveForm
~WaveForm() override
Definition: visualize.cpp:582
PianoFactory::create
VisualBase * create(MainVisual *, const QString &) const override
Definition: visualize.cpp:2062
ImageType
ImageType
Definition: musicmetadata.h:28
Piano::m_rects
std::vector< QRect > m_rects
Definition: visualize.h:370
musicmetadata.h
Piano::process_all_types
bool process_all_types(VisualNode *node, bool this_will_be_displayed)
Definition: visualize.cpp:1799
Spectrogram::kSGAudioSize
static constexpr int kSGAudioSize
Definition: visualize.h:221
Piano::piano_key_data::max_magnitude_seen
goertzel_data max_magnitude_seen
Definition: visualize.h:334
VisualBase::drawWarning
static void drawWarning(QPainter *p, const QColor &back, QSize size, const QString &warning, int fontsize=28)
Definition: visualize.cpp:61
decoder.h
M_PI
static constexpr double M_PI
Definition: goom_tools.h:9
Spectrum::m_targetColor
QColor m_targetColor
Definition: visualize.h:277
Spectrum::m_size
QSize m_size
Definition: visualize.h:281
musicplayer.h
BlankFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:2295
MelScale::m_scale
int m_scale
Definition: visualize.h:212
piano_audio
#define piano_audio
Definition: visualize.h:324
SpectrumFactory::plugins
uint plugins(QStringList *list) const override
Definition: visualize.cpp:1521
BlankFactory
Definition: visualize.cpp:2285