Add "Psychedelic Colour" mode to all analyzers

(Well, except Nyanalyzer and Rainbow dash because they are already colourful enough.)

I have added functionality for any 2D analyzer to change any part of its colour palatte with the frequency content of the music, in the same way that Moodbars do.
I find this gives the analyzer a sort of "third dimention".
This is built into Analyzer::Base, so all analyzers can use it and override it as they please. I have thus added support for Block, Boom, Turbine, Sonogram, and Bar, however Boom and Block seem to look the best in my opinion.
This is of course all optional and is toggled by a checkbox in the context menu for the analyzer, disabled by default.
I have not been able to measure any increase in CPU activity with this enabled, even at 60fps.
This commit is contained in:
Mark Furneaux 2015-07-01 11:48:03 -04:00
parent 73ae416534
commit c102bf7fe6
18 changed files with 660 additions and 417 deletions

View File

@ -2,7 +2,7 @@
Copyright 2003, Max Howell <max.howell@methylblue.com> Copyright 2003, Max Howell <max.howell@methylblue.com>
Copyright 2009, 2011-2012, David Sansome <me@davidsansome.com> Copyright 2009, 2011-2012, David Sansome <me@davidsansome.com>
Copyright 2010, 2012, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2012, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -33,6 +33,7 @@
#include <QtDebug> #include <QtDebug>
#include "engines/enginebase.h" #include "engines/enginebase.h"
#include "core/arraysize.h"
// INSTRUCTIONS Base2D // INSTRUCTIONS Base2D
// 1. do anything that depends on height() in init(), Base2D will call it before // 1. do anything that depends on height() in init(), Base2D will call it before
@ -45,7 +46,8 @@
// TODO(David Sansome): make an INSTRUCTIONS file // TODO(David Sansome): make an INSTRUCTIONS file
// can't mod scope in analyze you have to use transform // can't mod scope in analyze you have to use transform
// TODO(John Maguire): for 2D use setErasePixmap Qt function insetead of m_background // TODO(John Maguire): for 2D use setErasePixmap Qt function insetead of
// m_background
// make the linker happy only for gcc < 4.0 // make the linker happy only for gcc < 4.0
#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \ #if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
@ -53,20 +55,29 @@
template class Analyzer::Base<QWidget>; template class Analyzer::Base<QWidget>;
#endif #endif
static const int sBarkBands[] = {
100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720,
2000, 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500};
static const int sBarkBandCount = arraysize(sBarkBands);
Analyzer::Base::Base(QWidget* parent, uint scopeSize) Analyzer::Base::Base(QWidget* parent, uint scopeSize)
: QWidget(parent), : QWidget(parent),
m_timeout(40) // msec timeout_(40) // msec
, ,
m_fht(new FHT(scopeSize)), fht_(new FHT(scopeSize)),
m_engine(nullptr), engine_(nullptr),
m_lastScope(512), lastScope_(512),
current_chunk_(0),
new_frame_(false), new_frame_(false),
is_playing_(false) {} is_playing_(false),
barkband_table_(QList<uint>()),
prev_color_index_(0),
bands_(0),
psychedelic_enabled_(false) {}
void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); } void Analyzer::Base::hideEvent(QHideEvent*) { timer_.stop(); }
void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); } void Analyzer::Base::showEvent(QShowEvent*) { timer_.start(timeout(), this); }
void Analyzer::Base::transform(Scope& scope) { void Analyzer::Base::transform(Scope& scope) {
// this is a standard transformation that should give // this is a standard transformation that should give
@ -74,16 +85,16 @@ void Analyzer::Base::transform(Scope& scope) {
// NOTE resizing here is redundant as FHT routines only calculate FHT::size() // NOTE resizing here is redundant as FHT routines only calculate FHT::size()
// values // values
// scope.resize( m_fht->size() ); // scope.resize( fht_->size() );
float* front = static_cast<float*>(&scope.front()); float* front = static_cast<float*>(&scope.front());
float* f = new float[m_fht->size()]; float* f = new float[fht_->size()];
m_fht->copy(&f[0], front); fht_->copy(&f[0], front);
m_fht->logSpectrum(front, &f[0]); fht_->logSpectrum(front, &f[0]);
m_fht->scale(front, 1.0 / 20); fht_->scale(front, 1.0 / 20);
scope.resize(m_fht->size() / 2); // second half of values are rubbish scope.resize(fht_->size() / 2); // second half of values are rubbish
delete[] f; delete[] f;
} }
@ -91,30 +102,30 @@ void Analyzer::Base::paintEvent(QPaintEvent* e) {
QPainter p(this); QPainter p(this);
p.fillRect(e->rect(), palette().color(QPalette::Window)); p.fillRect(e->rect(), palette().color(QPalette::Window));
switch (m_engine->state()) { switch (engine_->state()) {
case Engine::Playing: { case Engine::Playing: {
const Engine::Scope& thescope = m_engine->scope(m_timeout); const Engine::Scope& thescope = engine_->scope(timeout_);
int i = 0; int i = 0;
// convert to mono here - our built in analyzers need mono, but the // convert to mono here - our built in analyzers need mono, but the
// engines provide interleaved pcm // engines provide interleaved pcm
for (uint x = 0; static_cast<int>(x) < m_fht->size(); ++x) { for (uint x = 0; static_cast<int>(x) < fht_->size(); ++x) {
m_lastScope[x] = lastScope_[x] = static_cast<double>(thescope[i] + thescope[i + 1]) /
static_cast<double>(thescope[i] + thescope[i + 1]) / (2 * (1 << 15)); (2 * (1 << 15));
i += 2; i += 2;
} }
is_playing_ = true; is_playing_ = true;
transform(m_lastScope); transform(lastScope_);
analyze(p, m_lastScope, new_frame_); analyze(p, lastScope_, new_frame_);
// scope.resize( m_fht->size() ); // scope.resize( fht_->size() );
break; break;
} }
case Engine::Paused: case Engine::Paused:
is_playing_ = false; is_playing_ = false;
analyze(p, m_lastScope, new_frame_); analyze(p, lastScope_, new_frame_);
break; break;
default: default:
@ -131,9 +142,9 @@ int Analyzer::Base::resizeExponent(int exp) {
else if (exp > 9) else if (exp > 9)
exp = 9; exp = 9;
if (exp != m_fht->sizeExp()) { if (exp != fht_->sizeExp()) {
delete m_fht; delete fht_;
m_fht = new FHT(exp); fht_ = new FHT(exp);
} }
return exp; return exp;
} }
@ -154,7 +165,7 @@ int Analyzer::Base::resizeForBands(int bands) {
exp = 9; exp = 9;
resizeExponent(exp); resizeExponent(exp);
return m_fht->size() / 2; return fht_->size() / 2;
} }
void Analyzer::Base::demo(QPainter& p) { void Analyzer::Base::demo(QPainter& p) {
@ -175,6 +186,67 @@ void Analyzer::Base::demo(QPainter& p) {
++t; ++t;
} }
void Analyzer::Base::psychedelicModeChanged(bool enabled) {
psychedelic_enabled_ = enabled;
}
int Analyzer::Base::BandFrequency(int band) const {
return ((kSampleRate / 2) * band + kSampleRate / 4) / bands_;
}
void Analyzer::Base::updateBandSize(const int scopeSize) {
// prevent possible dbz in BandFrequency
if (scopeSize == 0) {
return;
}
bands_ = scopeSize;
barkband_table_.clear();
barkband_table_.reserve(bands_ + 1);
int barkband = 0;
for (int i = 0; i < bands_ + 1; ++i) {
if (barkband < sBarkBandCount - 1 &&
BandFrequency(i) >= sBarkBands[barkband]) {
barkband++;
}
barkband_table_.append(barkband);
}
}
QColor Analyzer::Base::getPsychedelicColor(const Scope& scope,
const int ampFactor,
const int bias) {
if (scope.size() > barkband_table_.length()) {
return palette().color(QPalette::Highlight);
}
// Calculate total magnitudes for different bark bands.
double bands[sBarkBandCount]{};
for (int i = 0; i < barkband_table_.size(); ++i) {
bands[barkband_table_[i]] += scope[i];
}
// Now divide the bark bands into thirds and compute their total amplitudes.
double rgb[3]{};
for (int i = 0; i < sBarkBandCount - 1; ++i) {
rgb[(i * 3) / sBarkBandCount] += bands[i] * bands[i];
}
for (int i = 0; i < 3; ++i) {
// bias colours for a threshold around normally amplified audio
rgb[i] = (int)((sqrt(rgb[i]) * ampFactor) + bias);
if (rgb[i] > 255) {
rgb[i] = 255;
}
}
return QColor::fromRgb(rgb[0], rgb[1], rgb[2]);
}
void Analyzer::Base::polishEvent() { void Analyzer::Base::polishEvent() {
init(); // virtual init(); // virtual
} }
@ -211,7 +283,7 @@ void Analyzer::initSin(Scope& v, const uint size) {
void Analyzer::Base::timerEvent(QTimerEvent* e) { void Analyzer::Base::timerEvent(QTimerEvent* e) {
QWidget::timerEvent(e); QWidget::timerEvent(e);
if (e->timerId() != m_timer.timerId()) return; if (e->timerId() != timer_.timerId()) return;
new_frame_ = true; new_frame_ = true;
update(); update();

View File

@ -3,7 +3,7 @@
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2011, Arnaud Bienner <arnaud.bienner@gmail.com> Copyright 2011, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -58,21 +58,22 @@ class Base : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
~Base() { delete m_fht; } ~Base() { delete fht_; }
uint timeout() const { return m_timeout; } uint timeout() const { return timeout_; }
void set_engine(EngineBase* engine) { m_engine = engine; } void set_engine(EngineBase* engine) { engine_ = engine; }
void changeTimeout(uint newTimeout) { void changeTimeout(uint newTimeout) {
m_timeout = newTimeout; timeout_ = newTimeout;
if (m_timer.isActive()) { if (timer_.isActive()) {
m_timer.stop(); timer_.stop();
m_timer.start(m_timeout, this); timer_.start(timeout_, this);
} }
} }
virtual void framerateChanged() {} virtual void framerateChanged() {}
virtual void psychedelicModeChanged(bool);
protected: protected:
explicit Base(QWidget*, uint scopeSize = 7); explicit Base(QWidget*, uint scopeSize = 7);
@ -86,21 +87,32 @@ class Base : public QWidget {
int resizeExponent(int); int resizeExponent(int);
int resizeForBands(int); int resizeForBands(int);
int BandFrequency(int) const;
void updateBandSize(const int);
QColor getPsychedelicColor(const Scope&, const int, const int);
virtual void init() {} virtual void init() {}
virtual void transform(Scope&); virtual void transform(Scope&);
virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0; virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0;
virtual void demo(QPainter& p); virtual void demo(QPainter& p);
protected: protected:
QBasicTimer m_timer; static const int kSampleRate =
uint m_timeout; 44100; // we shouldn't need to care about ultrasonics
FHT* m_fht;
EngineBase* m_engine; QBasicTimer timer_;
Scope m_lastScope; uint timeout_;
int current_chunk_; FHT* fht_;
EngineBase* engine_;
Scope lastScope_;
bool new_frame_; bool new_frame_;
bool is_playing_; bool is_playing_;
QList<uint> barkband_table_;
double prev_colors_[10][3];
int prev_color_index_;
int bands_;
bool psychedelic_enabled_;
}; };
void interpolate(const Scope&, Scope&); void interpolate(const Scope&, Scope&);

View File

@ -3,7 +3,7 @@
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com> Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com> Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -57,6 +57,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget* parent)
visualisation_action_(nullptr), visualisation_action_(nullptr),
double_click_timer_(new QTimer(this)), double_click_timer_(new QTimer(this)),
ignore_next_click_(false), ignore_next_click_(false),
psychedelic_colors_on_(false),
current_analyzer_(nullptr), current_analyzer_(nullptr),
engine_(nullptr) { engine_(nullptr) {
QHBoxLayout* layout = new QHBoxLayout(this); QHBoxLayout* layout = new QHBoxLayout(this);
@ -88,6 +89,11 @@ AnalyzerContainer::AnalyzerContainer(QWidget* parent)
disable_action_->setCheckable(true); disable_action_->setCheckable(true);
group_->addAction(disable_action_); group_->addAction(disable_action_);
context_menu_->addSeparator();
psychedelic_enable_ = context_menu_->addAction(
tr("Use Psychedelic Colors"), this, SLOT(TogglePsychedelicColors()));
psychedelic_enable_->setCheckable(true);
context_menu_->addSeparator(); context_menu_->addSeparator();
// Visualisation action gets added in SetActions // Visualisation action gets added in SetActions
@ -145,6 +151,12 @@ void AnalyzerContainer::DisableAnalyzer() {
Save(); Save();
} }
void AnalyzerContainer::TogglePsychedelicColors() {
psychedelic_colors_on_ ^= true;
current_analyzer_->psychedelicModeChanged(psychedelic_colors_on_);
SavePsychedelic();
}
void AnalyzerContainer::ChangeAnalyzer(int id) { void AnalyzerContainer::ChangeAnalyzer(int id) {
QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this)); QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
@ -161,6 +173,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
current_framerate_ = current_framerate_ =
current_framerate_ == 0 ? kMediumFramerate : current_framerate_; current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
current_analyzer_->changeTimeout(1000 / current_framerate_); current_analyzer_->changeTimeout(1000 / current_framerate_);
current_analyzer_->psychedelicModeChanged(psychedelic_colors_on_);
layout()->addWidget(current_analyzer_); layout()->addWidget(current_analyzer_);
@ -183,6 +196,10 @@ void AnalyzerContainer::Load() {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
// Colours
psychedelic_colors_on_ = s.value("psychedelic", false).toBool();
psychedelic_enable_->setChecked(psychedelic_colors_on_);
// Analyzer // Analyzer
QString type = s.value("type", "BlockAnalyzer").toString(); QString type = s.value("type", "BlockAnalyzer").toString();
if (type.isEmpty()) { if (type.isEmpty()) {
@ -227,6 +244,13 @@ void AnalyzerContainer::Save() {
: QVariant()); : QVariant());
} }
void AnalyzerContainer::SavePsychedelic() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("psychedelic", psychedelic_colors_on_);
}
void AnalyzerContainer::AddFramerate(const QString& name, int framerate) { void AnalyzerContainer::AddFramerate(const QString& name, int framerate) {
QAction* action = QAction* action =
context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map())); context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map()));

View File

@ -4,6 +4,7 @@
Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com> Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com> Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -40,7 +41,7 @@ class AnalyzerContainer : public QWidget {
static const char* kSettingsGroup; static const char* kSettingsGroup;
static const char* kSettingsFramerate; static const char* kSettingsFramerate;
signals: signals:
void WheelEvent(int delta); void WheelEvent(int delta);
protected: protected:
@ -53,6 +54,7 @@ class AnalyzerContainer : public QWidget {
void ChangeFramerate(int new_framerate); void ChangeFramerate(int new_framerate);
void DisableAnalyzer(); void DisableAnalyzer();
void ShowPopupMenu(); void ShowPopupMenu();
void TogglePsychedelicColors();
private: private:
static const int kLowFramerate; static const int kLowFramerate;
@ -63,6 +65,7 @@ class AnalyzerContainer : public QWidget {
void Load(); void Load();
void Save(); void Save();
void SaveFramerate(int framerate); void SaveFramerate(int framerate);
void SavePsychedelic();
template <typename T> template <typename T>
void AddAnalyzerType(); void AddAnalyzerType();
void AddFramerate(const QString& name, int framerate); void AddFramerate(const QString& name, int framerate);
@ -80,11 +83,13 @@ class AnalyzerContainer : public QWidget {
QList<int> framerate_list_; QList<int> framerate_list_;
QList<QAction*> actions_; QList<QAction*> actions_;
QAction* disable_action_; QAction* disable_action_;
QAction* psychedelic_enable_;
QAction* visualisation_action_; QAction* visualisation_action_;
QTimer* double_click_timer_; QTimer* double_click_timer_;
QPoint last_click_pos_; QPoint last_click_pos_;
bool ignore_next_click_; bool ignore_next_click_;
bool psychedelic_colors_on_;
Analyzer::Base* current_analyzer_; Analyzer::Base* current_analyzer_;
EngineBase* engine_; EngineBase* engine_;

View File

@ -2,7 +2,7 @@
Copyright 2003, Mark Kretschmann <markey@web.de> Copyright 2003, Mark Kretschmann <markey@web.de>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com> Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com> Copyright 2014, John Maguire <john.maguire@gmail.com>
@ -23,7 +23,6 @@
/* Original Author: Mark Kretschmann <markey@web.de> 2003 /* Original Author: Mark Kretschmann <markey@web.de> 2003
*/ */
#include "baranalyzer.h" #include "baranalyzer.h"
#include <cmath> #include <cmath>
#include <QtDebug> #include <QtDebug>
@ -36,19 +35,20 @@ const char* BarAnalyzer::kName =
BarAnalyzer::BarAnalyzer(QWidget* parent) : Analyzer::Base(parent, 8) { BarAnalyzer::BarAnalyzer(QWidget* parent) : Analyzer::Base(parent, 8) {
// roof pixmaps don't depend on size() so we do in the ctor // roof pixmaps don't depend on size() so we do in the ctor
m_bg = parent->palette().color(QPalette::Background); bg_ = parent->palette().color(QPalette::Background);
QColor fg(parent->palette().color(QPalette::Highlight).lighter(150)); QColor fg(parent->palette().color(QPalette::Highlight).lighter(150));
double dr = static_cast<double>(m_bg.red() - fg.red()) / double dr = static_cast<double>(bg_.red() - fg.red()) /
(NUM_ROOFS - 1); // -1 because we start loop below at 0 (kNumRoofs - 1); // -1 because we start loop below at 0
double dg = static_cast<double>(m_bg.green() - fg.green()) / (NUM_ROOFS - 1); double dg = static_cast<double>(bg_.green() - fg.green()) / (kNumRoofs - 1);
double db = static_cast<double>(m_bg.blue() - fg.blue()) / (NUM_ROOFS - 1); double db = static_cast<double>(bg_.blue() - fg.blue()) / (kNumRoofs - 1);
for (uint i = 0; i < NUM_ROOFS; ++i) { for (uint i = 0; i < kNumRoofs; ++i) {
m_pixRoof[i] = QPixmap(COLUMN_WIDTH, 1); pixRoof_[i] = QPixmap(kColumnWidth, 1);
m_pixRoof[i].fill(QColor(fg.red() + static_cast<int>(dr * i), fg.green() + static_cast<int>(dg * i), pixRoof_[i].fill(QColor(fg.red() + static_cast<int>(dr * i),
fg.blue() + static_cast<int>(db * i))); fg.green() + static_cast<int>(dg * i),
fg.blue() + static_cast<int>(db * i)));
} }
} }
@ -58,46 +58,66 @@ void BarAnalyzer::resizeEvent(QResizeEvent* e) { init(); }
void BarAnalyzer::init() { void BarAnalyzer::init() {
const double MAX_AMPLITUDE = 1.0; const double MAX_AMPLITUDE = 1.0;
const double F = static_cast<double>(height() - 2) / (log10(255) * MAX_AMPLITUDE); const double F =
static_cast<double>(height() - 2) / (log10(255) * MAX_AMPLITUDE);
BAND_COUNT = width() / 5; band_count_ = width() / 5;
MAX_DOWN = static_cast<int>(0 - (qMax(1, height() / 50))); max_down_ = static_cast<int>(0 - (qMax(1, height() / 50)));
MAX_UP = static_cast<int>(qMax(1, height() / 25)); max_up_ = static_cast<int>(qMax(1, height() / 25));
barVector.resize(BAND_COUNT, 0); barVector_.resize(band_count_, 0);
roofVector.resize(BAND_COUNT, height() - 5); roofVector_.resize(band_count_, height() - 5);
roofVelocityVector.resize(BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR); roofVelocityVector_.resize(band_count_, kRoofVelocityReductionFactor);
m_roofMem.resize(BAND_COUNT); roofMem_.resize(band_count_);
m_scope.resize(BAND_COUNT); scope_.resize(band_count_);
// generate a list of values that express amplitudes in range 0-MAX_AMP as // generate a list of values that express amplitudes in range 0-MAX_AMP as
// ints from 0-height() on log scale // ints from 0-height() on log scale
for (uint x = 0; x < 256; ++x) { for (uint x = 0; x < 256; ++x) {
m_lvlMapper[x] = static_cast<uint>(F * log10(x + 1)); lvlMapper_[x] = static_cast<uint>(F * log10(x + 1));
} }
m_pixBarGradient = QPixmap(height() * COLUMN_WIDTH, height()); pixBarGradient_ = QPixmap(height() * kColumnWidth, height());
m_pixCompose = QPixmap(size()); pixCompose_ = QPixmap(size());
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
QPainter p(&m_pixBarGradient); updateBandSize(band_count_);
QColor rgb(palette().color(QPalette::Highlight)); colorChanged();
setMinimumSize(QSize(band_count_ * kColumnWidth, 10));
}
for (int x = 0, r = rgb.red(), g = rgb.green(), b = rgb.blue(), r2 = 255 - r; x < height(); void BarAnalyzer::colorChanged() {
++x) { if (pixBarGradient_.isNull()) {
return;
}
QPainter p(&pixBarGradient_);
QColor rgb;
if (psychedelic_enabled_) {
rgb = getPsychedelicColor(scope_, 50, 100);
} else {
rgb = palette().color(QPalette::Highlight);
}
for (int x = 0, r = rgb.red(), g = rgb.green(), b = rgb.blue(), r2 = 255 - r;
x < height(); ++x) {
for (int y = x; y > 0; --y) { for (int y = x; y > 0; --y) {
const double fraction = static_cast<double>(y) / height(); const double fraction = static_cast<double>(y) / height();
// p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 * // p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 *
// fraction) ) ); // fraction) ) );
p.setPen(QColor(r + static_cast<int>(r2 * fraction), g, b)); p.setPen(QColor(r + static_cast<int>(r2 * fraction), g, b));
p.drawLine(x * COLUMN_WIDTH, height() - y, (x + 1) * COLUMN_WIDTH, p.drawLine(x * kColumnWidth, height() - y, (x + 1) * kColumnWidth,
height() - y); height() - y);
} }
} }
}
setMinimumSize(QSize(BAND_COUNT * COLUMN_WIDTH, 10)); void BarAnalyzer::psychedelicModeChanged(bool enabled) {
psychedelic_enabled_ = enabled;
// reset colours back to normal
colorChanged();
} }
void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) { void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
@ -107,20 +127,25 @@ void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
} }
// Analyzer::interpolate( s, m_bands ); // Analyzer::interpolate( s, m_bands );
Scope& v = m_scope; Analyzer::interpolate(s, scope_);
Analyzer::interpolate(s, v);
QPainter canvas_painter(&canvas_); QPainter canvas_painter(&canvas_);
// update the graphics with the new colour
if (psychedelic_enabled_) {
colorChanged();
}
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
for (uint i = 0, x = 0, y2; i < v.size(); ++i, x += COLUMN_WIDTH + 1) { for (uint i = 0, x = 0, y2; i < scope_.size(); ++i, x += kColumnWidth + 1) {
// assign pre[log10]'d value // assign pre[log10]'d value
y2 = static_cast<uint>(v[i] * y2 = static_cast<uint>(
256); // 256 will be optimised to a bitshift //no, it's a float scope_[i] *
y2 = m_lvlMapper[(y2 > 255) ? 255 : y2]; // lvlMapper is array of ints with 256); // 256 will be optimised to a bitshift //no, it's a float
// values 0 to height() y2 = lvlMapper_[(y2 > 255) ? 255 : y2]; // lvlMapper is array of ints with
// values 0 to height()
int change = y2 - barVector[i]; int change = y2 - barVector_[i];
// using the best of Markey's, piggz and Max's ideas on the way to shift the // using the best of Markey's, piggz and Max's ideas on the way to shift the
// bars // bars
@ -129,54 +154,52 @@ void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
// 2. shift large upwards with a bias towards last value // 2. shift large upwards with a bias towards last value
// 3. fall downwards at a constant pace // 3. fall downwards at a constant pace
/*if ( change > MAX_UP ) //anything too much greater than 2 gives "jitter" /*if ( change > max_up_ ) //anything too much greater than 2 gives "jitter"
//add some dynamics - makes the value slightly closer to what it was last time //add some dynamics - makes the value slightly closer to what it was last time
y2 = ( barVector[i] + MAX_UP ); y2 = ( barVector_[i] + max_up_ );
//y2 = ( barVector[i] * 2 + y2 ) / 3; //y2 = ( barVector_[i] * 2 + y2 ) / 3;
else*/ if (change < else*/ if (change <
MAX_DOWN) max_down_)
y2 = barVector[i] + MAX_DOWN; y2 = barVector_[i] + max_down_;
if (static_cast<int>(y2) > roofVector[i]) { if (static_cast<int>(y2) > roofVector_[i]) {
roofVector[i] = static_cast<int>(y2); roofVector_[i] = static_cast<int>(y2);
roofVelocityVector[i] = 1; roofVelocityVector_[i] = 1;
} }
// remember where we are // remember where we are
barVector[i] = y2; barVector_[i] = y2;
if (m_roofMem[i].size() > NUM_ROOFS) if (roofMem_[i].size() > kNumRoofs) roofMem_[i].erase(roofMem_[i].begin());
m_roofMem[i].erase(m_roofMem[i].begin());
// blt last n roofs, a.k.a motion blur // blt last n roofs, a.k.a motion blur
for (uint c = 0; c < m_roofMem[i].size(); ++c) for (uint c = 0; c < roofMem_[i].size(); ++c)
// bitBlt( m_pComposePixmap, x, m_roofMem[i]->at( c ), m_roofPixmaps[ c ] // bitBlt( m_pComposePixmap, x, roofMem_[i]->at( c ), m_roofPixmaps[ c ]
// ); // );
// bitBlt( canvas(), x, m_roofMem[i][c], &m_pixRoof[ NUM_ROOFS - 1 - c ] // bitBlt( canvas(), x, roofMem_[i][c], &pixRoof_[ kNumRoofs - 1 - c ]
// ); // );
canvas_painter.drawPixmap(x, m_roofMem[i][c], canvas_painter.drawPixmap(x, roofMem_[i][c], pixRoof_[kNumRoofs - 1 - c]);
m_pixRoof[NUM_ROOFS - 1 - c]);
// blt the bar // blt the bar
canvas_painter.drawPixmap(x, height() - y2, *gradient(), y2 * COLUMN_WIDTH, canvas_painter.drawPixmap(x, height() - y2, *gradient(), y2 * kColumnWidth,
height() - y2, COLUMN_WIDTH, y2); height() - y2, kColumnWidth, y2);
/*bitBlt( canvas(), x, height() - y2, /*bitBlt( canvas(), x, height() - y2,
gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2, gradient(), y2 * kColumnWidth, height() - y2, kColumnWidth, y2,
Qt::CopyROP );*/ Qt::CopyROP );*/
m_roofMem[i].push_back(height() - roofVector[i] - 2); roofMem_[i].push_back(height() - roofVector_[i] - 2);
// set roof parameters for the NEXT draw // set roof parameters for the NEXT draw
if (roofVelocityVector[i] != 0) { if (roofVelocityVector_[i] != 0) {
if (roofVelocityVector[i] > 32) // no reason to do == 32 if (roofVelocityVector_[i] > 32) // no reason to do == 32
roofVector[i] -= roofVector_[i] -=
(roofVelocityVector[i] - 32) / 20; // trivial calculation (roofVelocityVector_[i] - 32) / 20; // trivial calculation
if (roofVector[i] < 0) { if (roofVector_[i] < 0) {
roofVector[i] = 0; // not strictly necessary roofVector_[i] = 0; // not strictly necessary
roofVelocityVector[i] = 0; roofVelocityVector_[i] = 0;
} else { } else {
++roofVelocityVector[i]; ++roofVelocityVector_[i];
} }
} }
} }

View File

@ -2,7 +2,7 @@
Copyright 2003-2005, Max Howell <max.howell@methylblue.com> Copyright 2003-2005, Max Howell <max.howell@methylblue.com>
Copyright 2005, Mark Kretschmann <markey@web.de> Copyright 2005, Mark Kretschmann <markey@web.de>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com> Copyright 2014, John Maguire <john.maguire@gmail.com>
@ -39,44 +39,45 @@ class BarAnalyzer : public Analyzer::Base {
void init(); void init();
virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame); virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame);
// virtual void transform( Scope& ); virtual void psychedelicModeChanged(bool);
/** /**
* Resizes the widget to a new geometry according to @p e * Resizes the widget to a new geometry according to @p e
* @param e The resize-event * @param e The resize-event
*/ */
void resizeEvent(QResizeEvent* e); void resizeEvent(QResizeEvent* e);
void colorChanged();
uint BAND_COUNT; uint band_count_;
int MAX_DOWN; int max_down_;
int MAX_UP; int max_up_;
static const uint ROOF_HOLD_TIME = 48; static const uint kRoofHoldTime = 48;
static const int ROOF_VELOCITY_REDUCTION_FACTOR = 32; static const int kRoofVelocityReductionFactor = 32;
static const uint NUM_ROOFS = 16; static const uint kNumRoofs = 16;
static const uint COLUMN_WIDTH = 4; static const uint kColumnWidth = 4;
static const char* kName; static const char* kName;
protected: protected:
QPixmap m_pixRoof[NUM_ROOFS]; QPixmap pixRoof_[kNumRoofs];
// vector<uint> m_roofMem[BAND_COUNT]; // vector<uint> roofMem_[band_count_];
// Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope // Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope
// every iteration // every iteration
uint m_lvlMapper[256]; uint lvlMapper_[256];
std::vector<aroofMemVec> m_roofMem; std::vector<aroofMemVec> roofMem_;
std::vector<uint> barVector; // positions of bars std::vector<uint> barVector_; // positions of bars
std::vector<int> roofVector; // positions of roofs std::vector<int> roofVector_; // positions of roofs
std::vector<uint> roofVelocityVector; // speed that roofs falls std::vector<uint> roofVelocityVector_; // speed that roofs falls
const QPixmap* gradient() const { return &m_pixBarGradient; } const QPixmap* gradient() const { return &pixBarGradient_; }
private: private:
QPixmap m_pixBarGradient; QPixmap pixBarGradient_;
QPixmap m_pixCompose; QPixmap pixCompose_;
QPixmap canvas_; QPixmap canvas_;
Analyzer::Scope m_scope; // so we don't create a vector every frame Analyzer::Scope scope_; // so we don't create a vector every frame
QColor m_bg; QColor bg_;
}; };
#endif // ANALYZERS_BARANALYZER_H_ #endif // ANALYZERS_BARANALYZER_H_

View File

@ -3,7 +3,7 @@
Copyright 2005, Mark Kretschmann <markey@web.de> Copyright 2005, Mark Kretschmann <markey@web.de>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -33,35 +33,34 @@
#include <cstdlib> #include <cstdlib>
#include <QPainter> #include <QPainter>
const uint BlockAnalyzer::HEIGHT = 2; const uint BlockAnalyzer::kHeight = 2;
const uint BlockAnalyzer::WIDTH = 4; const uint BlockAnalyzer::kWidth = 4;
const uint BlockAnalyzer::MIN_ROWS = 3; // arbituary const uint BlockAnalyzer::kMinRows = 3; // arbituary
const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary const uint BlockAnalyzer::kMinColumns = 32; // arbituary
const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n const uint BlockAnalyzer::kMaxColumns = 256; // must be 2**n
const uint BlockAnalyzer::FADE_SIZE = 90; const uint BlockAnalyzer::kFadeSize = 90;
const char* BlockAnalyzer::kName = const char* BlockAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer"); QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
BlockAnalyzer::BlockAnalyzer(QWidget* parent) BlockAnalyzer::BlockAnalyzer(QWidget* parent)
: Analyzer::Base(parent, 9), : Analyzer::Base(parent, 9),
m_columns(0), columns_(0),
m_rows(0), rows_(0),
m_y(0), y_(0),
m_barPixmap(1, 1), barPixmap_(1, 1),
m_topBarPixmap(WIDTH, HEIGHT), topBarPixmap_(kWidth, kHeight),
m_scope(MIN_COLUMNS), scope_(kMinColumns),
m_store(1 << 8, 0), store_(1 << 8, 0),
m_fade_bars(FADE_SIZE), fade_bars_(kFadeSize),
m_fade_pos(1 << 8, 50), fade_pos_(1 << 8, 50),
m_fade_intensity(1 << 8, 32) { fade_intensity_(1 << 8, 32) {
setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1, setMinimumSize(kMinColumns * (kWidth + 1) - 1, kMinRows * (kHeight + 1) - 1);
MIN_ROWS * (HEIGHT + 1) - 1);
// -1 is padding, no drawing takes place there // -1 is padding, no drawing takes place there
setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1); setMaximumWidth(kMaxColumns * (kWidth + 1) - 1);
// mxcl says null pixmaps cause crashes, so let's play it safe // mxcl says null pixmaps cause crashes, so let's play it safe
for (uint i = 0; i < FADE_SIZE; ++i) m_fade_bars[i] = QPixmap(1, 1); for (uint i = 0; i < kFadeSize; ++i) fade_bars_[i] = QPixmap(1, 1);
} }
BlockAnalyzer::~BlockAnalyzer() {} BlockAnalyzer::~BlockAnalyzer() {}
@ -69,41 +68,44 @@ BlockAnalyzer::~BlockAnalyzer() {}
void BlockAnalyzer::resizeEvent(QResizeEvent* e) { void BlockAnalyzer::resizeEvent(QResizeEvent* e) {
QWidget::resizeEvent(e); QWidget::resizeEvent(e);
m_background = QPixmap(size()); background_ = QPixmap(size());
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
const uint oldRows = m_rows; const uint oldRows = rows_;
// all is explained in analyze().. // all is explained in analyze()..
// +1 to counter -1 in maxSizes, trust me we need this! // +1 to counter -1 in maxSizes, trust me we need this!
m_columns = qMin(static_cast<uint>(static_cast<double>(width() + 1) / (WIDTH + 1)) + 1, MAX_COLUMNS); columns_ = qMin(
m_rows = static_cast<uint>(static_cast<double>(height() + 1) / (HEIGHT + 1)); static_cast<uint>(static_cast<double>(width() + 1) / (kWidth + 1)) + 1,
kMaxColumns);
rows_ = static_cast<uint>(static_cast<double>(height() + 1) / (kHeight + 1));
// this is the y-offset for drawing from the top of the widget // this is the y-offset for drawing from the top of the widget
m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2; y_ = (height() - (rows_ * (kHeight + 1)) + 2) / 2;
m_scope.resize(m_columns); scope_.resize(columns_);
if (m_rows != oldRows) { if (rows_ != oldRows) {
m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); barPixmap_ = QPixmap(kWidth, rows_ * (kHeight + 1));
for (uint i = 0; i < FADE_SIZE; ++i) for (uint i = 0; i < kFadeSize; ++i)
m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); fade_bars_[i] = QPixmap(kWidth, rows_ * (kHeight + 1));
m_yscale.resize(m_rows + 1); yscale_.resize(rows_ + 1);
const uint PRE = 1, const uint PRE = 1,
PRO = 1; // PRE and PRO allow us to restrict the range somewhat PRO = 1; // PRE and PRO allow us to restrict the range somewhat
for (uint z = 0; z < m_rows; ++z) for (uint z = 0; z < rows_; ++z)
m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO)); yscale_[z] = 1 - (log10(PRE + z) / log10(PRE + rows_ + PRO));
m_yscale[m_rows] = 0; yscale_[rows_] = 0;
determineStep(); determineStep();
paletteChange(palette()); paletteChange(palette());
} }
updateBandSize(columns_);
drawBackground(); drawBackground();
} }
@ -113,9 +115,9 @@ void BlockAnalyzer::determineStep() {
// I calculated the value 30 based on some trial and error // I calculated the value 30 based on some trial and error
// the fall time of 30 is too slow on framerates above 50fps // the fall time of 30 is too slow on framerates above 50fps
const double fallTime = timeout() < 20 ? 20 * m_rows : 30 * m_rows; const double fallTime = timeout() < 20 ? 20 * rows_ : 30 * rows_;
m_step = static_cast<double>(m_rows * timeout()) / fallTime; step_ = static_cast<double>(rows_ * timeout()) / fallTime;
} }
void BlockAnalyzer::framerateChanged() { // virtual void BlockAnalyzer::framerateChanged() { // virtual
@ -127,15 +129,14 @@ void BlockAnalyzer::transform(Analyzer::Scope& s) {
float* front = static_cast<float*>(&s.front()); float* front = static_cast<float*>(&s.front());
m_fht->spectrum(front); fht_->spectrum(front);
m_fht->scale(front, 1.0 / 20); fht_->scale(front, 1.0 / 20);
// the second half is pretty dull, so only show it if the user has a large // the second half is pretty dull, so only show it if the user has a large
// analyzer // analyzer
// by setting to m_scope.size() if large we prevent interpolation of large // by setting to scope_.size() if large we prevent interpolation of large
// analyzers, this is good! // analyzers, this is good!
s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 s.resize(scope_.size() <= kMaxColumns / 2 ? kMaxColumns / 2 : scope_.size());
: m_scope.size());
} }
void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
@ -150,7 +151,7 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
// y represents the number of blanks // y represents the number of blanks
// y starts from the top and increases in units of blocks // y starts from the top and increases in units of blocks
// m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } // yscale_ looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
// if it contains 6 elements there are 5 rows in the analyzer // if it contains 6 elements there are 5 rows in the analyzer
if (!new_frame) { if (!new_frame) {
@ -160,51 +161,55 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
QPainter canvas_painter(&canvas_); QPainter canvas_painter(&canvas_);
Analyzer::interpolate(s, m_scope); Analyzer::interpolate(s, scope_);
// update the graphics with the new colour
if (psychedelic_enabled_) {
paletteChange(QPalette());
}
// Paint the background // Paint the background
canvas_painter.drawPixmap(0, 0, m_background); canvas_painter.drawPixmap(0, 0, background_);
for (uint y, x = 0; x < m_scope.size(); ++x) { for (uint y, x = 0; x < scope_.size(); ++x) {
// determine y // determine y
for (y = 0; m_scope[x] < m_yscale[y]; ++y) for (y = 0; scope_[x] < yscale_[y]; ++y) continue;
continue;
// this is opposite to what you'd think, higher than y // this is opposite to what you'd think, higher than y
// means the bar is lower than y (physically) // means the bar is lower than y (physically)
if (static_cast<float>(y) > m_store[x]) if (static_cast<float>(y) > store_[x])
y = static_cast<int>(m_store[x] += m_step); y = static_cast<int>(store_[x] += step_);
else else
m_store[x] = y; store_[x] = y;
// if y is lower than m_fade_pos, then the bar has exceeded the height of // if y is lower than fade_pos_, then the bar has exceeded the kHeight of
// the fadeout // the fadeout
// if the fadeout is quite faded now, then display the new one // if the fadeout is quite faded now, then display the new one
if (y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) { if (y <= fade_pos_[x] /*|| fade_intensity_[x] < kFadeSize / 3*/) {
m_fade_pos[x] = y; fade_pos_[x] = y;
m_fade_intensity[x] = FADE_SIZE; fade_intensity_[x] = kFadeSize;
} }
if (m_fade_intensity[x] > 0) { if (fade_intensity_[x] > 0) {
const uint offset = --m_fade_intensity[x]; const uint offset = --fade_intensity_[x];
const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1)); const uint y = y_ + (fade_pos_[x] * (kHeight + 1));
canvas_painter.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0, canvas_painter.drawPixmap(x * (kWidth + 1), y, fade_bars_[offset], 0, 0,
WIDTH, height() - y); kWidth, height() - y);
} }
if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows; if (fade_intensity_[x] == 0) fade_pos_[x] = rows_;
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, // REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing,
// m_rows means none are // rows_ means none are
canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(), canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(),
0, y * (HEIGHT + 1), bar()->width(), 0, y * (kHeight + 1), bar()->width(),
bar()->height()); bar()->height());
} }
for (uint x = 0; x < m_store.size(); ++x) for (uint x = 0; x < store_.size(); ++x)
canvas_painter.drawPixmap(x * (WIDTH + 1), canvas_painter.drawPixmap(x * (kWidth + 1),
static_cast<int>(m_store[x]) * (HEIGHT + 1) + m_y, static_cast<int>(store_[x]) * (kHeight + 1) + y_,
m_topBarPixmap); topBarPixmap_);
p.drawPixmap(0, 0, canvas_); p.drawPixmap(0, 0, canvas_);
} }
@ -232,6 +237,12 @@ static inline void adjustToLimits(int& b, int& f, uint& amount) {
} }
} }
void BlockAnalyzer::psychedelicModeChanged(bool enabled) {
psychedelic_enabled_ = enabled;
// reset colours back to normal
paletteChange(QPalette());
}
/** /**
* Clever contrast function * Clever contrast function
* *
@ -305,7 +316,8 @@ QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
if (static_cast<int>(_amount) > 0) adjustToLimits(bs, fs, _amount); if (static_cast<int>(_amount) > 0) adjustToLimits(bs, fs, _amount);
// see if we need to adjust the hue // see if we need to adjust the hue
if (static_cast<int>(_amount) > 0) fh += static_cast<int>(_amount); // cycles around; if (static_cast<int>(_amount) > 0)
fh += static_cast<int>(_amount); // cycles around;
return QColor::fromHsv(fh, fs, fv); return QColor::fromHsv(fh, fs, fv);
} }
@ -327,23 +339,34 @@ QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
void BlockAnalyzer::paletteChange(const QPalette&) { void BlockAnalyzer::paletteChange(const QPalette&) {
const QColor bg = palette().color(QPalette::Background); const QColor bg = palette().color(QPalette::Background);
const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight)); QColor fg;
m_topBarPixmap.fill(fg); if (psychedelic_enabled_) {
fg = getPsychedelicColor(scope_, 10, 75);
} else {
fg = ensureContrast(bg, palette().color(QPalette::Highlight));
}
const double dr = 15 * static_cast<double>(bg.red() - fg.red()) / (m_rows * 16); topBarPixmap_.fill(fg);
const double dg = 15 * static_cast<double>(bg.green() - fg.green()) / (m_rows * 16);
const double db = 15 * static_cast<double>(bg.blue() - fg.blue()) / (m_rows * 16); const double dr =
15 * static_cast<double>(bg.red() - fg.red()) / (rows_ * 16);
const double dg =
15 * static_cast<double>(bg.green() - fg.green()) / (rows_ * 16);
const double db =
15 * static_cast<double>(bg.blue() - fg.blue()) / (rows_ * 16);
const int r = fg.red(), g = fg.green(), b = fg.blue(); const int r = fg.red(), g = fg.green(), b = fg.blue();
bar()->fill(bg); bar()->fill(bg);
QPainter p(bar()); QPainter p(bar());
for (int y = 0; static_cast<uint>(y) < m_rows; ++y)
for (int y = 0; static_cast<uint>(y) < rows_; ++y)
// graduate the fg color // graduate the fg color
p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT, p.fillRect(
QColor(r + static_cast<int>(dr * y), g + static_cast<int>(dg * y), 0, y * (kHeight + 1), kWidth, kHeight,
b + static_cast<int>(db * y))); QColor(r + static_cast<int>(dr * y), g + static_cast<int>(dg * y),
b + static_cast<int>(db * y)));
{ {
const QColor bg = palette().color(QPalette::Background).dark(112); const QColor bg = palette().color(QPalette::Background).dark(112);
@ -360,13 +383,15 @@ void BlockAnalyzer::paletteChange(const QPalette&) {
const int r = bg.red(), g = bg.green(), b = bg.blue(); const int r = bg.red(), g = bg.green(), b = bg.blue();
// Precalculate all fade-bar pixmaps // Precalculate all fade-bar pixmaps
for (uint y = 0; y < FADE_SIZE; ++y) { for (uint y = 0; y < kFadeSize; ++y) {
m_fade_bars[y].fill(palette().color(QPalette::Background)); fade_bars_[y].fill(palette().color(QPalette::Background));
QPainter f(&m_fade_bars[y]); QPainter f(&fade_bars_[y]);
for (int z = 0; static_cast<uint>(z) < m_rows; ++z) { for (int z = 0; static_cast<uint>(z) < rows_; ++z) {
const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE)); const double Y = 1.0 - (log10(kFadeSize - y) / log10(kFadeSize));
f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT, f.fillRect(
QColor(r + static_cast<int>(dr * Y), g + static_cast<int>(dg * Y), b + static_cast<int>(db * Y))); 0, z * (kHeight + 1), kWidth, kHeight,
QColor(r + static_cast<int>(dr * Y), g + static_cast<int>(dg * Y),
b + static_cast<int>(db * Y)));
} }
} }
} }
@ -378,11 +403,16 @@ void BlockAnalyzer::drawBackground() {
const QColor bg = palette().color(QPalette::Background); const QColor bg = palette().color(QPalette::Background);
const QColor bgdark = bg.dark(112); const QColor bgdark = bg.dark(112);
m_background.fill(bg); background_.fill(bg);
QPainter p(&m_background); QPainter p(&background_);
for (int x = 0; (uint)x < m_columns; ++x)
for (int y = 0; (uint)y < m_rows; ++y) if (p.paintEngine() == 0) {
p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT, return;
}
for (int x = 0; (uint)x < columns_; ++x)
for (int y = 0; (uint)y < rows_; ++y)
p.fillRect(x * (kWidth + 1), y * (kHeight + 1) + y_, kWidth, kHeight,
bgdark); bgdark);
} }

View File

@ -2,7 +2,7 @@
Copyright 2003-2005, Max Howell <max.howell@methylblue.com> Copyright 2003-2005, Max Howell <max.howell@methylblue.com>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -39,12 +39,12 @@ class BlockAnalyzer : public Analyzer::Base {
Q_INVOKABLE BlockAnalyzer(QWidget*); Q_INVOKABLE BlockAnalyzer(QWidget*);
~BlockAnalyzer(); ~BlockAnalyzer();
static const uint HEIGHT; static const uint kHeight;
static const uint WIDTH; static const uint kWidth;
static const uint MIN_ROWS; static const uint kMinRows;
static const uint MIN_COLUMNS; static const uint kMinColumns;
static const uint MAX_COLUMNS; static const uint kMaxColumns;
static const uint FADE_SIZE; static const uint kFadeSize;
static const char* kName; static const char* kName;
@ -54,29 +54,30 @@ class BlockAnalyzer : public Analyzer::Base {
virtual void resizeEvent(QResizeEvent*); virtual void resizeEvent(QResizeEvent*);
virtual void paletteChange(const QPalette&); virtual void paletteChange(const QPalette&);
virtual void framerateChanged(); virtual void framerateChanged();
virtual void psychedelicModeChanged(bool);
void drawBackground(); void drawBackground();
void determineStep(); void determineStep();
private: private:
QPixmap* bar() { return &m_barPixmap; } QPixmap* bar() { return &barPixmap_; }
uint m_columns, m_rows; // number of rows and columns of blocks uint columns_, rows_; // number of rows and columns of blocks
uint m_y; // y-offset from top of widget uint y_; // y-offset from top of widget
QPixmap m_barPixmap; QPixmap barPixmap_;
QPixmap m_topBarPixmap; QPixmap topBarPixmap_;
QPixmap m_background; QPixmap background_;
QPixmap canvas_; QPixmap canvas_;
Analyzer::Scope m_scope; // so we don't create a vector every frame Analyzer::Scope scope_; // so we don't create a vector every frame
std::vector<float> m_store; // current bar heights std::vector<float> store_; // current bar kHeights
std::vector<float> m_yscale; std::vector<float> yscale_;
// FIXME why can't I namespace these? c++ issue? // FIXME why can't I namespace these? c++ issue?
std::vector<QPixmap> m_fade_bars; std::vector<QPixmap> fade_bars_;
std::vector<uint> m_fade_pos; std::vector<uint> fade_pos_;
std::vector<int> m_fade_intensity; std::vector<int> fade_intensity_;
float m_step; // rows to fall per frame float step_; // rows to fall per frame
}; };
#endif // ANALYZERS_BLOCKANALYZER_H_ #endif // ANALYZERS_BLOCKANALYZER_H_

View File

@ -2,7 +2,7 @@
Copyright 2004, Max Howell <max.howell@methylblue.com> Copyright 2004, Max Howell <max.howell@methylblue.com>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -28,69 +28,78 @@
using Analyzer::Scope; using Analyzer::Scope;
const uint BoomAnalyzer::kColumnWidth = 4;
const uint BoomAnalyzer::kMaxBandCount = 256;
const uint BoomAnalyzer::kMinBandCount = 32;
const char* BoomAnalyzer::kName = const char* BoomAnalyzer::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer"); QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
BoomAnalyzer::BoomAnalyzer(QWidget* parent) BoomAnalyzer::BoomAnalyzer(QWidget* parent)
: Analyzer::Base(parent, 9), : Analyzer::Base(parent, 9),
K_barHeight(1.271) // 1.471 bands_(0),
scope_(kMinBandCount),
fg_(palette().color(QPalette::Highlight)),
K_barHeight_(1.271) // 1.471
, ,
F_peakSpeed(1.103) // 1.122 F_peakSpeed_(1.103) // 1.122
, ,
F(1.0), F_(1.0),
bar_height(BAND_COUNT, 0), bar_height_(kMaxBandCount, 0),
peak_height(BAND_COUNT, 0), peak_height_(kMaxBandCount, 0),
peak_speed(BAND_COUNT, 0.01), peak_speed_(kMaxBandCount, 0.01),
barPixmap(COLUMN_WIDTH, 50) {} barPixmap_(kColumnWidth, 50) {
setMinimumWidth(kMinBandCount * (kColumnWidth + 1) - 1);
setMaximumWidth(kMaxBandCount * (kColumnWidth + 1) - 1);
}
void BoomAnalyzer::changeK_barHeight(int newValue) { void BoomAnalyzer::changeK_barHeight(int newValue) {
K_barHeight = static_cast<double>(newValue) / 1000; K_barHeight_ = static_cast<double>(newValue) / 1000;
} }
void BoomAnalyzer::changeF_peakSpeed(int newValue) { void BoomAnalyzer::changeF_peakSpeed(int newValue) {
F_peakSpeed = static_cast<double>(newValue) / 1000; F_peakSpeed_ = static_cast<double>(newValue) / 1000;
} }
void BoomAnalyzer::resizeEvent(QResizeEvent*) { init(); } void BoomAnalyzer::resizeEvent(QResizeEvent* e) {
QWidget::resizeEvent(e);
void BoomAnalyzer::init() {
const uint HEIGHT = height() - 2; const uint HEIGHT = height() - 2;
const double h = 1.2 / HEIGHT; const double h = 1.2 / HEIGHT;
F = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/); bands_ = qMin(
static_cast<uint>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) +
1,
kMaxBandCount);
scope_.resize(bands_);
barPixmap = QPixmap(COLUMN_WIDTH - 2, HEIGHT); F_ = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
QPainter p(&barPixmap); QPainter p(&barPixmap_);
for (uint y = 0; y < HEIGHT; ++y) { for (uint y = 0; y < HEIGHT; ++y) {
const double F = static_cast<double>(y) * h; const double F = static_cast<double>(y) * h;
p.setPen(QColor(qMax(0, 255 - static_cast<int>(229.0 * F)), p.setPen(QColor(qMax(0, 255 - static_cast<int>(229.0 * F)),
qMax(0, 255 - static_cast<int>(229.0 * F)), qMax(0, 255 - static_cast<int>(229.0 * F)),
qMax(0, 255 - static_cast<int>(191.0 * F)))); qMax(0, 255 - static_cast<int>(191.0 * F))));
p.drawLine(0, y, COLUMN_WIDTH - 2, y); p.drawLine(0, y, kColumnWidth - 2, y);
} }
updateBandSize(bands_);
} }
void BoomAnalyzer::transform(Scope& s) { void BoomAnalyzer::transform(Scope& s) {
float* front = static_cast<float*>(&s.front()); float* front = static_cast<float*>(&s.front());
m_fht->spectrum(front); fht_->spectrum(front);
m_fht->scale(front, 1.0 / 60); fht_->scale(front, 1.0 / 50);
Scope scope(32, 0); s.resize(scope_.size() <= kMaxBandCount / 2 ? kMaxBandCount / 2
: scope_.size());
const uint xscale[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 19, 24, 29, 36,
43, 52, 63, 76, 91, 108, 129, 153, 182, 216, 255};
for (uint j, i = 0; i < 32; i++)
for (j = xscale[i]; j < xscale[i + 1]; j++)
if (s[j] > scope[i]) scope[i] = s[j];
s = scope;
} }
void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) { void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
@ -104,47 +113,70 @@ void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
QPainter canvas_painter(&canvas_); QPainter canvas_painter(&canvas_);
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) { Analyzer::interpolate(scope, scope_);
h = log10(scope[i] * 256.0) * F;
// update the graphics with the new colour
if (psychedelic_enabled_) {
paletteChange(QPalette());
}
for (uint i = 0, x = 0, y; i < bands_; ++i, x += kColumnWidth + 1) {
h = log10(scope_[i] * 256.0) * F_;
if (h > MAX_HEIGHT) h = MAX_HEIGHT; if (h > MAX_HEIGHT) h = MAX_HEIGHT;
if (h > bar_height[i]) { if (h > bar_height_[i]) {
bar_height[i] = h; bar_height_[i] = h;
if (h > peak_height[i]) { if (h > peak_height_[i]) {
peak_height[i] = h; peak_height_[i] = h;
peak_speed[i] = 0.01; peak_speed_[i] = 0.01;
} else { } else {
goto peak_handling; goto peak_handling;
} }
} else { } else {
if (bar_height[i] > 0.0) { if (bar_height_[i] > 0.0) {
bar_height[i] -= K_barHeight; // 1.4 bar_height_[i] -= K_barHeight_; // 1.4
if (bar_height[i] < 0.0) bar_height[i] = 0.0; if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
} }
peak_handling: peak_handling:
if (peak_height[i] > 0.0) { if (peak_height_[i] > 0.0) {
peak_height[i] -= peak_speed[i]; peak_height_[i] -= peak_speed_[i];
peak_speed[i] *= F_peakSpeed; // 1.12 peak_speed_[i] *= F_peakSpeed_; // 1.12
if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i]; if (peak_height_[i] < bar_height_[i]) peak_height_[i] = bar_height_[i];
if (peak_height[i] < 0.0) peak_height[i] = 0.0; if (peak_height_[i] < 0.0) peak_height_[i] = 0.0;
} }
} }
y = height() - uint(bar_height[i]); y = height() - uint(bar_height_[i]);
canvas_painter.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1); canvas_painter.drawPixmap(x + 1, y, barPixmap_, 0, y, -1, -1);
canvas_painter.setPen(palette().color(QPalette::Highlight)); canvas_painter.setPen(fg_);
if (bar_height[i] > 0) if (bar_height_[i] > 0)
canvas_painter.drawRect(x, y, COLUMN_WIDTH - 1, height() - y - 1); canvas_painter.drawRect(x, y, kColumnWidth - 1, height() - y - 1);
y = height() - uint(peak_height[i]); y = height() - uint(peak_height_[i]);
canvas_painter.setPen(palette().color(QPalette::Base)); canvas_painter.setPen(palette().color(QPalette::Midlight));
canvas_painter.drawLine(x, y, x + COLUMN_WIDTH - 1, y); canvas_painter.drawLine(x, y, x + kColumnWidth - 1, y);
} }
p.drawPixmap(0, 0, canvas_); p.drawPixmap(0, 0, canvas_);
} }
void BoomAnalyzer::psychedelicModeChanged(bool enabled) {
psychedelic_enabled_ = enabled;
// reset colours back to normal
paletteChange(QPalette());
}
void BoomAnalyzer::paletteChange(const QPalette&) {
if (psychedelic_enabled_) {
fg_ = getPsychedelicColor(scope_, 50, 100);
} else {
// the highlight colour changes when the main window loses focus,
// so we use save and use the focused colour
fg_ = palette().color(QPalette::Highlight);
}
}

View File

@ -1,7 +1,7 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2004, Max Howell <max.howell@methylblue.com> Copyright 2004, Max Howell <max.howell@methylblue.com>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com> Copyright 2014, John Maguire <john.maguire@gmail.com>
@ -27,10 +27,6 @@
#include "analyzerbase.h" #include "analyzerbase.h"
/**
@author Max Howell
*/
class BoomAnalyzer : public Analyzer::Base { class BoomAnalyzer : public Analyzer::Base {
Q_OBJECT Q_OBJECT
@ -39,9 +35,9 @@ class BoomAnalyzer : public Analyzer::Base {
static const char* kName; static const char* kName;
virtual void init();
virtual void transform(Analyzer::Scope& s); virtual void transform(Analyzer::Scope& s);
virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame); virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame);
virtual void psychedelicModeChanged(bool);
public slots: public slots:
void changeK_barHeight(int); void changeK_barHeight(int);
@ -49,17 +45,23 @@ class BoomAnalyzer : public Analyzer::Base {
protected: protected:
void resizeEvent(QResizeEvent* e); void resizeEvent(QResizeEvent* e);
void paletteChange(const QPalette&);
static const uint COLUMN_WIDTH = 4; static const uint kColumnWidth;
static const uint BAND_COUNT = 32; static const uint kMaxBandCount;
static const uint kMinBandCount;
double K_barHeight, F_peakSpeed, F; uint bands_;
Analyzer::Scope scope_;
QColor fg_;
std::vector<float> bar_height; double K_barHeight_, F_peakSpeed_, F_;
std::vector<float> peak_height;
std::vector<float> peak_speed;
QPixmap barPixmap; std::vector<float> bar_height_;
std::vector<float> peak_height_;
std::vector<float> peak_speed_;
QPixmap barPixmap_;
QPixmap canvas_; QPixmap canvas_;
}; };

