Skip to content
Browse files

MythMusic: Add a 88-Key Piano Visualization

Basically, it's a spectrum analyzer that detects frequencies specifically at
the pitches of a piano keyboard, and then displays the notes being pressed on
a piano keyboard displayed across the screen. Thanks to Martin Andrews for the
patch. Closes #10214.

Note: Edited slightly to match our coding standards.
Signed-off-by: Paul Harrison <pharrison@mythtv.org>
  • Loading branch information...
1 parent 1e9d43a commit 845b087f99b5daf76de3c0381f88e2265ca858a3 Martin Andrews committed with Paul Harrison Dec 29, 2011
View
50 mythplugins/mythmusic/mythmusic/mainvisual.cpp
@@ -78,7 +78,7 @@ void VisualBase::drawWarning(QPainter *p, const QColor &back, const QSize &size,
}
MainVisual::MainVisual(QWidget *parent, const char *name)
- : QWidget(parent), vis(0), playing(false), fps(20),
+ : QWidget(parent), vis(0), playing(false), fps(20), samples(SAMPLES_DEFAULT_SIZE),
timer (0), bannerTimer(0), info_widget(0)
{
setObjectName(name);
@@ -144,6 +144,7 @@ void MainVisual::setVisual(const QString &name)
vis = pVisFactory->create(this, (long int) winId(), pluginName);
vis->resize(size());
fps = vis->getDesiredFPS();
+ samples = vis->getDesiredSamples();
break;
}
}
@@ -163,44 +164,46 @@ void MainVisual::prepare()
}
}
+// This is called via : mythtv/libs/libmyth/output.cpp :: OutputListeners::dispatchVisual
+// from : mythtv/libs/libmyth/audio/audiooutputbase.cpp :: AudioOutputBase::AddData
// Caller holds mutex() lock
-void MainVisual::add(uchar *b, unsigned long b_len, unsigned long w, int c, int p)
+void MainVisual::add(uchar *buffer, unsigned long b_len, unsigned long timecode, int source_channels, int bits_per_sample)
{
- long len = b_len, cnt;
+ unsigned long len = b_len, cnt;
short *l = 0, *r = 0;
- len /= c;
- len /= (p / 8);
+ // len is length of buffer in fully converted samples
+ len /= source_channels;
+ len /= (bits_per_sample / 8);
-#define SAMPLES 512
- if (len > SAMPLES)
- len = SAMPLES;
+ if (len > samples)
+ len = samples;
cnt = len;
- if (c == 2)
+ if (source_channels == 2)
{
l = new short[len];
r = new short[len];
- if (p == 8)
- stereo16_from_stereopcm8(l, r, b, cnt);
- else if (p == 16)
- stereo16_from_stereopcm16(l, r, (short *) b, cnt);
+ if (bits_per_sample == 8)
+ stereo16_from_stereopcm8(l, r, buffer, cnt);
+ else if (bits_per_sample == 16)
+ stereo16_from_stereopcm16(l, r, (short *) buffer, cnt);
}
- else if (c == 1)
+ else if (source_channels == 1)
{
l = new short[len];
- if (p == 8)
- mono16_from_monopcm8(l, b, cnt);
- else if (p == 16)
- mono16_from_monopcm16(l, (short *) b, cnt);
+ if (bits_per_sample == 8)
+ mono16_from_monopcm8(l, buffer, cnt);
+ else if (bits_per_sample == 16)
+ mono16_from_monopcm16(l, (short *) buffer, cnt);
}
else
len = 0;
- nodes.append(new VisualNode(l, r, len, w));
+ nodes.append(new VisualNode(l, r, len, timecode));
}
void MainVisual::timeout()
@@ -220,6 +223,9 @@ void MainVisual::timeout()
break;
nodes.pop_front();
+ if (vis)
+ vis->processUndisplayed(node);
+
delete node;
node = n;
}
@@ -233,7 +239,7 @@ void MainVisual::timeout()
stop = vis->process(node);
QPainter p(&pixmap);
if (vis->draw(&p, Qt::black))
- update();
+ update(); // This implictly picks up the data in pixmap, filled in by draw
}
if (!playing && stop)
@@ -519,7 +525,7 @@ bool StereoScope::process( VisualNode *node )
if (node) {
double index = 0;
- double const step = (double)SAMPLES / size.width();
+ double const step = (double)SAMPLES_DEFAULT_SIZE / size.width();
for ( int i = 0; i < size.width(); i++) {
unsigned long indexTo = (unsigned long)(index + step);
if (indexTo == (unsigned long)(index))
@@ -722,7 +728,7 @@ bool MonoScope::process( VisualNode *node )
if (node)
{
double index = 0;
- double const step = (double)SAMPLES / size.width();
+ double const step = (double)SAMPLES_DEFAULT_SIZE / size.width();
for (int i = 0; i < size.width(); i++)
{
unsigned long indexTo = (unsigned long)(index + step);
View
13 mythplugins/mythmusic/mythmusic/mainvisual.h
@@ -32,6 +32,8 @@ class InfoWidget;
class Metadata;
class MainVisual;
+#define SAMPLES_DEFAULT_SIZE 512
+
class VisualNode
{
public:
@@ -60,10 +62,20 @@ class VisualBase
// return true if the output should stop
virtual bool process( VisualNode *node ) = 0;
+
+ // this is called on nodes that will not be displayed :: Not needed for most visualizations
+ // (i.e. between the displayed frames, if you need the whole audio stream)
+ virtual bool processUndisplayed( VisualNode * )
+ {
+ return true; // By default this does nothing : Ignore the in-between chunks of audio data
+ };
+
virtual bool draw( QPainter *, const QColor & ) = 0;
virtual void resize( const QSize &size ) = 0;
virtual void handleKeyPress(const QString &action) = 0;
virtual int getDesiredFPS(void) { return fps; }
+ // Override this if you need the potential of capturing more data than the default
+ virtual unsigned long getDesiredSamples(void) { return SAMPLES_DEFAULT_SIZE; }
void drawWarning(QPainter *, const QColor &, const QSize &, QString);
protected:
@@ -133,6 +145,7 @@ public slots:
QList<VisualNode*> nodes;
bool playing;
int fps;
+ unsigned long samples;
QTimer *timer;
QTimer *bannerTimer;
InfoWidget* info_widget;
View
475 mythplugins/mythmusic/mythmusic/visualize.cpp
@@ -34,6 +34,7 @@ using namespace std;
#define FFTW_N 512
+// static_assert(FFTW_N==SAMPLES_DEFAULT_SIZE)
Spectrum::Spectrum()
#if defined(FFTW3_SUPPORT) || defined(FFTW2_SUPPORT)
@@ -306,6 +307,480 @@ static class SpectrumFactory : public VisFactory
}
}SpectrumFactory;
+Piano::Piano()
+ : piano_data(NULL), audio_data(NULL)
+{
+ // Setup the "magical" audio coefficients
+ // required by the Goetzel Algorithm
+
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Initialised"));
+
+ piano_data = (piano_key_data *) malloc(sizeof(piano_key_data) * PIANO_N);
+ audio_data = (piano_audio *) malloc(sizeof(piano_audio) * PIANO_AUDIO_SIZE);
+
+ double sample_rate = 44100.0; // TODO : This should be obtained from gPlayer (likely candidate...)
+
+ fps = 20; // This is the display frequency. We're capturing all audio chunks by defining .process_undisplayed() though.
+
+ double concert_A = 440.0;
+ double semi_tone = pow(2.0, 1.0/12.0);
+
+ /* Lowest note on piano is 4 octaves below concert A */
+ double bottom_A = concert_A / 2.0 / 2.0 / 2.0 / 2.0;
+
+ unsigned int key;
+ double current_freq = bottom_A, samples_required;
+ for (key = 0; key < PIANO_N; key++)
+ {
+ // This is constant through time
+ piano_data[key].coeff = (goertzel_data)(2.0 * cos(2.0 * M_PI * current_freq / sample_rate));
+
+ samples_required = sample_rate/current_freq * 20.0; // Want 20 whole cycles of the current waveform at least
+ if (samples_required > sample_rate/4.0)
+ {
+ // For the really low notes, 4 updates a second is good enough...
+ samples_required = sample_rate/4.0;
+ }
+ if (samples_required < sample_rate/(double)fps*0.75)
+ { // For the high notes, use as many samples as we need in a display_fps
+ samples_required = sample_rate/(double)fps*0.75;
+ }
+ piano_data[key].samples_process_before_display_update = (int)samples_required;
+ piano_data[key].is_black_note = false; // Will be put right in .resize()
+
+ current_freq *= semi_tone;
+ }
+
+ zero_analysis();
+
+ whiteStartColor = QColor(245,245,245);
+ whiteTargetColor = Qt::red;
+
+ blackStartColor = QColor(10,10,10);
+ blackTargetColor = Qt::red;
+}
+
+Piano::~Piano()
+{
+ if (piano_data)
+ free(piano_data);
+ if (audio_data)
+ free(audio_data);
+}
+
+void Piano::zero_analysis(void)
+{
+ unsigned int key;
+ for (key = 0; key < PIANO_N; key++)
+ {
+ // These get updated continously, and must be stored between chunks of audio data
+ piano_data[key].q2 = (goertzel_data)0.0f;
+ piano_data[key].q1 = (goertzel_data)0.0f;
+ piano_data[key].magnitude = (goertzel_data)0.0f;
+ piano_data[key].max_magnitude_seen =
+ (goertzel_data)(PIANO_RMS_NEGLIGIBLE*PIANO_RMS_NEGLIGIBLE); // This is a guess - will be quickly overwritten
+
+ piano_data[key].samples_processed = 0;
+ }
+ offset_processed = 0;
+
+ return;
+}
+
+void Piano::resize(const QSize &newsize)
+{
+ // Just change internal data about the
+ // size of the pixmap to be drawn (ie. the
+ // size of the screen) and the logically
+ // ensuing number of up/down bars to hold
+ // the audio magnitudes
+
+ size = newsize;
+
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Being Resized"));
+
+ zero_analysis();
+
+ // There are 88-36=52 white notes on piano keyboard
+ double key_unit_size = (double)size.width() / 54.0; // One white key extra spacing, if possible
+ if (key_unit_size < 10.0) // Keys have to be at least this many pixels wide
+ key_unit_size = 10.0;
+
+ double white_width_pct = .8;
+ double black_width_pct = .6;
+ double black_offset_pct = .05;
+
+ double white_height_pct = 6;
+ double black_height_pct = 4;
+
+ // This is the starting position of the keyboard (may be beyond LHS)
+ // - actually position of C below bottom A (will be added to...). This is 4 octaves below middle C.
+ double left = (double)size.width() / 2.0 - (4.0*7.0 + 3.5) * key_unit_size; // The extra 3.5 centers 'F' inthe middle of the screen
+ double top_of_keys = (double)size.height() / 2.0 - key_unit_size * white_height_pct / 2.0; // Vertically center keys
+
+ double width, height, center, offset;
+
+ rects.resize(PIANO_N);
+
+ unsigned int key;
+ int note;
+ bool is_black = false;
+ for (key = 0; key < PIANO_N; key++)
+ {
+ note = ((int)key - 3 + 12) % 12; // This means that C=0, C#=1, D=2, etc (since lowest note is bottom A)
+ if (note == 0) // If we're on a 'C', move the left 'cursor' over an octave
+ {
+ left += key_unit_size*7.0;
+ }
+
+ center = 0.0;
+ offset = 0.0;
+ is_black = false;
+
+ switch (note)
+ {
+ case 0: center = 0.5; break;
+ case 1: center = 1.0; is_black = true; offset = -1; break;
+ case 2: center = 1.5; break;
+ case 3: center = 2.0; is_black = true; offset = +1; break;
+ case 4: center = 2.5; break;
+ case 5: center = 3.5; break;
+ case 6: center = 4.0; is_black = true; offset = -2; break;
+ case 7: center = 4.5; break;
+ case 8: center = 5.0; is_black = true; offset = 0; break;
+ case 9: center = 5.5; break;
+ case 10: center = 6.0; is_black = true; offset = 2; break;
+ case 11: center = 6.5; break;
+ }
+ piano_data[key].is_black_note = is_black;
+
+ width = (is_black ? black_width_pct:white_width_pct) * key_unit_size;
+ height = (is_black? black_height_pct:white_height_pct) * key_unit_size;
+
+ rects[key].setRect(
+ left + center * key_unit_size // Basic position of left side of key
+ - width / 2.0 // Less half the width
+ + (is_black ? (offset * black_offset_pct * key_unit_size):0.0), // And jiggle the positions of the black keys for aethetic reasons
+ top_of_keys, // top
+ width, // width
+ height // height
+ );
+ }
+
+ magnitude.resize(PIANO_N);
+ for (key = 0; key < (uint)magnitude.size(); key++)
+ {
+ magnitude[key] = 0.0;
+ }
+
+ return;
+}
+
+unsigned long Piano::getDesiredSamples(void)
+{
+ // We want all the data! (within reason)
+ // typical observed values are 882 -
+ // 12.5 chunks of data per second from 44100Hz signal : Sampled at 50Hz, lots of 4, see :
+ // mythtv/libs/libmyth/audio/audiooutputbase.cpp :: AudioOutputBase::AddData
+ // See : mythtv/mythplugins/mythmusic/mythmusic/avfdecoder.cpp "20ms worth"
+ return (unsigned long) PIANO_AUDIO_SIZE; // Maximum we can be given
+}
+
+bool Piano::process_undisplayed(VisualNode *node)
+{
+ //LOG(VB_GENERAL, LOG_INFO, QString("Piano : Processing undisplayed node"));
+ return process_all_types(node, false);
+}
+
+bool Piano::process(VisualNode *node)
+{
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node for DISPLAY"));
+ return process_all_types(node, true);
+}
+
+bool Piano::process_all_types(VisualNode *node, bool this_will_be_displayed)
+{
+ (void) this_will_be_displayed;
+
+ // Take a bunch of data in *node and break it down into piano key spectrum values
+ // NB: Remember the state data between calls, so as to accumulate more accurate results.
+ bool allZero = true;
+
+ uint i, n, key;
+
+ goertzel_data q0, q1, q2, coeff;
+ goertzel_data magnitude2, magnitude_av;
+ piano_audio short_to_bounded = 32768.0f;
+ int n_samples;
+
+ if (node)
+ {
+ // Detect start of new song (current node more than 10s earlier than already seen)
+ if (node->offset + 10000 < offset_processed)
+ {
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Node offset=%1 too far backwards : NEW SONG").arg(node->offset));
+ zero_analysis();
+ }
+
+ // Check whether we've seen this node (more recently than 10secs ago)
+ if (node->offset <= offset_processed)
+ {
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Already seen node offset=%1, returning without processing").arg(node->offset));
+ return allZero; // Nothing to see here - the server can stop if it wants to
+ }
+ }
+
+ if (node)
+ {
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Processing node offset=%1, size=%2").arg(node->offset).arg(node->length));
+ n = node->length;
+
+ if (node->right) // Preprocess the data into a combined middle channel, if we have stereo data
+ {
+ for (i = 0; i < n; i++)
+ {
+ audio_data[i] = (piano_audio)(((piano_audio)node->left[i] + (piano_audio)node->right[i]) / 2.0 / short_to_bounded);
+ }
+ }
+ else // This is only one channel of data
+ {
+ for (i = 0; i < n; i++)
+ {
+ audio_data[i] = (piano_audio)node->left[i] / short_to_bounded;
+ }
+ }
+ }
+ else
+ {
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Hit an empty node, and returning empty-handed"));
+ return allZero; // Nothing to see here - the server can stop if it wants to
+ }
+
+ for (key = 0; key < PIANO_N; key++)
+ {
+ coeff = piano_data[key].coeff;
+
+ q2 = piano_data[key].q2;
+ q1 = piano_data[key].q1;
+
+ for (i = 0; i < n; i++)
+ {
+ q0 = coeff * q1 - q2 + audio_data[i];
+ q2 = q1;
+ q1 = q0;
+ }
+ piano_data[key].q2 = q2;
+ piano_data[key].q1 = q1;
+
+ piano_data[key].samples_processed += n;
+
+ n_samples = piano_data[key].samples_processed;
+
+ // Only do this update if we've processed enough chunks for this key...
+ if (n_samples > piano_data[key].samples_process_before_display_update)
+ {
+ magnitude2 = q1*q1 + q2*q2 - q1*q2*coeff;
+
+ if (false) // This is RMS of signal
+ {
+ magnitude_av = sqrt(magnitude2)/(goertzel_data)n_samples; // Should be 0<magnitude_av<.5
+ }
+ if (true) // This is pure magnitude of signal
+ {
+ magnitude_av = magnitude2/(goertzel_data)n_samples/(goertzel_data)n_samples; // Should be 0<magnitude_av<.25
+ }
+
+ if (false) // Take logs everywhere, and shift up to [0, ??]
+ {
+ if(magnitude_av > 0.0)
+ {
+ magnitude_av = log(magnitude_av);
+ }
+ else
+ {
+ magnitude_av = PIANO_MIN_VOL;
+ }
+ magnitude_av -= PIANO_MIN_VOL;
+
+ if (magnitude_av < 0.0)
+ {
+ magnitude_av = 0.0;
+ }
+ }
+
+ if (magnitude_av > (goertzel_data)0.01)
+ {
+ allZero = false;
+ }
+
+ piano_data[key].magnitude = magnitude_av; // Store this for later : We'll do the colours from this...
+ if ( piano_data[key].max_magnitude_seen < magnitude_av)
+ {
+ piano_data[key].max_magnitude_seen = magnitude_av;
+ }
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Updated Key %1 from %2 samples, magnitude=%3")
+ .arg(key).arg(n_samples).arg(magnitude_av));
+
+ piano_data[key].samples_processed = 0; // Reset the counts, now that we've set the magnitude...
+ piano_data[key].q1 = (goertzel_data)0.0;
+ piano_data[key].q2 = (goertzel_data)0.0;
+ }
+ }
+
+ // All done now - record that we've done this offset
+ offset_processed = node->offset;
+ return allZero;
+}
+
+double Piano::clamp(double cur, double max, double min)
+{
+ if (cur > max)
+ cur = max;
+ if (cur < min)
+ cur = min;
+ return cur;
+}
+
+bool Piano::draw(QPainter *p, const QColor &back)
+{
+ // This draws on a pixmap owned by MainVisual.
+ //
+ // In other words, this is not a Qt Widget, it
+ // just uses some Qt methods to draw on a pixmap.
+ // MainVisual then bitblts that onto the screen.
+
+ QRect *rectsp = rects.data();
+ double *magnitudep = magnitude.data();
+
+ unsigned int key, n = PIANO_N;
+ double r, g, b, per;
+
+ p->fillRect(0, 0, size.width(), size.height(), back);
+
+ // Protect maximum array length
+ if(n > (uint)rects.size())
+ n = (uint)rects.size();
+
+ // Sweep up across the keys, making sure the max_magnitude_seen is at minimum X% of its neighbours
+ double mag = PIANO_RMS_NEGLIGIBLE;
+ for (key = 0; key < n; key++)
+ {
+ if (piano_data[key].max_magnitude_seen < mag)
+ {
+ // Spread the previous value to this key
+ piano_data[key].max_magnitude_seen = mag;
+ }
+ else
+ {
+ // This key has seen better peaks, use this for the next one
+ mag = piano_data[key].max_magnitude_seen;
+ }
+ mag *= PIANO_SPECTRUM_SMOOTHING;
+ }
+
+ // Similarly, down, making sure the max_magnitude_seen is at minimum X% of its neighbours
+ mag = PIANO_RMS_NEGLIGIBLE;
+ for (int key_i = n - 1; key_i >= 0; key_i--)
+ {
+ key = key_i; // Wow, this is to avoid a zany error for ((unsigned)0)--
+ if (piano_data[key].max_magnitude_seen < mag)
+ {
+ // Spread the previous value to this key
+ piano_data[key].max_magnitude_seen = mag;
+ }
+ else
+ {
+ // This key has seen better peaks, use this for the next one
+ mag = piano_data[key].max_magnitude_seen;
+ }
+ mag *= PIANO_SPECTRUM_SMOOTHING;
+ }
+
+ // Now find the key that has been hit the hardest relative to its experience, and renormalize...
+ // Set a minimum, to prevent divide-by-zero (and also all-pressed when music very quiet)
+ double magnitude_max = PIANO_RMS_NEGLIGIBLE;
+ for (key = 0; key < n; key++)
+ {
+ mag = piano_data[key].magnitude / piano_data[key].max_magnitude_seen;
+ if (magnitude_max < mag)
+ magnitude_max = mag;
+
+ magnitudep[key] = mag;
+ }
+
+ // Deal with all the white keys first
+ for (key = 0; key < n; key++)
+ {
+ if (piano_data[key].is_black_note)
+ continue;
+
+ per = magnitudep[key] / magnitude_max;
+ per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
+
+ if (per < PIANO_KEYPRESS_TOO_LIGHT)
+ per = 0.0; // Clamp to zero for lightly detected keys
+ LOG(VB_GENERAL, LOG_DEBUG, QString("Piano : Display key %1, magnitude=%2, seen=%3")
+ .arg(key).arg(per*100.0).arg(piano_data[key].max_magnitude_seen));
+
+ r = whiteStartColor.red() + (whiteTargetColor.red() - whiteStartColor.red()) * per;
+ g = whiteStartColor.green() + (whiteTargetColor.green() - whiteStartColor.green()) * per;
+ b = whiteStartColor.blue() + (whiteTargetColor.blue() - whiteStartColor.blue()) * per;
+
+ p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
+ }
+
+ // Then overlay the black keys
+ for (key = 0; key < n; key++)
+ {
+ if (!piano_data[key].is_black_note)
+ continue;
+
+ per = magnitudep[key]/magnitude_max;
+ per = clamp(per, 1.0, 0.0); // By construction, this should be unnecessary
+
+ if (per < PIANO_KEYPRESS_TOO_LIGHT)
+ per = 0.0; // Clamp to zero for lightly detected keys
+
+ r = blackStartColor.red() + (blackTargetColor.red() - blackStartColor.red()) * per;
+ g = blackStartColor.green() + (blackTargetColor.green() - blackStartColor.green()) * per;
+ b = blackStartColor.blue() + (blackTargetColor.blue() - blackStartColor.blue()) * per;
+
+ p->fillRect(rectsp[key], QColor(int(r), int(g), int(b)));
+ }
+
+ if (0)
+ {
+ drawWarning(p, back, size,
+ QObject::tr("Piano Visualization Message") + "\n" +
+ QObject::tr("Are you sane running this code?"));
+ }
+ return true;
+}
+
+static class PianoFactory : public VisFactory
+{
+ public:
+ const QString &name(void) const
+ {
+ static QString name("Piano");
+ return name;
+ }
+
+ uint plugins(QStringList *list) const
+ {
+ *list << name();
+ return 1;
+ }
+
+ VisualBase *create(MainVisual *parent, long int winid, const QString &pluginName) const
+ {
+ (void)parent;
+ (void)winid;
+ (void)pluginName;
+ return new Piano();
+ }
+}PianoFactory;
+
AlbumArt::AlbumArt(void)
{
findFrontCover();
View
63 mythplugins/mythmusic/mythmusic/visualize.h
@@ -82,6 +82,69 @@ class Spectrum : public VisualBase
#endif
};
+class Piano : public VisualBase
+{
+ // This class draws bars (up and down)
+ // based on the magnitudes at piano pitch
+ // frequencies in the audio data.
+
+#define PIANO_AUDIO_SIZE 4096
+#define PIANO_N 88
+
+#define piano_audio float
+#define goertzel_data float
+
+#define PIANO_RMS_NEGLIGIBLE .001
+#define PIANO_SPECTRUM_SMOOTHING 0.95
+#define PIANO_MIN_VOL -10
+#define PIANO_KEYPRESS_TOO_LIGHT .2
+
+typedef struct piano_key_data {
+ goertzel_data q1, q2, coeff, magnitude;
+ goertzel_data max_magnitude_seen;
+
+ // This keeps track of the samples processed for each note
+ // Low notes require a lot of samples to be correctly identified
+ // Higher ones are displayed quicker
+ int samples_processed;
+ int samples_process_before_display_update;
+
+ bool is_black_note; // These are painted on top of white notes, and have different colouring
+} piano_key_data;
+
+ public:
+ Piano();
+ virtual ~Piano();
+
+ virtual void resize(const QSize &size);
+
+ bool process(VisualNode *node);
+
+ // These functions are new, since we need to inspect all the data
+ bool process_undisplayed(VisualNode *node);
+ unsigned long getDesiredSamples(void);
+
+ virtual bool draw(QPainter *p, const QColor &back = Qt::black);
+ void handleKeyPress(const QString &action) {(void) action;}
+
+ protected:
+ inline double clamp(double cur, double max, double min);
+ bool process_all_types(VisualNode *node, bool this_will_be_displayed);
+ void zero_analysis(void);
+
+ QColor whiteStartColor, whiteTargetColor, blackStartColor, blackTargetColor;
+
+ vector<QRect> rects;
+ QSize size;
+
+ unsigned long offset_processed;
+
+ piano_key_data *piano_data;
+ piano_audio *audio_data;
+
+ vector<double> magnitude;
+};
+
class AlbumArt : public VisualBase
{
public:

0 comments on commit 845b087

Please sign in to comment.
Something went wrong with that request. Please try again.