From a0bbac9035430db5e95683c95e82de81c148809f Mon Sep 17 00:00:00 2001 From: Timothy Witham Date: Mon, 9 Oct 2023 08:47:21 -0500 Subject: [PATCH] MythMusic: fix handleKeyPress of visualizers (#801) * MythMusic: call handleKeyPress of visualizers so they can have options So far only SELECT is used by AlbumArt, WaveForm and Spectrogram * MythMusic: suppress text from WaveForm, hit SELECT to toggle * MythMusic: let SELECT toggle frequency display in Spectrum * MythMusic: add music NOTES to Spectrum overlay on SELECT key * MythMusic: let Qt center the note/Hz text in Spectrum * MythMusic: move notes into Mel Scale, let Qt center the notes on pixels * MythMusic: skip sharps in bass to fit more notes in Spectrum overlay --- mythplugins/mythmusic/mythmusic/visualize.cpp | 167 +++++++++++++----- mythplugins/mythmusic/mythmusic/visualize.h | 24 +-- .../mythmusic/mythmusic/visualizerview.cpp | 4 + 3 files changed, 141 insertions(+), 54 deletions(-) diff --git a/mythplugins/mythmusic/mythmusic/visualize.cpp b/mythplugins/mythmusic/mythmusic/visualize.cpp index 6b6b0ac2e92..9637c83e999 100644 --- a/mythplugins/mythmusic/mythmusic/visualize.cpp +++ b/mythplugins/mythmusic/mythmusic/visualize.cpp @@ -165,6 +165,10 @@ void MelScale::setMax(int maxscale, int maxrange, int maxfreq) m_indices.clear(); m_indices.resize(maxrange, 0); + int note = 0; // first note is C0 + double freq = 16.35; // frequency of first note + double next = pow(2.0, 1.0 / 12.0); // factor separating notes + double maxmel = hz2mel(maxfreq); double hzperbin = (double) maxfreq / (double) maxscale; @@ -176,6 +180,12 @@ void MelScale::setMax(int maxscale, int maxrange, int maxfreq) m_indices[i] = bin; // LOG(VB_PLAYBACK, LOG_INFO, QString("Mel maxmel=%1, hzperbin=%2, hz=%3, i=%4, bin=%5") // .arg(maxmel).arg(hzperbin).arg(hz).arg(i).arg(bin)); + + if (hz > freq) { // map note to pixel location for note labels + m_freqs[note] = int(freq + 0.5); + m_pixels[note++] = i; + freq *= next; + } } } @@ -184,6 +194,27 @@ int MelScale::operator[](int index) return m_indices[index]; } +QString MelScale::note(int note) +{ + if (note < 0 || note > 125) + return QString(""); + return m_notes[note % 12]; +} + +int MelScale::pixel(int note) +{ + if (note < 0 || note > 125) + return 0; + return m_pixels[note]; +} + +int MelScale::freq(int note) +{ + if (note < 0 || note > 125) + return 0; + return m_freqs[note]; +} + /////////////////////////////////////////////////////////////////////////////// // StereoScope @@ -815,10 +846,8 @@ bool WaveForm::draw( QPainter *p, const QColor &back ) void WaveForm::handleKeyPress(const QString &action) { - LOG(VB_PLAYBACK, LOG_INFO, QString("WF keypress = %1").arg(action)); + LOG(VB_PLAYBACK, LOG_DEBUG, QString("WF keypress = %1").arg(action)); - // I'd like to toggle overlay text upon certain key hit, but - // mythfrontend doesn't appear to call this. Bug? if (action == "SELECT") { m_showtext = ! m_showtext; @@ -927,6 +956,8 @@ Spectrogram::Spectrogram(bool hist) m_fps = 40; // getting 1152 samples / 44100 = 38.28125 fps + m_color = gCoreContext->GetNumSetting("MusicSpectrogramColor", 0); + if (s_image.isNull()) // static histogram survives resize/restart { s_image = QImage( @@ -1008,34 +1039,62 @@ unsigned long Spectrogram::getDesiredSamples(void) bool Spectrogram::process(VisualNode */*node*/) { - // TODO: label certain frequencies instead: 50, 100, 200, 500, - // 1000, 2000, 5000, 10000 - - // QPainter painter(m_image); - // painter.setPen(Qt::cyan); - // QFont font = QApplication::font(); - // font.setPixelSize(14); - // painter.setFont(font); - // if (m_history) - // { - // for (auto h = m_sgsize.height(); h > 0; h -= m_sgsize.height() / 2) - // { - // for (auto i = 0; i < m_sgsize.height() / 2; i += 20) - // { - // painter.drawText(0, h - i, - // QString("...%1.%2.%3...").arg(i).arg(m_scale[i]) - // .arg(m_scale[i] * 22050 / 8192)); // hack!!! - // } - // } - // } else { - // painter.rotate(90); - // for (auto i = 0; i < m_sgsize.width(); i += 20) - // { - // painter.drawText(0, -1 * i, - // QString("...%1.%2.%3...").arg(i).arg(m_scale[i]) - // .arg(m_scale[i] * 22050 / 8192)); // hack!!! - // } - // } + if (!m_showtext) + return false; + + QPainter painter(m_image); + painter.setPen(Qt::white); + QFont font = QApplication::font(); + font.setPixelSize(16); + painter.setFont(font); + int half = m_sgsize.height() / 2; + + if (m_history) // currently unused in v34... + { + for (auto h = m_sgsize.height(); h > 0; h -= half) + { + for (auto i = 0; i < half; i += 20) + { + painter.drawText(0, h - i, + QString("...%1.%2.%3...").arg(i).arg(m_scale[i]) + .arg(m_scale[i] * 22050 / (m_fftlen/2))); // hack!!! + } + } + } else { + // static std::array treble = {52, 55, 59, 62, 65}; + // for (auto i = 0; i < 5; i++) { + // painter.drawLine(m_scale.pixel(treble[i]), 0, + // m_scale.pixel(treble[i]), m_sgsize.height()); + // } + for (auto i = 0; i < 125; i++) // 125 notes fit in 22050 Hz + { // let Qt center the note text on the pixel + painter.drawText(m_scale.pixel(i) - 20, half - (i % 12) * 15 - 40, + 40, 40, Qt::AlignCenter, m_scale.note(i)); + if (i % 12 == 5) // octave numbers + painter.drawText(m_scale.pixel(i) - 20, half - 220, + 40, 40, Qt::AlignCenter, + QString("%1").arg(int(i / 12))); + } + painter.rotate(90); // frequency in Hz draws down + int prev = -30; + for (auto i = 0; i < 125; i++) // 125 notes fit in 22050 Hz + { + if (i < 72 && m_scale.note(i) == ".") // skip low sharps + continue; + int now = m_scale.pixel(i); + if (now >= prev + 20) { // skip until good spacing + painter.drawText(half + 20, -1 * now - 40, + 80, 80, Qt::AlignVCenter|Qt::AlignLeft, + QString("%1").arg(m_scale.freq(i))); + if (m_scale.note(i) != ".") + painter.drawText(half + 100, -1 * now - 40, + 80, 80, Qt::AlignVCenter|Qt::AlignLeft, + QString("%1%2").arg(m_scale.note(i)) + .arg(int(i / 12))); + prev = now; + } + } + } return false; } @@ -1076,8 +1135,8 @@ bool Spectrogram::processUndisplayed(VisualNode *node) int end = m_fftlen / 40; // ramp window ends down to zero crossing for (int k = 0; k < m_fftlen; k++) { - mult = k < end ? (float)k / (float)end - : k > m_fftlen - end ? + mult = k < end ? (float)k / (float)end + : k > m_fftlen - end ? (float)(m_fftlen - k) / (float)end : 1; m_dftL[k] = m_sigL[k] * mult; m_dftR[k] = m_sigR[k] * mult; @@ -1141,8 +1200,16 @@ bool Spectrogram::processUndisplayed(VisualNode *node) h = m_sgsize.height() / 2; painter.drawLine(s_offset, h - i, s_offset + mag, h - i); painter.drawLine(s_offset - w, h - i, s_offset - w + mag, h - i); - left > 255 ? painter.setPen(Qt::yellow) : - painter.setPen(qRgb(mag, mag, mag)); + if (m_color & 0x01) // left in color? + { + if (left > 255) + painter.setPen(Qt::white); + if (mag == 0) + painter.setPen(Qt::black); + } else { + left > 255 ? painter.setPen(Qt::yellow) : + painter.setPen(qRgb(mag, mag, mag)); + } painter.drawPoint(s_offset, h - i); } else { painter.drawLine(i, h / 2, i, h / 2 - h / 2 * mag / 256); @@ -1158,8 +1225,16 @@ bool Spectrogram::processUndisplayed(VisualNode *node) h = m_sgsize.height(); painter.drawLine(s_offset, h - i, s_offset + mag, h - i); painter.drawLine(s_offset - w, h - i, s_offset - w + mag, h - i); - right > 255 ? painter.setPen(Qt::yellow) : - painter.setPen(qRgb(mag, mag, mag)); + if (m_color & 0x02) // right in color? + { + if (left > 255) + painter.setPen(Qt::white); + if (mag == 0) + painter.setPen(Qt::black); + } else { + right > 255 ? painter.setPen(Qt::yellow) : + painter.setPen(qRgb(mag, mag, mag)); + } painter.drawPoint(s_offset, h - i); } else { painter.drawLine(i, h / 2, i, h / 2 + h / 2 * mag / 256); @@ -1167,7 +1242,7 @@ bool Spectrogram::processUndisplayed(VisualNode *node) prev = index; // next pixel is FFT bins from here index = m_scale[i]; // to the next bin by LOG scale - prev = std::min(prev, index - 1); + prev = std::min(prev, index - 1); } if (m_history && ++s_offset >= w) { @@ -1206,13 +1281,17 @@ bool Spectrogram::draw(QPainter *p, const QColor &back) void Spectrogram::handleKeyPress(const QString &action) { - LOG(VB_PLAYBACK, LOG_INFO, QString("SG keypress = %1").arg(action)); + LOG(VB_PLAYBACK, LOG_DEBUG, QString("SG keypress = %1").arg(action)); - // I'd like to tweak options upon certain key hit, but - // mythfrontend doesn't appear to call this. Bug? - if (action == "SELECT") - { - LOG(VB_PLAYBACK, LOG_INFO, QString("SG keypress SELECT hit")); + if (action == "SELECT") { + if (m_history) + { + m_color = (m_color + 1) & 0x03; // left and right color bits + gCoreContext->SaveSetting("MusicSpectrogramColor", + QString("%1").arg(m_color)); + } + else + m_showtext = ! m_showtext; } } diff --git a/mythplugins/mythmusic/mythmusic/visualize.h b/mythplugins/mythmusic/mythmusic/visualize.h index 2f5b2753804..8bf848829d4 100644 --- a/mythplugins/mythmusic/mythmusic/visualize.h +++ b/mythplugins/mythmusic/mythmusic/visualize.h @@ -78,7 +78,7 @@ class VisualBase virtual bool draw( QPainter *, const QColor & ) = 0; virtual void resize( const QSize &size ) = 0; - virtual void handleKeyPress(const QString &action) = 0; + virtual void handleKeyPress([[maybe_unused]] const QString &action) { }; virtual int getDesiredFPS(void) { return m_fps; } // Override this if you need the potential of capturing more data than the default virtual unsigned long getDesiredSamples(void) { return SAMPLES_DEFAULT_SIZE; } @@ -117,7 +117,6 @@ class StereoScope : public VisualBase void resize( const QSize &size ) override; // VisualBase bool process( VisualNode *node ) override; // VisualBase bool draw( QPainter *p, const QColor &back ) override; // VisualBase - void handleKeyPress([[maybe_unused]] const QString &action) override {}; // VisualBase protected: QColor m_startColor {Qt::yellow}; @@ -161,7 +160,7 @@ class WaveForm : public StereoScope unsigned long m_offset {0}; // node offset for draw short *m_right {nullptr}; QFont m_font; // optional text overlay - bool m_showtext {true}; + bool m_showtext {false}; MusicMetadata *m_currentMetadata {nullptr}; unsigned long m_duration {60000}; // file length in milliseconds unsigned int m_lastx {1920}; // vert line tracker @@ -206,9 +205,16 @@ class MelScale static double hz2mel(double hz) { return 1127 * log(1 + hz / 700); } static double mel2hz(double mel) { return 700 * (exp(mel / 1127) - 1); } int operator[](int index); + QString note(int note); // text of note, 0 - 125 + int pixel(int note); // position of note + int freq(int note); // frequency of note private: - std::vector m_indices; + std::vector m_indices; // FFT bin of each pixel + std::array m_notes // one octave of notes + = {"C", ".", "D", ".", "E", "F", ".", "G", ".", "A", ".", "B"}; + std::array m_pixels {0}; // pixel of each note + std::array m_freqs {0}; // frequency of each note int m_scale {0}; int m_range {0}; }; @@ -231,7 +237,7 @@ class Spectrogram : public VisualBase bool process( VisualNode *node ) override; bool draw(QPainter *p, const QColor &back = Qt::black) override; void handleKeyPress(const QString &action) override; - // {(void) action;} + static QImage s_image; // picture of spectrogram static int s_offset; // position on screen @@ -239,9 +245,10 @@ class Spectrogram : public VisualBase static inline double clamp(double cur, double max, double min); QImage *m_image; // picture in use QSize m_sgsize {1920, 1080}; // picture size - QSize m_size; // displayed dize + QSize m_size; // displayed size MelScale m_scale; // Y-axis int m_fftlen {16 * 1024}; // window width + int m_color {0}; // color or grayscale QVector m_sigL; // decaying signal window QVector m_sigR; FFTSample* m_dftL { nullptr }; // real in, complex out @@ -252,6 +259,7 @@ class Spectrogram : public VisualBase std::array m_blue {0}; bool m_binpeak { true }; // peak of bins, else mean bool m_history { true }; // spectrogram? or spectrum + bool m_showtext {false}; // freq overlay? }; class Spectrum : public VisualBase @@ -268,7 +276,6 @@ class Spectrum : public VisualBase bool process(VisualNode *node) override; // VisualBase bool processUndisplayed(VisualNode *node) override; // VisualBase bool draw(QPainter *p, const QColor &back = Qt::black) override; // VisualBase - void handleKeyPress([[maybe_unused]] const QString &action) override {}; // VisualBase protected: static inline double clamp(double cur, double max, double min); @@ -303,7 +310,6 @@ class Squares : public Spectrum void resize (const QSize &newsize) override; // Spectrum bool draw(QPainter *p, const QColor &back = Qt::black) override; // Spectrum - void handleKeyPress([[maybe_unused]] const QString &action) override {}; // Spectrum private: void drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h); @@ -355,7 +361,6 @@ struct piano_key_data { unsigned long getDesiredSamples(void) override; // VisualBase bool draw(QPainter *p, const QColor &back = Qt::black) override; // VisualBase - void handleKeyPress([[maybe_unused]] const QString &action) override {}; // VisualBase protected: static inline double clamp(double cur, double max, double min); @@ -415,7 +420,6 @@ class Blank : public VisualBase void resize(const QSize &size) override; // VisualBase bool process(VisualNode *node = nullptr) override; // VisualBase bool draw(QPainter *p, const QColor &back = Qt::black) override; // VisualBase - void handleKeyPress([[maybe_unused]] const QString &action) override {}; // VisualBase private: QSize m_size; diff --git a/mythplugins/mythmusic/mythmusic/visualizerview.cpp b/mythplugins/mythmusic/mythmusic/visualizerview.cpp index c334c32c7fb..84e564c076a 100644 --- a/mythplugins/mythmusic/mythmusic/visualizerview.cpp +++ b/mythplugins/mythmusic/mythmusic/visualizerview.cpp @@ -18,6 +18,7 @@ #include // mythmusic +#include "mainvisual.h" #include "musiccommon.h" #include "visualizerview.h" @@ -75,6 +76,9 @@ bool VisualizerView::keyPressEvent(QKeyEvent *event) QString action = actions[i]; handled = true; + if (m_mainvisual && m_mainvisual->visual()) + m_mainvisual->visual()->handleKeyPress(action); + // unassgined arrow keys might as well be useful if (action == "UP") {