View File

@ -24,70 +24,69 @@
#include <string.h> #include <string.h>
#include "fht.h" #include "fht.h"
FHT::FHT(int n) : m_buf(0), m_tab(0), m_log(0) { FHT::FHT(int n) : buf_(0), tab_(0), log_(0) {
if (n < 3) { if (n < 3) {
m_num = 0; num_ = 0;
m_exp2 = -1; exp2_ = -1;
return; return;
} }
m_exp2 = n; exp2_ = n;
m_num = 1 << n; num_ = 1 << n;
if (n > 3) { if (n > 3) {
m_buf = new float[m_num]; buf_ = new float[num_];
m_tab = new float[m_num * 2]; tab_ = new float[num_ * 2];
makeCasTable(); makeCasTable();
} }
} }
FHT::~FHT() { FHT::~FHT() {
delete[] m_buf; delete[] buf_;
delete[] m_tab; delete[] tab_;
delete[] m_log; delete[] log_;
} }
void FHT::makeCasTable(void) { void FHT::makeCasTable(void) {
float d, *costab, *sintab; float d, *costab, *sintab;
int ul, ndiv2 = m_num / 2; int ul, ndiv2 = num_ / 2;
for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; for (costab = tab_, sintab = tab_ + num_ / 2 + 1, ul = 0; ul < num_; ul++) {
ul++) {
d = M_PI * ul / ndiv2; d = M_PI * ul / ndiv2;
*costab = *sintab = cos(d); *costab = *sintab = cos(d);
costab += 2, sintab += 2; costab += 2, sintab += 2;
if (sintab > m_tab + m_num * 2) sintab = m_tab + 1; if (sintab > tab_ + num_ * 2) sintab = tab_ + 1;
} }
} }
float* FHT::copy(float* d, float* s) { float* FHT::copy(float* d, float* s) {
return static_cast<float*>(memcpy(d, s, m_num * sizeof(float))); return static_cast<float*>(memcpy(d, s, num_ * sizeof(float)));
} }
float* FHT::clear(float* d) { float* FHT::clear(float* d) {
return static_cast<float*>(memset(d, 0, m_num * sizeof(float))); return static_cast<float*>(memset(d, 0, num_ * sizeof(float)));
} }
void FHT::scale(float* p, float d) { void FHT::scale(float* p, float d) {
for (int i = 0; i < (m_num / 2); i++) *p++ *= d; for (int i = 0; i < (num_ / 2); i++) *p++ *= d;
} }
void FHT::ewma(float* d, float* s, float w) { void FHT::ewma(float* d, float* s, float w) {
for (int i = 0; i < (m_num / 2); i++, d++, s++) *d = *d * w + *s * (1 - w); for (int i = 0; i < (num_ / 2); i++, d++, s++) *d = *d * w + *s * (1 - w);
} }
void FHT::logSpectrum(float* out, float* p) { void FHT::logSpectrum(float* out, float* p) {
int n = m_num / 2, i, j, k, *r; int n = num_ / 2, i, j, k, *r;
if (!m_log) { if (!log_) {
m_log = new int[n]; log_ = new int[n];
float f = n / log10(static_cast<double>(n)); float f = n / log10(static_cast<double>(n));
for (i = 0, r = m_log; i < n; i++, r++) { for (i = 0, r = log_; i < n; i++, r++) {
j = static_cast<int>(rint(log10(i + 1.0) * f)); j = static_cast<int>(rint(log10(i + 1.0) * f));
*r = j >= n ? n - 1 : j; *r = j >= n ? n - 1 : j;
} }
} }
semiLogSpectrum(p); semiLogSpectrum(p);
*out++ = *p = *p / 100; *out++ = *p = *p / 100;
for (k = i = 1, r = m_log; i < n; i++) { for (k = i = 1, r = log_; i < n; i++) {
j = *r++; j = *r++;
if (i == j) { if (i == j) {
*out++ = p[i]; *out++ = p[i];
@ -102,7 +101,7 @@ void FHT::logSpectrum(float* out, float* p) {
void FHT::semiLogSpectrum(float* p) { void FHT::semiLogSpectrum(float* p) {
float e; float e;
power2(p); power2(p);
for (int i = 0; i < (m_num / 2); i++, p++) { for (int i = 0; i < (num_ / 2); i++, p++) {
e = 10.0 * log10(sqrt(*p * .5)); e = 10.0 * log10(sqrt(*p * .5));
*p = e < 0 ? 0 : e; *p = e < 0 ? 0 : e;
} }
@ -110,31 +109,31 @@ void FHT::semiLogSpectrum(float* p) {
void FHT::spectrum(float* p) { void FHT::spectrum(float* p) {
power2(p); power2(p);
for (int i = 0; i < (m_num / 2); i++, p++) for (int i = 0; i < (num_ / 2); i++, p++)
*p = static_cast<float>(sqrt(*p * .5)); *p = static_cast<float>(sqrt(*p * .5));
} }
void FHT::power(float* p) { void FHT::power(float* p) {
power2(p); power2(p);
for (int i = 0; i < (m_num / 2); i++) *p++ *= .5; for (int i = 0; i < (num_ / 2); i++) *p++ *= .5;
} }
void FHT::power2(float* p) { void FHT::power2(float* p) {
int i; int i;
float* q; float* q;
_transform(p, m_num, 0); _transform(p, num_, 0);
*p = (*p * *p), *p += *p, p++; *p = (*p * *p), *p += *p, p++;
for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q) for (i = 1, q = p + num_ - 2; i < (num_ / 2); i++, --q)
*p = (*p * *p) + (*q * *q), p++; *p = (*p * *p) + (*q * *q), p++;
} }
void FHT::transform(float* p) { void FHT::transform(float* p) {
if (m_num == 8) if (num_ == 8)
transform8(p); transform8(p);
else else
_transform(p, m_num, 0); _transform(p, num_, 0);
} }
void FHT::transform8(float* p) { void FHT::transform8(float* p) {
@ -173,19 +172,19 @@ void FHT::_transform(float* p, int n, int k) {
int i, j, ndiv2 = n / 2; int i, j, ndiv2 = n / 2;
float a, *t1, *t2, *t3, *t4, *ptab, *pp; float a, *t1, *t2, *t3, *t4, *ptab, *pp;
for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++) for (i = 0, t1 = buf_, t2 = buf_ + ndiv2, pp = &p[k]; i < ndiv2; i++)
*t1++ = *pp++, *t2++ = *pp++; *t1++ = *pp++, *t2++ = *pp++;
memcpy(p + k, m_buf, sizeof(float) * n); memcpy(p + k, buf_, sizeof(float) * n);
_transform(p, ndiv2, k); _transform(p, ndiv2, k);
_transform(p, ndiv2, k + ndiv2); _transform(p, ndiv2, k + ndiv2);
j = m_num / ndiv2 - 1; j = num_ / ndiv2 - 1;
t1 = m_buf; t1 = buf_;
t2 = t1 + ndiv2; t2 = t1 + ndiv2;
t3 = p + k + ndiv2; t3 = p + k + ndiv2;
ptab = m_tab; ptab = tab_;
pp = p + k; pp = p + k;
a = *ptab++ * *t3++; a = *ptab++ * *t3++;
@ -202,5 +201,5 @@ void FHT::_transform(float* p, int n, int k) {
*t1++ = *pp + a; *t1++ = *pp + a;
*t2++ = *pp++ - a; *t2++ = *pp++ - a;
} }
memcpy(p + k, m_buf, sizeof(float) * n); memcpy(p + k, buf_, sizeof(float) * n);
} }

View File

@ -32,11 +32,11 @@
* [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379
*/ */
class FHT { class FHT {
int m_exp2; int exp2_;
int m_num; int num_;
float* m_buf; float* buf_;
float* m_tab; float* tab_;
int* m_log; int* log_;
/** /**
* Create a table of "cas" (cosine and sine) values. * Create a table of "cas" (cosine and sine) values.
@ -59,8 +59,8 @@ class FHT {
explicit FHT(int); explicit FHT(int);
~FHT(); ~FHT();
inline int sizeExp() const { return m_exp2; } inline int sizeExp() const { return exp2_; }
inline int size() const { return m_num; } inline int size() const { return num_; }
float* copy(float*, float*); float* copy(float*, float*);
float* clear(float*); float* clear(float*);
void scale(float*, float); void scale(float*, float);

View File

@ -57,7 +57,7 @@ NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent)
} }
} }
void NyanCatAnalyzer::transform(Scope& s) { m_fht->spectrum(&s.front()); } void NyanCatAnalyzer::transform(Scope& s) { fht_->spectrum(&s.front()); }
void NyanCatAnalyzer::timerEvent(QTimerEvent* e) { void NyanCatAnalyzer::timerEvent(QTimerEvent* e) {
if (e->timerId() == timer_id_) { if (e->timerId() == timer_id_) {
@ -74,7 +74,8 @@ void NyanCatAnalyzer::resizeEvent(QResizeEvent* e) {
buffer_[1] = QPixmap(); buffer_[1] = QPixmap();
available_rainbow_width_ = width() - kCatWidth + kRainbowOverlap; available_rainbow_width_ = width() - kCatWidth + kRainbowOverlap;
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1; px_per_frame_ =
static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_; x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
} }
@ -112,11 +113,13 @@ void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
QPointF* dest = polyline; QPointF* dest = polyline;
float* source = history_; float* source = history_;
const float top_of_cat = static_cast<float>(height()) / 2 - static_cast<float>(kCatHeight) / 2; const float top_of_cat =
static_cast<float>(height()) / 2 - static_cast<float>(kCatHeight) / 2;
for (int band = 0; band < kRainbowBands; ++band) { for (int band = 0; band < kRainbowBands; ++band) {
// Calculate the Y position of this band. // Calculate the Y position of this band.
const float y = const float y =
static_cast<float>(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) + top_of_cat; static_cast<float>(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) +
top_of_cat;
// Add each point in the line. // Add each point in the line.
for (int x = 0; x < kHistorySize; ++x) { for (int x = 0; x < kHistorySize; ++x) {

View File

@ -1,6 +1,6 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com> Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, David Sansome <me@davidsansome.com> Copyright 2014, David Sansome <me@davidsansome.com>
@ -57,7 +57,7 @@ RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget* parent)
} }
} }
void RainbowDashAnalyzer::transform(Scope& s) { m_fht->spectrum(&s.front()); } void RainbowDashAnalyzer::transform(Scope& s) { fht_->spectrum(&s.front()); }
void RainbowDashAnalyzer::timerEvent(QTimerEvent* e) { void RainbowDashAnalyzer::timerEvent(QTimerEvent* e) {
if (e->timerId() == timer_id_) { if (e->timerId() == timer_id_) {
@ -74,7 +74,8 @@ void RainbowDashAnalyzer::resizeEvent(QResizeEvent* e) {
buffer_[1] = QPixmap(); buffer_[1] = QPixmap();
available_rainbow_width_ = width() - kDashWidth + kRainbowOverlap; available_rainbow_width_ = width() - kDashWidth + kRainbowOverlap;
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1; px_per_frame_ =
static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_; x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
} }
@ -111,12 +112,13 @@ void RainbowDashAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
QPointF* dest = polyline; QPointF* dest = polyline;
float* source = history_; float* source = history_;
const float top_of_Dash = static_cast<float>(height()) / 2 - static_cast<float>(kRainbowHeight) / 2; const float top_of_Dash = static_cast<float>(height()) / 2 -
static_cast<float>(kRainbowHeight) / 2;
for (int band = 0; band < kRainbowBands; ++band) { for (int band = 0; band < kRainbowBands; ++band) {
// Calculate the Y position of this band. // Calculate the Y position of this band.
const float y = const float y = static_cast<float>(kRainbowHeight) / (kRainbowBands + 1) *
static_cast<float>(kRainbowHeight) / (kRainbowBands + 1) * (band + 0.5) + (band + 0.5) +
top_of_Dash; top_of_Dash;
// Add each point in the line. // Add each point in the line.
for (int x = 0; x < kHistorySize; ++x) { for (int x = 0; x < kHistorySize; ++x) {

View File

@ -1,6 +1,6 @@
/* This file is part of Clementine. /* This file is part of Clementine.
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com> Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify

View File

@ -2,7 +2,7 @@
Copyright 2004, Melchior FRANZ <mfranz@kde.org> Copyright 2004, Melchior FRANZ <mfranz@kde.org>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com> Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
@ -31,7 +31,8 @@ using Analyzer::Scope;
const char* Sonogram::kName = const char* Sonogram::kName =
QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram"); QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
Sonogram::Sonogram(QWidget* parent) : Analyzer::Base(parent, 9) {} Sonogram::Sonogram(QWidget* parent)
: Analyzer::Base(parent, 9), scope_size_(128) {}
Sonogram::~Sonogram() {} Sonogram::~Sonogram() {}
@ -45,6 +46,12 @@ void Sonogram::resizeEvent(QResizeEvent* e) {
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
updateBandSize(scope_size_);
}
void Sonogram::psychedelicModeChanged(bool enabled) {
psychedelic_enabled_ = enabled;
updateBandSize(scope_size_);
} }
void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) { void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
@ -60,20 +67,45 @@ void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, x, -1); canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, x, -1);
Scope::const_iterator it = s.begin(), end = s.end(); Scope::const_iterator it = s.begin(), end = s.end();
for (int y = height() - 1; y;) { if (scope_size_ != s.size()) {
if (it >= end || *it < .005) scope_size_ = s.size();
c = palette().color(QPalette::Background); updateBandSize(scope_size_);
else if (*it < .05) }
c.setHsv(95, 255, 255 - static_cast<int>(*it * 4000.0));
else if (*it < 1.0)
c.setHsv(95 - static_cast<int>(*it * 90.0), 255, 255);
else
c = Qt::red;
canvas_painter.setPen(c); if (psychedelic_enabled_) {
canvas_painter.drawPoint(x, y--); c = getPsychedelicColor(s, 20, 100);
for (int y = height() - 1; y;) {
if (it >= end || *it < .005) {
c = palette().color(QPalette::Background);
} else if (*it < .05) {
c.setHsv(c.hue(), c.saturation(), 255 - static_cast<int>(*it * 4000.0));
} else if (*it < 1.0) {
c.setHsv((c.hue() + static_cast<int>(*it * 90.0)) % 255, 255, 255);
} else {
c = getPsychedelicColor(s, 10, 50);
}
if (it < end) ++it; canvas_painter.setPen(c);
canvas_painter.drawPoint(x, y--);
if (it < end) ++it;
}
} else {
for (int y = height() - 1; y;) {
if (it >= end || *it < .005)
c = palette().color(QPalette::Background);
else if (*it < .05)
c.setHsv(95, 255, 255 - static_cast<int>(*it * 4000.0));
else if (*it < 1.0)
c.setHsv(95 - static_cast<int>(*it * 90.0), 255, 255);
else
c = Qt::red;
canvas_painter.setPen(c);
canvas_painter.drawPoint(x, y--);
if (it < end) ++it;
}
} }
canvas_painter.end(); canvas_painter.end();
@ -83,11 +115,11 @@ void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
void Sonogram::transform(Scope& scope) { void Sonogram::transform(Scope& scope) {
float* front = static_cast<float*>(&scope.front()); float* front = static_cast<float*>(&scope.front());
m_fht->power2(front); fht_->power2(front);
m_fht->scale(front, 1.0 / 256); fht_->scale(front, 1.0 / 256);
scope.resize(m_fht->size() / 2); scope.resize(fht_->size() / 2);
} }
void Sonogram::demo(QPainter& p) { void Sonogram::demo(QPainter& p) {
analyze(p, Scope(m_fht->size(), 0), new_frame_); analyze(p, Scope(fht_->size(), 0), new_frame_);
} }

View File

@ -3,6 +3,7 @@
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com> Copyright 2014, John Maguire <john.maguire@gmail.com>
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
Clementine is free software: you can redistribute it and/or modify Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -26,10 +27,6 @@
#include "analyzerbase.h" #include "analyzerbase.h"
/**
@author Melchior FRANZ
*/
class Sonogram : public Analyzer::Base { class Sonogram : public Analyzer::Base {
Q_OBJECT Q_OBJECT
public: public:
@ -43,8 +40,10 @@ class Sonogram : public Analyzer::Base {
void transform(Analyzer::Scope&); void transform(Analyzer::Scope&);
void demo(QPainter& p); void demo(QPainter& p);
void resizeEvent(QResizeEvent*); void resizeEvent(QResizeEvent*);
void psychedelicModeChanged(bool);
QPixmap canvas_; QPixmap canvas_;
int scope_size_;
}; };
#endif // ANALYZERS_SONOGRAM_H_ #endif // ANALYZERS_SONOGRAM_H_

View File

@ -2,7 +2,7 @@
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net> Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
Copyright 2003, Max Howell <max.howell@methylblue.com> Copyright 2003, Max Howell <max.howell@methylblue.com>
Copyright 2009-2010, David Sansome <davidsansome@gmail.com> Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
Copyright 2014, Mark Furneaux <mark@romaco.ca> Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com> Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com> Copyright 2014, John Maguire <john.maguire@gmail.com>
@ -21,7 +21,7 @@
*/ */
/* Original Author: Stanislav Karchebny <berkus@users.sf.net> 2003 /* Original Author: Stanislav Karchebny <berkus@users.sf.net> 2003
* Original Author: Max Howell <max.howell@methylblue.com> 2003 * Original Author: Max Howell <max.howell@methylblue.com> 2003
*/ */
#include <cmath> #include <cmath>
@ -42,58 +42,64 @@ void TurbineAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
float h; float h;
const uint hd2 = height() / 2; const uint hd2 = height() / 2;
const uint MAX_HEIGHT = hd2 - 1; const uint kMaxHeight = hd2 - 1;
QPainter canvas_painter(&canvas_); QPainter canvas_painter(&canvas_);
canvas_.fill(palette().color(QPalette::Background)); canvas_.fill(palette().color(QPalette::Background));
for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) { Analyzer::interpolate(scope, scope_);
h = log10(scope[i] * 256.0) * F * 0.5;
if (h > MAX_HEIGHT) h = MAX_HEIGHT; // update the graphics with the new colour
if (psychedelic_enabled_) {
paletteChange(QPalette());
}
if (h > bar_height[i]) { for (uint i = 0, x = 0, y; i < bands_; ++i, x += kColumnWidth + 1) {
bar_height[i] = h; h = log10(scope_[i] * 256.0) * F_ * 0.5;
if (h > peak_height[i]) { if (h > kMaxHeight) h = kMaxHeight;
peak_height[i] = h;
peak_speed[i] = 0.01; if (h > bar_height_[i]) {
bar_height_[i] = h;
if (h > peak_height_[i]) {
peak_height_[i] = h;
peak_speed_[i] = 0.01;
} else { } else {
goto peak_handling; goto peak_handling;
} }
} else { } else {
if (bar_height[i] > 0.0) { if (bar_height_[i] > 0.0) {
bar_height[i] -= K_barHeight; // 1.4 bar_height_[i] -= K_barHeight_; // 1.4
if (bar_height[i] < 0.0) bar_height[i] = 0.0; if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
} }
peak_handling: peak_handling:
if (peak_height[i] > 0.0) { if (peak_height_[i] > 0.0) {
peak_height[i] -= peak_speed[i]; peak_height_[i] -= peak_speed_[i];
peak_speed[i] *= F_peakSpeed; // 1.12 peak_speed_[i] *= F_peakSpeed_; // 1.12
if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i]; if (peak_height_[i] < bar_height_[i]) peak_height_[i] = bar_height_[i];
if (peak_height[i] < 0.0) peak_height[i] = 0.0; if (peak_height_[i] < 0.0) peak_height_[i] = 0.0;
} }
} }
y = hd2 - static_cast<uint>(bar_height[i]); y = hd2 - static_cast<uint>(bar_height_[i]);
canvas_painter.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1); canvas_painter.drawPixmap(x + 1, y, barPixmap_, 0, y, -1, -1);
canvas_painter.drawPixmap(x + 1, hd2, barPixmap, 0, canvas_painter.drawPixmap(x + 1, hd2, barPixmap_, 0,
static_cast<int>(bar_height[i]), static_cast<int>(bar_height_[i]), -1, -1);
-1, -1);
canvas_painter.setPen(palette().color(QPalette::Highlight)); canvas_painter.setPen(fg_);
if (bar_height[i] > 0) if (bar_height_[i] > 0)
canvas_painter.drawRect(x, y, COLUMN_WIDTH - 1, canvas_painter.drawRect(x, y, kColumnWidth - 1,
static_cast<int>(bar_height[i]) * 2 - 1); static_cast<int>(bar_height_[i]) * 2 - 1);
const uint x2 = x + COLUMN_WIDTH - 1; const uint x2 = x + kColumnWidth - 1;
canvas_painter.setPen(palette().color(QPalette::Base)); canvas_painter.setPen(palette().color(QPalette::Midlight));
y = hd2 - uint(peak_height[i]); y = hd2 - uint(peak_height_[i]);
canvas_painter.drawLine(x, y, x2, y); canvas_painter.drawLine(x, y, x2, y);
y = hd2 + uint(peak_height[i]); y = hd2 + uint(peak_height_[i]);
canvas_painter.drawLine(x, y, x2, y); canvas_painter.drawLine(x, y, x2, y);
} }