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