diff --git a/data/data.qrc b/data/data.qrc index 5af418be..88f96384 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -29,6 +29,8 @@ pictures/osd_shadow_corner.png pictures/osd_shadow_edge.png pictures/deezer.png - fonts/HumongousofEternitySt.ttf + pictures/nyancat.png + pictures/rainbowdash.png + fonts/HumongousofEternitySt.ttf diff --git a/data/pictures/nyancat.png b/data/pictures/nyancat.png new file mode 100644 index 00000000..7ce86474 Binary files /dev/null and b/data/pictures/nyancat.png differ diff --git a/data/pictures/rainbowdash.png b/data/pictures/rainbowdash.png new file mode 100644 index 00000000..db52cb5b Binary files /dev/null and b/data/pictures/rainbowdash.png differ diff --git a/dist/debian/copyright b/dist/debian/copyright index 547e559e..561d8e8a 100644 --- a/dist/debian/copyright +++ b/dist/debian/copyright @@ -59,8 +59,8 @@ Files: src/core/main.h src/scrobbler/* src/tidal/* src/deezer/* - transcoderoptionswavpack.cpp - transcoderoptionswavpack.h + src/transcoder/transcoderoptionswavpack.cpp + src/transcoder/transcoderoptionswavpack.h Copyright: 2012-2014, 2017-2018, Jonas Kvinge License: GPL-3+ @@ -195,18 +195,46 @@ License: GPL-3+ Files: src/analyzer/fht.cpp src/analyzer/fht.h -Copyright: 2004, Melchior FRANZ - mfranz@kde.org +Copyright: 2004, Melchior FRANZ + 2010, 2014, John Maguire + 2014, Krzysztof Sobiecki + 2017, Santiago Gil License: GPL-2+ Files: src/analyzer/analyzerbase.cpp src/analyzer/analyzerbase.h Copyright: 2003-2005, Max Howell + 2009-2012, David Sansome + 2010, 2012, 2014, John Maguire + 2017, Santiago Gil License: GPL-2+ Files: src/analyzer/blockanalyzer.cpp src/analyzer/blockanalyzer.h Copyright: 2003-2005, Max Howell 2005, Mark Kretschmann + 2009-2010, David Sansome + 2010, 2014, John Maguire +License: GPL-2+ + +Files: src/analyzer/boomanalyzer.cpp + src/analyzer/boomanalyzer.h +Copyright: 2004, Max Howell + 2009-2010, David Sansome + 2010, 2014, John Maguire + 2014-2015, Mark Furneaux + 2014, Krzysztof Sobiecki +License: GPL-2+ + +Files: src/analyzer/rainbowanalyzer.cpp + src/analyzer/rainbowanalyzer.h +Copyright: 2011, Tyler Rhodes + 2011-2012, 2014, David Sansome + 2011, 2014, John Maguire + 2014, Alibek Omarov + 2014, Krzysztof Sobiecki + 2014-2015, Mark Furneaux + 2015, Arun Narayanankutty License: GPL-2+ Files: src/collection/savedgroupingmanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d684a612..cab682bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -125,6 +125,8 @@ set(SOURCES analyzer/analyzerbase.cpp analyzer/analyzercontainer.cpp analyzer/blockanalyzer.cpp + analyzer/boomanalyzer.cpp + analyzer/rainbowanalyzer.cpp equalizer/equalizer.cpp equalizer/equalizerslider.cpp @@ -307,6 +309,8 @@ set(HEADERS analyzer/analyzerbase.h analyzer/analyzercontainer.h analyzer/blockanalyzer.h + analyzer/boomanalyzer.h + analyzer/rainbowanalyzer.h equalizer/equalizer.h equalizer/equalizerslider.h diff --git a/src/analyzer/analyzerbase.cpp b/src/analyzer/analyzerbase.cpp index 5536e965..37208655 100644 --- a/src/analyzer/analyzerbase.cpp +++ b/src/analyzer/analyzerbase.cpp @@ -1,32 +1,40 @@ -/*************************************************************************** - viswidget.cpp - description - ------------------- - begin : Die Jan 7 2003 - copyright : (C) 2003 by Max Howell - email : markey@web.de - ***************************************************************************/ +/* + Strawberry Music Player + This file was part of Amarok. + Copyright 2003-2004, Max Howell + Copyright 2009-2012, David Sansome + Copyright 2010, 2012, 2014, John Maguire + Copyright 2017, Santiago Gil -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ #include "config.h" +#include "analyzerbase.h" + +#include #include +#include #include +#include #include #include #include #include -#include "analyzerbase.h" - #include "core/logging.h" #include "engine/enginebase.h" @@ -36,10 +44,11 @@ // 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it // 4. if you want to manipulate the scope, reimplement transform() // 5. for convenience are pre-included -// TODO make an INSTRUCTIONS file +// +// TODO: +// Make an INSTRUCTIONS file // can't mod scope in analyze you have to use transform - -// TODO for 2D use setErasePixmap Qt function insetead of m_background +// for 2D use setErasePixmap Qt function insetead of m_background // make the linker happy only for gcc < 4.0 #if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \ @@ -53,7 +62,6 @@ Analyzer::Base::Base(QWidget *parent, uint scopeSize) fht_(new FHT(scopeSize)), engine_(nullptr), lastscope_(512), - current_chunk_(0), new_frame_(false), is_playing_(false) {} @@ -63,19 +71,18 @@ void Analyzer::Base::showEvent(QShowEvent*) { timer_.start(timeout(), this); } void Analyzer::Base::transform(Scope& scope) { - // This is a standard transformation that should give an FFT scope that has bands for pretty analyzers + QVector aux(fht_->size()); + if (aux.size() >= scope.size()) { + std::copy(scope.begin(), scope.end(), aux.begin()); + } + else { + std::copy(scope.begin(), scope.begin() + aux.size(), aux.begin()); + } - // NOTE: Resizing here is redundant as FHT routines only calculate FHT::size() values scope.resize( fht_->size() ); - - float *front = static_cast(&scope.front()); - - float *f = new float[fht_->size()]; - fht_->copy(&f[0], front); - fht_->logSpectrum(front, &f[0]); - fht_->scale(front, 1.0 / 20); + fht_->logSpectrum(scope.data(), aux.data()); + fht_->scale(scope.data(), 1.0 / 20); scope.resize(fht_->size() / 2); // second half of values are rubbish - delete[] f; } diff --git a/src/analyzer/analyzerbase.h b/src/analyzer/analyzerbase.h index 2ec8354d..910dff5c 100644 --- a/src/analyzer/analyzerbase.h +++ b/src/analyzer/analyzerbase.h @@ -1,5 +1,24 @@ -// Maintainer: Max Howell , (C) 2004 -// Copyright: See COPYING file that comes with this distribution +/* + Strawberry Music Player + This file was part of Amarok. + Copyright 2003-2004, Max Howell + Copyright 2009-2012, David Sansome + Copyright 2010, 2012, 2014, John Maguire + Copyright 2017, Santiago Gil + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ #ifndef ANALYZERBASE_H #define ANALYZERBASE_H @@ -7,7 +26,7 @@ #include "config.h" #ifdef __FreeBSD__ -#include +# include #endif #include @@ -23,6 +42,7 @@ #include "analyzer/fht.h" #include "engine/engine_fwd.h" +#include "engine/enginebase.h" class QHideEvent; class QShowEvent; @@ -54,7 +74,7 @@ class Base : public QWidget { virtual void framerateChanged() {} protected: - Base(QWidget*, uint scopeSize = 7); + explicit Base(QWidget*, uint scopeSize = 7); void hideEvent(QHideEvent*); void showEvent(QShowEvent*); @@ -76,7 +96,7 @@ class Base : public QWidget { FHT *fht_; EngineBase *engine_; Scope lastscope_; - int current_chunk_; + bool new_frame_; bool is_playing_; }; @@ -84,6 +104,7 @@ class Base : public QWidget { void interpolate(const Scope&, Scope&); void initSin(Scope&, const uint = 6000); -} // END namespace Analyzer +} // namespace Analyzer + +#endif // ANALYZERBASE_H -#endif diff --git a/src/analyzer/analyzercontainer.cpp b/src/analyzer/analyzercontainer.cpp index 6397f76a..5beb2ded 100644 --- a/src/analyzer/analyzercontainer.cpp +++ b/src/analyzer/analyzercontainer.cpp @@ -38,6 +38,8 @@ #include "analyzerbase.h" #include "blockanalyzer.h" +#include "boomanalyzer.h" +#include "rainbowanalyzer.h" #include "core/logging.h" @@ -64,6 +66,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent) ignore_next_click_(false), current_analyzer_(nullptr), engine_(nullptr) { + QHBoxLayout *layout = new QHBoxLayout(this); setLayout(layout); layout->setContentsMargins(0, 0, 0, 0); @@ -79,6 +82,9 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent) context_menu_->addSeparator(); AddAnalyzerType(); + AddAnalyzerType(); + AddAnalyzerType(); + AddAnalyzerType(); connect(mapper_, SIGNAL(mapped(int)), SLOT(ChangeAnalyzer(int))); disable_action_ = context_menu_->addAction(tr("No analyzer"), this, SLOT(DisableAnalyzer())); @@ -93,6 +99,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent) connect(double_click_timer_, SIGNAL(timeout()), SLOT(ShowPopupMenu())); Load(); + } void AnalyzerContainer::SetActions(QAction *visualisation) { @@ -101,6 +108,7 @@ void AnalyzerContainer::SetActions(QAction *visualisation) { } void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { if (ignore_next_click_) { ignore_next_click_ = false; @@ -114,6 +122,7 @@ void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) { else if (e->button() == Qt::RightButton) { context_menu_->popup(e->globalPos()); } + } void AnalyzerContainer::ShowPopupMenu() { @@ -144,6 +153,7 @@ void AnalyzerContainer::DisableAnalyzer() { } void AnalyzerContainer::ChangeAnalyzer(int id) { + QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this)); if (!instance) { @@ -161,9 +171,11 @@ void AnalyzerContainer::ChangeAnalyzer(int id) { layout()->addWidget(current_analyzer_); Save(); + } void AnalyzerContainer::ChangeFramerate(int new_framerate) { + if (current_analyzer_) { // Even if it is not supposed to happen, I don't want to get a dbz error new_framerate = new_framerate == 0 ? kMediumFramerate : new_framerate; @@ -173,14 +185,18 @@ void AnalyzerContainer::ChangeFramerate(int new_framerate) { current_analyzer_->framerateChanged(); } SaveFramerate(new_framerate); + } void AnalyzerContainer::Load() { + QSettings s; s.beginGroup(kSettingsGroup); + QString type = s.value("type", "BlockAnalyzer").toString(); + current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt(); + s.endGroup(); // Analyzer - QString type = s.value("type", "BlockAnalyzer").toString(); if (type.isEmpty()) { DisableAnalyzer(); disable_action_->setChecked(true); @@ -196,7 +212,6 @@ void AnalyzerContainer::Load() { } // Framerate - current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt(); for (int i = 0; i < framerate_list_.count(); ++i) { if (current_framerate_ == framerate_list_[i]) { ChangeFramerate(current_framerate_); @@ -204,27 +219,35 @@ void AnalyzerContainer::Load() { break; } } + } void AnalyzerContainer::SaveFramerate(int framerate) { + // For now, framerate is common for all analyzers. Maybe each analyzer should have its own framerate? current_framerate_ = framerate; QSettings s; s.beginGroup(kSettingsGroup); s.setValue(kSettingsFramerate, current_framerate_); + s.endGroup(); + } void AnalyzerContainer::Save() { + QSettings s; s.beginGroup(kSettingsGroup); - s.setValue("type", current_analyzer_ ? current_analyzer_->metaObject()->className() : QVariant()); + s.endGroup(); + } void AnalyzerContainer::AddFramerate(const QString& name, int framerate) { + QAction *action = context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map())); mapper_framerate_->setMapping(action, framerate); group_framerate_->addAction(action); framerate_list_ << framerate; action->setCheckable(true); + } diff --git a/src/analyzer/analyzercontainer.h b/src/analyzer/analyzercontainer.h index 597d534f..17872ddc 100644 --- a/src/analyzer/analyzercontainer.h +++ b/src/analyzer/analyzercontainer.h @@ -26,12 +26,12 @@ #include #include +#include +#include #include #include #include #include -#include -#include #include #include #include @@ -120,5 +120,4 @@ void AnalyzerContainer::AddAnalyzerType() { actions_ << action; } -#endif - +#endif // ANALYZERCONTAINER_H diff --git a/src/analyzer/blockanalyzer.cpp b/src/analyzer/blockanalyzer.cpp index 32b308b1..397892b1 100644 --- a/src/analyzer/blockanalyzer.cpp +++ b/src/analyzer/blockanalyzer.cpp @@ -1,13 +1,30 @@ -// Author: Max Howell , (C) 2003-5 -// Mark Kretschmann , (C) 2005 -// Copyright: See COPYING file that comes with this distribution -// +/* + Strawberry Music Player + This file was part of Amarok. + Copyright 2003-2005, Max Howell + Copyright 2005, Mark Kretschmann + Copyright 2009-2010, David Sansome + Copyright 2010, 2014, John Maguire + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . + +*/ #include "blockanalyzer.h" #include #include -#include #include #include @@ -19,12 +36,12 @@ #include "analyzerbase.h" #include "fht.h" -const uint BlockAnalyzer::HEIGHT = 2; -const uint BlockAnalyzer::WIDTH = 4; -const uint BlockAnalyzer::MIN_ROWS = 3; // arbituary -const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary -const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n -const uint BlockAnalyzer::FADE_SIZE = 90; +const uint BlockAnalyzer::kHeight = 2; +const uint BlockAnalyzer::kWidth = 4; +const uint BlockAnalyzer::kMinRows = 3; // arbituary +const uint BlockAnalyzer::kMinColumns = 32; // arbituary +const uint BlockAnalyzer::kMaxColumns = 256; // must be 2**n +const uint BlockAnalyzer::kFadeSize = 90; const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer"); @@ -34,18 +51,18 @@ BlockAnalyzer::BlockAnalyzer(QWidget *parent) rows_(0), y_(0), barpixmap_(1, 1), - topbarpixmap_(WIDTH, HEIGHT), - scope_(MIN_COLUMNS), + topbarpixmap_(kWidth, kHeight), + scope_(kMinColumns), store_(1 << 8, 0), - fade_bars_(FADE_SIZE), + fade_bars_(kFadeSize), fade_pos_(1 << 8, 50), fade_intensity_(1 << 8, 32) { - setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1, MIN_ROWS * (HEIGHT + 1) - 1); //-1 is padding, no drawing takes place there - setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1); + setMinimumSize(kMinColumns * (kWidth + 1) - 1, kMinRows * (kHeight + 1) - 1); //-1 is padding, no drawing takes place there + setMaximumWidth(kMaxColumns * (kWidth + 1) - 1); // mxcl says null pixmaps cause crashes, so let's play it safe - for (uint i = 0; i < FADE_SIZE; ++i) fade_bars_[i] = QPixmap(1, 1); + for (uint i = 0; i < kFadeSize; ++i) fade_bars_[i] = QPixmap(1, 1); } @@ -61,20 +78,20 @@ void BlockAnalyzer::resizeEvent(QResizeEvent *e) { const uint oldRows = rows_; // all is explained in analyze().. - //+1 to counter -1 in maxSizes, trust me we need this! - columns_ = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS); - rows_ = uint(double(height() + 1) / (HEIGHT + 1)); + // +1 to counter -1 in maxSizes, trust me we need this! + columns_ = qMin(static_cast(static_cast(width() + 1) / (kWidth + 1)) + 1, kMaxColumns); + rows_ = static_cast(static_cast(height() + 1) / (kHeight + 1)); // this is the y-offset for drawing from the top of the widget - y_ = (height() - (rows_ * (HEIGHT + 1)) + 2) / 2; + y_ = (height() - (rows_ * (kHeight + 1)) + 2) / 2; scope_.resize(columns_); if (rows_ != oldRows) { - barpixmap_ = QPixmap(WIDTH, rows_ * (HEIGHT + 1)); + barpixmap_ = QPixmap(kWidth, rows_ * (kHeight + 1)); - for (uint i = 0; i < FADE_SIZE; ++i) - fade_bars_[i] = QPixmap(WIDTH, rows_ * (HEIGHT + 1)); + for (uint i = 0; i < kFadeSize; ++i) + fade_bars_[i] = QPixmap(kWidth, rows_ * (kHeight + 1)); yscale_.resize(rows_ + 1); @@ -113,13 +130,11 @@ void BlockAnalyzer::transform(Analyzer::Scope &s) { for (uint x = 0; x < s.size(); ++x) s[x] *= 2; - float* front = static_cast(&s.front()); - - fht_->spectrum(front); - fht_->scale(front, 1.0 / 20); + fht_->spectrum(s.data()); + fht_->scale(s.data(), 1.0 / 20); // the second half is pretty dull, so only show it if the user has a large analyzer by setting to scope_.size() if large we prevent interpolation of large analyzers, this is good! - s.resize(scope_.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : scope_.size()); + s.resize(scope_.size() <= kMaxColumns / 2 ? kMaxColumns / 2 : scope_.size()); } @@ -154,35 +169,33 @@ void BlockAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_fram // determine y for (y = 0; scope_[x] < yscale_[y]; ++y) continue; - // this is opposite to what you'd think, higher than y means the bar is lower than y (physically) - if ((float)y > store_[x]) - y = int(store_[x] += step_); + // This is opposite to what you'd think, higher than y means the bar is lower than y (physically) + if (static_cast(y) > store_[x]) + y = static_cast(store_[x] += step_); else store_[x] = y; - // if y is lower than fade_pos_, then the bar has exceeded the height of the fadeout + // If y is lower than fade_pos_, then the bar has exceeded the height of the fadeout // if the fadeout is quite faded now, then display the new one - if (y <= fade_pos_[x] /*|| fade_intensity_[x] < FADE_SIZE / 3*/) { + if (y <= fade_pos_[x] /*|| fade_intensity_[x] < kFadeSize / 3*/) { fade_pos_[x] = y; - fade_intensity_[x] = FADE_SIZE; + fade_intensity_[x] = kFadeSize; } if (fade_intensity_[x] > 0) { const uint offset = --fade_intensity_[x]; - const uint y = y_ + (fade_pos_[x] * (HEIGHT + 1)); - canvas_painter.drawPixmap(x * (WIDTH + 1), y, fade_bars_[offset], 0, 0, WIDTH, height() - y); + const uint y = y_ + (fade_pos_[x] * (kHeight + 1)); + canvas_painter.drawPixmap(x * (kWidth + 1), y, fade_bars_[offset], 0, 0, kWidth, height() - y); } if (fade_intensity_[x] == 0) fade_pos_[x] = rows_; // REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are - canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + y_, *bar(), - 0, y * (HEIGHT + 1), bar()->width(), - bar()->height()); + canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(), 0, y * (kHeight + 1), bar()->width(), bar()->height()); } for (uint x = 0; x < store_.size(); ++x) - canvas_painter.drawPixmap(x * (WIDTH + 1), int(store_[x]) * (HEIGHT + 1) + y_, topbarpixmap_); + canvas_painter.drawPixmap(x * (kWidth + 1), static_cast(store_[x]) * (kHeight + 1) + y_, topbarpixmap_); p.drawPixmap(0, 0, canvas_); @@ -223,11 +236,11 @@ static inline void adjustToLimits(int &b, int &f, uint &amount) { * It won't modify the hue of fg unless absolutely necessary * @return the adjusted form of fg */ -QColor ensureContrast(const QColor &bg, const QColor &fg, uint _amount = 150) { +QColor ensureContrast(const QColor &bg, const QColor &fg, uint amount = 150) { class OutputOnExit { public: - OutputOnExit(const QColor &color) : c(color) {} + explicit OutputOnExit(const QColor &color) : c(color) {} ~OutputOnExit() { int h, s, v; c.getHsv(&h, &s, &v); @@ -237,14 +250,6 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, uint _amount = 150) { const QColor &c; }; -// hack so I don't have to cast everywhere -#define amount static_cast(_amount) - // #define STAMP debug() << (QValueList() << fh << fs << fv) << endl; - // #define STAMP1( string ) debug() << string << ": " << - // (QValueList() << fh << fs << fv) << endl; - // #define STAMP2( string, value ) debug() << string << "=" << value << ": - // " << (QValueList() << fh << fs << fv) << endl; - OutputOnExit allocateOnTheStack(fg); int bh, bs, bv; @@ -255,23 +260,17 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, uint _amount = 150) { int dv = abs(bv - fv); - // STAMP2( "DV", dv ); - // value is the best measure of contrast // if there is enough difference in value already, return fg unchanged - if (dv > amount) return fg; + if (dv > static_cast(amount)) return fg; int ds = abs(bs - fs); - // STAMP2( "DS", ds ); - // saturation is good enough too. But not as good. TODO adapt this a little - if (ds > amount) return fg; + if (ds > static_cast(amount)) return fg; int dh = abs(bh - fh); - // STAMP2( "DH", dh ); - if (dh > 120) { // a third of the colour wheel automatically guarentees contrast // but only if the values are high enough and saturations significant enough @@ -279,79 +278,49 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, uint _amount = 150) { // check the saturation for the two colours is sufficient that hue alone can // provide sufficient contrast - if (ds > amount / 2 && (bs > 125 && fs > 125)) - // STAMP1( "Sufficient saturation difference, and hues are - // compliemtary" ); + if (ds > static_cast(amount) / 2 && (bs > 125 && fs > 125)) return fg; - else if (dv > amount / 2 && (bv > 125 && fv > 125)) - // STAMP1( "Sufficient value difference, and hues are - // compliemtary" ); + else if (dv > static_cast(amount) / 2 && (bv > 125 && fv > 125)) return fg; - - // STAMP1( "Hues are complimentary but we must modify the value or - // saturation of the contrasting colour" ); - - // but either the colours are two desaturated, or too dark - // so we need to adjust the system, although not as much - ///_amount /= 2; } if (fs < 50 && ds < 40) { // low saturation on a low saturation is sad const int tmp = 50 - fs; fs = 50; - if (amount > tmp) - _amount -= tmp; + if (static_cast(amount) > tmp) + amount -= tmp; else - _amount = 0; + amount = 0; } // test that there is available value to honor our contrast requirement - if (255 - dv < amount) { + if (255 - dv < static_cast(amount)) { // we have to modify the value and saturation of fg // adjustToLimits( bv, fv, amount ); - - // STAMP - // see if we need to adjust the saturation - if (amount > 0) adjustToLimits(bs, fs, _amount); - - // STAMP + if (static_cast(amount) > 0) adjustToLimits(bs, fs, amount); // see if we need to adjust the hue - if (amount > 0) fh += amount; // cycles around; - - // STAMP + if (static_cast(amount) > 0) + fh += static_cast(amount); // cycles around; return QColor::fromHsv(fh, fs, fv); } - // STAMP + if (fv > bv && bv > static_cast(amount)) + return QColor::fromHsv(fh, fs, bv - static_cast(amount)); - if (fv > bv && bv > amount) return QColor::fromHsv(fh, fs, bv - amount); + if (fv < bv && fv > static_cast(amount)) + return QColor::fromHsv(fh, fs, fv - static_cast(amount)); - // STAMP + if (fv > bv && (255 - fv > static_cast(amount))) + return QColor::fromHsv(fh, fs, fv + static_cast(amount)); - if (fv < bv && fv > amount) return QColor::fromHsv(fh, fs, fv - amount); - - // STAMP - - if (fv > bv && (255 - fv > amount)) - return QColor::fromHsv(fh, fs, fv + amount); - - // STAMP - - if (fv < bv && (255 - bv > amount)) - return QColor::fromHsv(fh, fs, bv + amount); - - // STAMP - // debug() << "Something went wrong!\n"; + if (fv < bv && (255 - bv > static_cast(amount))) + return QColor::fromHsv(fh, fs, bv + static_cast(amount)); return Qt::blue; - -#undef amount - // #undef STAMP - } void BlockAnalyzer::paletteChange(const QPalette&) { @@ -361,17 +330,17 @@ void BlockAnalyzer::paletteChange(const QPalette&) { topbarpixmap_.fill(fg); - const double dr = 15 * double(bg.red() - fg.red()) / (rows_ * 16); - const double dg = 15 * double(bg.green() - fg.green()) / (rows_ * 16); - const double db = 15 * double(bg.blue() - fg.blue()) / (rows_ * 16); + const double dr = 15 * static_cast(bg.red() - fg.red()) / (rows_ * 16); + const double dg = 15 * static_cast(bg.green() - fg.green()) / (rows_ * 16); + const double db = 15 * static_cast(bg.blue() - fg.blue()) / (rows_ * 16); const int r = fg.red(), g = fg.green(), b = fg.blue(); bar()->fill(bg); QPainter p(bar()); - for (int y = 0; (uint)y < rows_; ++y) + for (int y = 0; static_cast(y) < rows_; ++y) // graduate the fg color - p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * y), g + int(dg * y), b + int(db * y))); + p.fillRect(0, y * (kHeight + 1), kWidth, kHeight, QColor(r + static_cast(dr * y), g + static_cast(dg * y), b + static_cast(db * y))); { const QColor bg = palette().color(QPalette::Background).dark(112); @@ -388,12 +357,12 @@ void BlockAnalyzer::paletteChange(const QPalette&) { const int r = bg.red(), g = bg.green(), b = bg.blue(); // Precalculate all fade-bar pixmaps - for (uint y = 0; y < FADE_SIZE; ++y) { + for (uint y = 0; y < kFadeSize; ++y) { fade_bars_[y].fill(palette().color(QPalette::Background)); QPainter f(&fade_bars_[y]); - for (int z = 0; (uint)z < rows_; ++z) { - const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE)); - f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * Y), g + int(dg * Y), b + int(db * Y))); + for (int z = 0; static_cast(z) < rows_; ++z) { + const double Y = 1.0 - (log10(kFadeSize - y) / log10(kFadeSize)); + f.fillRect(0, z * (kHeight + 1), kWidth, kHeight, QColor(r + static_cast(dr * Y), g + static_cast(dg * Y), b + static_cast(db * Y))); } } } @@ -404,14 +373,21 @@ void BlockAnalyzer::paletteChange(const QPalette&) { void BlockAnalyzer::drawBackground() { + if (background_.isNull()) { + return; + } + const QColor bg = palette().color(QPalette::Background); const QColor bgdark = bg.dark(112); background_.fill(bg); QPainter p(&background_); + + if (p.paintEngine() == 0) return; + for (int x = 0; (uint)x < columns_; ++x) for (int y = 0; (uint)y < rows_; ++y) - p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + y_, WIDTH, HEIGHT, bgdark); + p.fillRect(x * (kWidth + 1), y * (kHeight + 1) + y_, kWidth, kHeight, bgdark); } diff --git a/src/analyzer/blockanalyzer.h b/src/analyzer/blockanalyzer.h index be4dfe33..7d65ed63 100644 --- a/src/analyzer/blockanalyzer.h +++ b/src/analyzer/blockanalyzer.h @@ -1,6 +1,25 @@ -// Maintainer: Max Howell , (C) 2003-5 -// Copyright: See COPYING file that comes with this distribution -// +/* + Strawberry Music Player + This file was part of Amarok. + Copyright 2003-2005, Max Howell + Copyright 2005, Mark Kretschmann + Copyright 2009-2010, David Sansome + Copyright 2010, 2014, John Maguire + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . + +*/ #ifndef BLOCKANALYZER_H #define BLOCKANALYZER_H @@ -11,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -21,22 +41,18 @@ class QResizeEvent; -/** - * @author Max Howell - */ - class BlockAnalyzer : public Analyzer::Base { Q_OBJECT public: Q_INVOKABLE BlockAnalyzer(QWidget*); ~BlockAnalyzer(); - static const uint HEIGHT; - static const uint WIDTH; - static const uint MIN_ROWS; - static const uint MIN_COLUMNS; - static const uint MAX_COLUMNS; - static const uint FADE_SIZE; + static const uint kHeight; + static const uint kWidth; + static const uint kMinRows; + static const uint kMinColumns; + static const uint kMaxColumns; + static const uint kFadeSize; static const char *kName; @@ -53,22 +69,21 @@ class BlockAnalyzer : public Analyzer::Base { private: QPixmap *bar() { return &barpixmap_; } - uint columns_, rows_; // number of rows and columns of blocks - uint y_; // y-offset from top of widget + uint columns_, rows_; // number of rows and columns of blocks + uint y_; // y-offset from top of widget QPixmap barpixmap_; QPixmap topbarpixmap_; QPixmap background_; QPixmap canvas_; - Analyzer::Scope scope_; // so we don't create a vector every frame - std::vector store_; // current bar heights - std::vector yscale_; + Analyzer::Scope scope_; // so we don't create a vector every frame + QVector store_; // current bar heights + QVector yscale_; - // FIXME why can't I namespace these? c++ issue? - std::vector fade_bars_; - std::vector fade_pos_; - std::vector fade_intensity_; + QVector fade_bars_; + QVector fade_pos_; + QVector fade_intensity_; float step_; // rows to fall per frame }; -#endif +#endif // BLOCKANALYZER_H diff --git a/src/analyzer/boomanalyzer.cpp b/src/analyzer/boomanalyzer.cpp new file mode 100644 index 00000000..aaabac4e --- /dev/null +++ b/src/analyzer/boomanalyzer.cpp @@ -0,0 +1,169 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2004, Max Howell + Copyright 2009-2010, David Sansome + Copyright 2010, 2014, John Maguire + Copyright 2014-2015, Mark Furneaux + Copyright 2014, Krzysztof Sobiecki + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . + +*/ + +#include "boomanalyzer.h" + +#include + +#include +#include +#include +#include +#include + +using Analyzer::Scope; + +const uint BoomAnalyzer::kColumnWidth = 4; +const uint BoomAnalyzer::kMaxBandCount = 256; +const uint BoomAnalyzer::kMinBandCount = 32; + +const char* BoomAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer"); + +BoomAnalyzer::BoomAnalyzer(QWidget* parent) + : Analyzer::Base(parent, 9), + bands_(0), + scope_(kMinBandCount), + fg_(palette().color(QPalette::Highlight)), + K_barHeight_(1.271) // 1.471 + , + F_peakSpeed_(1.103) // 1.122 + , + F_(1.0), + bar_height_(kMaxBandCount, 0), + peak_height_(kMaxBandCount, 0), + peak_speed_(kMaxBandCount, 0.01), + barPixmap_(kColumnWidth, 50) { + + setMinimumWidth(kMinBandCount * (kColumnWidth + 1) - 1); + setMaximumWidth(kMaxBandCount * (kColumnWidth + 1) - 1); + +} + +void BoomAnalyzer::changeK_barHeight(int newValue) { + K_barHeight_ = static_cast(newValue) / 1000; +} + +void BoomAnalyzer::changeF_peakSpeed(int newValue) { + F_peakSpeed_ = static_cast(newValue) / 1000; +} + +void BoomAnalyzer::resizeEvent(QResizeEvent* e) { + + QWidget::resizeEvent(e); + + const uint HEIGHT = height() - 2; + const double h = 1.2 / HEIGHT; + + bands_ = qMin(static_cast(static_cast(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount); + scope_.resize(bands_); + + F_ = static_cast(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/); + + barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT); + canvas_ = QPixmap(size()); + canvas_.fill(palette().color(QPalette::Background)); + + QPainter p(&barPixmap_); + for (uint y = 0; y < HEIGHT; ++y) { + const double F = static_cast(y) * h; + + p.setPen(QColor(qMax(0, 255 - static_cast(229.0 * F)), + qMax(0, 255 - static_cast(229.0 * F)), + qMax(0, 255 - static_cast(191.0 * F)))); + p.drawLine(0, y, kColumnWidth - 2, y); + } + +} + +void BoomAnalyzer::transform(Scope& s) { + + fht_->spectrum(s.data()); + fht_->scale(s.data(), 1.0 / 50); + + s.resize(scope_.size() <= kMaxBandCount / 2 ? kMaxBandCount / 2 : scope_.size()); + +} + +void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) { + + if (!new_frame || engine_->state() == Engine::Paused) { + p.drawPixmap(0, 0, canvas_); + return; + } + float h; + const uint MAX_HEIGHT = height() - 1; + + QPainter canvas_painter(&canvas_); + canvas_.fill(palette().color(QPalette::Background)); + + Analyzer::interpolate(scope, scope_); + + 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 > bar_height_[i]) { + bar_height_[i] = h; + + if (h > peak_height_[i]) { + peak_height_[i] = h; + peak_speed_[i] = 0.01; + } + else { + goto peak_handling; + } + } + else { + if (bar_height_[i] > 0.0) { + bar_height_[i] -= K_barHeight_; // 1.4 + if (bar_height_[i] < 0.0) bar_height_[i] = 0.0; + } + + peak_handling: + + if (peak_height_[i] > 0.0) { + peak_height_[i] -= peak_speed_[i]; + peak_speed_[i] *= F_peakSpeed_; // 1.12 + + if (peak_height_[i] < bar_height_[i]) peak_height_[i] = bar_height_[i]; + if (peak_height_[i] < 0.0) peak_height_[i] = 0.0; + } + } + + y = height() - uint(bar_height_[i]); + canvas_painter.drawPixmap(x + 1, y, barPixmap_, 0, y, -1, -1); + canvas_painter.setPen(fg_); + if (bar_height_[i] > 0) + canvas_painter.drawRect(x, y, kColumnWidth - 1, height() - y - 1); + + y = height() - uint(peak_height_[i]); + canvas_painter.setPen(palette().color(QPalette::Midlight)); + canvas_painter.drawLine(x, y, x + kColumnWidth - 1, y); + } + + p.drawPixmap(0, 0, canvas_); + +} + diff --git a/src/analyzer/boomanalyzer.h b/src/analyzer/boomanalyzer.h new file mode 100644 index 00000000..fe2cc1a0 --- /dev/null +++ b/src/analyzer/boomanalyzer.h @@ -0,0 +1,75 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2004, Max Howell + Copyright 2009-2010, David Sansome + Copyright 2010, 2014, John Maguire + Copyright 2014-2015, Mark Furneaux + Copyright 2014, Krzysztof Sobiecki + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . + +*/ + +#ifndef BOOMANALYZER_H +#define BOOMANALYZER_H + +#include "analyzerbase.h" + +#include +#include +#include +#include +#include + +class QResizeEvent; + +class BoomAnalyzer : public Analyzer::Base { + Q_OBJECT + + public: + Q_INVOKABLE BoomAnalyzer(QWidget*); + + static const char* kName; + + virtual void transform(Analyzer::Scope& s); + virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame); + + public slots: + void changeK_barHeight(int); + void changeF_peakSpeed(int); + + protected: + void resizeEvent(QResizeEvent* e); + + static const uint kColumnWidth; + static const uint kMaxBandCount; + static const uint kMinBandCount; + + uint bands_; + Analyzer::Scope scope_; + QColor fg_; + + double K_barHeight_, F_peakSpeed_, F_; + + std::vector bar_height_; + std::vector peak_height_; + std::vector peak_speed_; + + QPixmap barPixmap_; + QPixmap canvas_; + +}; + +#endif // BOOMANALYZER_H diff --git a/src/analyzer/fht.cpp b/src/analyzer/fht.cpp index 50206961..2c6a04e6 100644 --- a/src/analyzer/fht.cpp +++ b/src/analyzer/fht.cpp @@ -1,140 +1,140 @@ -// FHT - Fast Hartley Transform Class -// -// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA -// -// $Id$ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2004, Melchior FRANZ + Copyright 2010, 2014, John Maguire + Copyright 2014, Krzysztof Sobiecki + Copyright 2017, Santiago Gil -#include -#include + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ #include "fht.h" -FHT::FHT(int n) : m_buf(0), m_tab(0), m_log(0) { - if (n < 3) { - m_num = 0; - m_exp2 = -1; - return; - } - m_exp2 = n; - m_num = 1 << n; +#include +#include + +#include + +FHT::FHT(int n) : num_((n < 3) ? 0 : 1 << n), exp2_((n < 3) ? -1 : n) { if (n > 3) { - m_buf = new float[m_num]; - m_tab = new float[m_num * 2]; + buf_vector_.resize(num_); + tab_vector_.resize(num_ * 2); makeCasTable(); } } -FHT::~FHT() { - delete[] m_buf; - delete[] m_tab; - delete[] m_log; -} +FHT::~FHT() {} + +int FHT::sizeExp() const { return exp2_; } +int FHT::size() const { return num_; } + +float* FHT::buf_() { return buf_vector_.data(); } +float* FHT::tab_() { return tab_vector_.data(); } +int* FHT::log_() { return log_vector_.data(); } void FHT::makeCasTable(void) { - float d, *costab, *sintab; - int ul, ndiv2 = m_num / 2; + float* costab = tab_(); + float* sintab = tab_() + num_ / 2 + 1; - for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) { - d = M_PI * ul / ndiv2; + for (int ul = 0; ul < num_; ul++) { + float d = M_PI * ul / (num_ / 2); *costab = *sintab = cos(d); - costab += 2, sintab += 2; - if (sintab > m_tab + m_num * 2) sintab = m_tab + 1; + costab += 2; + sintab += 2; + if (sintab > tab_() + num_ * 2) sintab = tab_() + 1; } } -float* FHT::copy(float* d, float* s) { - return (float*)memcpy(d, s, m_num * sizeof(float)); -} - -float* FHT::clear(float* d) { - return (float*)memset(d, 0, m_num * sizeof(float)); -} - 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) { - 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) { - int n = m_num / 2, i, j, k, *r; - if (!m_log) { - m_log = new int[n]; - float f = n / log10((double)n); - for (i = 0, r = m_log; i < n; i++, r++) { - j = int(rint(log10(i + 1.0) * f)); + + int n = num_ / 2, i, j, k, *r; + if (log_vector_.size() < n) { + log_vector_.resize(n); + float f = n / log10(static_cast(n)); + for (i = 0, r = log_(); i < n; i++, r++) { + j = static_cast(rint(log10(i + 1.0) * f)); *r = j >= n ? n - 1 : j; } } semiLogSpectrum(p); *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++; - if (i == j) + if (i == j) { *out++ = p[i]; + } else { float base = p[k - 1]; float step = (p[j] - base) / (j - (k - 1)); for (float corr = 0; k <= j; k++, corr += step) *out++ = base + corr; } } + } void FHT::semiLogSpectrum(float* p) { - float e; power2(p); - for (int i = 0; i < (m_num / 2); i++, p++) { - e = 10.0 * log10(sqrt(*p * .5)); + for (int i = 0; i < (num_ / 2); i++, p++) { + float e = 10.0 * log10(sqrt(*p / 2)); *p = e < 0 ? 0 : e; } } void FHT::spectrum(float* p) { power2(p); - for (int i = 0; i < (m_num / 2); i++, p++) *p = (float)sqrt(*p * .5); + for (int i = 0; i < (num_ / 2); i++, p++) + *p = static_cast(sqrt(*p / 2)); } void FHT::power(float* p) { power2(p); - for (int i = 0; i < (m_num / 2); i++) *p++ *= .5; + for (int i = 0; i < (num_ / 2); i++) *p++ /= 2; } void FHT::power2(float* p) { - int i; - float* q; - _transform(p, m_num, 0); + _transform(p, num_, 0); - *p = (*p * *p), *p += *p, p++; + *p = static_cast(2 * pow(*p, 2)); + p++; - for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q) - *p = (*p * *p) + (*q * *q), p++; + float* q = p + num_ - 2; + for (int i = 1; i < (num_ / 2); i++) { + *p = static_cast(pow(*p, 2) + pow(*q, 2)); + p++; + q--; + } } void FHT::transform(float* p) { - if (m_num == 8) + if (num_ == 8) transform8(p); else - _transform(p, m_num, 0); + _transform(p, num_, 0); } void FHT::transform8(float* p) { + float a, b, c, d, e, f, g, h, b_f2, d_h2; float a_c_eg, a_ce_g, ac_e_g, aceg, b_df_h, bdfh; @@ -159,6 +159,7 @@ void FHT::transform8(float* p) { *--p = a_ce_g + b_df_h; *--p = ac_e_g + b_f2; *--p = aceg + bdfh; + } void FHT::_transform(float* p, int n, int k) { @@ -171,19 +172,19 @@ void FHT::_transform(float* p, int n, int k) { int i, j, ndiv2 = n / 2; 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++; - memcpy(p + k, m_buf, sizeof(float) * n); + std::copy(buf_(), buf_() + n, p + k); _transform(p, ndiv2, k); _transform(p, ndiv2, k + ndiv2); - j = m_num / ndiv2 - 1; - t1 = m_buf; + j = num_ / ndiv2 - 1; + t1 = buf_(); t2 = t1 + ndiv2; t3 = p + k + ndiv2; - ptab = m_tab; + ptab = tab_(); pp = p + k; a = *ptab++ * *t3++; @@ -200,5 +201,7 @@ void FHT::_transform(float* p, int n, int k) { *t1++ = *pp + a; *t2++ = *pp++ - a; } - memcpy(p + k, m_buf, sizeof(float) * n); + + std::copy(buf_(), buf_() + n, p + k); + } diff --git a/src/analyzer/fht.h b/src/analyzer/fht.h index 73c5daa6..5b79679b 100644 --- a/src/analyzer/fht.h +++ b/src/analyzer/fht.h @@ -1,26 +1,30 @@ -// FHT - Fast Hartley Transform Class -// -// Copyright (C) 2004 Melchior FRANZ - mfranz@kde.org -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA -// -// $Id$ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2004, Melchior FRANZ + Copyright 2010, 2014, John Maguire + Copyright 2014, Krzysztof Sobiecki + Copyright 2017, Santiago Gil + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ #ifndef FHT_H #define FHT_H +#include + /** * Implementation of the Hartley Transform after Bracewell's discrete * algorithm. The algorithm is subject to US patent No. 4,646,256 (1987) @@ -30,11 +34,16 @@ * [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379 */ class FHT { - int m_exp2; - int m_num; - float* m_buf; - float* m_tab; - int* m_log; + const int num_; + const int exp2_; + + QVector buf_vector_; + QVector tab_vector_; + QVector log_vector_; + + float* buf_(); + float* tab_(); + int* log_(); /** * Create a table of "cas" (cosine and sine) values. @@ -57,10 +66,8 @@ class FHT { FHT(int); ~FHT(); - inline int sizeExp() const { return m_exp2; } - inline int size() const { return m_num; } - float* copy(float*, float*); - float* clear(float*); + int sizeExp() const; + int size() const; void scale(float*, float); /** @@ -115,4 +122,4 @@ class FHT { void transform(float*); }; -#endif +#endif // FHT_H diff --git a/src/analyzer/rainbowanalyzer.cpp b/src/analyzer/rainbowanalyzer.cpp new file mode 100644 index 00000000..740a87ee --- /dev/null +++ b/src/analyzer/rainbowanalyzer.cpp @@ -0,0 +1,207 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2011, Tyler Rhodes + Copyright 2011-2012, 2014, David Sansome + Copyright 2011, 2014, John Maguire + Copyright 2014, Alibek Omarov + Copyright 2014, Krzysztof Sobiecki + Copyright 2014-2015, Mark Furneaux + Copyright 2015, Arun Narayanankutty + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#include "rainbowanalyzer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/arraysize.h" +#include "core/logging.h" + +using Analyzer::Scope; + +const int Rainbow::RainbowAnalyzer::kHeight[] = {21, 33}; +const int Rainbow::RainbowAnalyzer::kWidth[] = {34, 53}; +const int Rainbow::RainbowAnalyzer::kFrameCount[] = {6, 16}; +const int Rainbow::RainbowAnalyzer::kRainbowHeight[] = {21, 16}; +const int Rainbow::RainbowAnalyzer::kRainbowOverlap[] = {13, 15}; +const int Rainbow::RainbowAnalyzer::kSleepingHeight[] = {24, 33}; + +const char* Rainbow::NyanCatAnalyzer::kName = "Nyanalyzer Cat"; +const char* Rainbow::RainbowDashAnalyzer::kName = "Rainbow Dash"; +const float Rainbow::RainbowAnalyzer::kPixelScale = 0.02f; + +Rainbow::RainbowAnalyzer::RainbowType Rainbow::RainbowAnalyzer::rainbowtype; + +Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType& rbtype, QWidget* parent) + : Analyzer::Base(parent, 9), + timer_id_(startTimer(kFrameIntervalMs)), + frame_(0), + current_buffer_(0), + available_rainbow_width_(0), + px_per_frame_(0), + x_offset_(0), + background_brush_(QColor(0x0f, 0x43, 0x73)) + { + + rainbowtype = rbtype; + cat_dash_[0] = QPixmap(":/pictures/nyancat.png"); + cat_dash_[1] = QPixmap(":/pictures/rainbowdash.png"); + memset(history_, 0, sizeof(history_)); + + for (int i = 0; i < kRainbowBands; ++i) { + colors_[i] = QPen(QColor::fromHsv(i * 255 / kRainbowBands, 255, 255), kRainbowHeight[rainbowtype] / kRainbowBands, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin); + + // pow constants computed so that + // | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32 + band_scale_[i] = -std::cos(M_PI * i / (kRainbowBands - 1)) * 0.5 * std::pow(2.3, i); + } + +} + +void Rainbow::RainbowAnalyzer::transform(Scope& s) { fht_->spectrum(s.data()); } + +void Rainbow::RainbowAnalyzer::timerEvent(QTimerEvent* e) { + + if (e->timerId() == timer_id_) { + frame_ = (frame_ + 1) % kFrameCount[rainbowtype]; + } + else { + Analyzer::Base::timerEvent(e); + } + +} + +void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent* e) { + + // Invalidate the buffer so it's recreated from scratch in the next paint event. + buffer_[0] = QPixmap(); + buffer_[1] = QPixmap(); + + available_rainbow_width_ = width() - kWidth[rainbowtype] + kRainbowOverlap[rainbowtype]; + px_per_frame_ = static_cast(available_rainbow_width_) / (kHistorySize - 1) + 1; + x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_; + +} + +void Rainbow::RainbowAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, bool new_frame) { + + // Discard the second half of the transform + const int scope_size = s.size() / 2; + + if ((new_frame && is_playing_) || (buffer_[0].isNull() && buffer_[1].isNull())) { + // Transform the music into rainbows! + for (int band = 0; band < kRainbowBands; ++band) { + float* band_start = history_ + band * kHistorySize; + + // Move the history of each band across by 1 frame. + memmove(band_start, band_start + 1, (kHistorySize - 1) * sizeof(float)); + } + + // Now accumulate the scope data into each band. Should maybe use a series + // of band pass filters for this, so bands can leak into neighbouring bands, + // but for now it's a series of separate square filters. + const int samples_per_band = scope_size / kRainbowBands; + int sample = 0; + for (int band = 0; band < kRainbowBands; ++band) { + float accumulator = 0.0; + for (int i = 0; i < samples_per_band; ++i) { + accumulator += s[sample++]; + } + + history_[(band + 1) * kHistorySize - 1] = accumulator * band_scale_[band]; + } + + // Create polylines for the rainbows. + QPointF polyline[kRainbowBands * kHistorySize]; + QPointF* dest = polyline; + float* source = history_; + + const float top_of = static_cast(height()) / 2 - static_cast(kRainbowHeight[rainbowtype]) / 2; + for (int band = 0; band < kRainbowBands; ++band) { + // Calculate the Y position of this band. + const float y = static_cast(kRainbowHeight[rainbowtype]) / (kRainbowBands + 1) * (band + 0.5) + top_of; + + // Add each point in the line. + for (int x = 0; x < kHistorySize; ++x) { + *dest = QPointF(px_per_frame_ * x, y + *source * kPixelScale); + ++dest; + ++source; + } + } + + // Do we have to draw the whole rainbow into the buffer? + if (buffer_[0].isNull()) { + for (int i = 0; i < 2; ++i) { + buffer_[i] = QPixmap(QSize(width() + x_offset_, height())); + buffer_[i].fill(background_brush_.color()); + } + current_buffer_ = 0; + + QPainter buffer_painter(&buffer_[0]); + buffer_painter.setRenderHint(QPainter::Antialiasing); + for (int band = kRainbowBands - 1; band >= 0; --band) { + buffer_painter.setPen(colors_[band]); + buffer_painter.drawPolyline(&polyline[band * kHistorySize], kHistorySize); + buffer_painter.drawPolyline(&polyline[band * kHistorySize], kHistorySize); + } + } + else { + const int last_buffer = current_buffer_; + current_buffer_ = (current_buffer_ + 1) % 2; + + // We can just shuffle the buffer along a bit and draw the new frame's data. + QPainter buffer_painter(&buffer_[current_buffer_]); + buffer_painter.setRenderHint(QPainter::Antialiasing); + + buffer_painter.drawPixmap(0, 0, buffer_[last_buffer], px_per_frame_, 0, x_offset_ + available_rainbow_width_ - px_per_frame_, 0); + buffer_painter.fillRect(x_offset_ + available_rainbow_width_ - px_per_frame_, 0, kWidth[rainbowtype] - kRainbowOverlap[rainbowtype] + px_per_frame_, height(), background_brush_); + + for (int band = kRainbowBands - 1; band >= 0; --band) { + buffer_painter.setPen(colors_[band]); + buffer_painter.drawPolyline(&polyline[(band + 1) * kHistorySize - 3], 3); + } + } + } + + // Draw the buffer on to the widget + p.drawPixmap(0, 0, buffer_[current_buffer_], x_offset_, 0, 0, 0); + + // Draw rainbow analyzer (nyan cat or rainbowdash) + // Nyan nyan nyan nyan dash dash dash dash. + if (!is_playing_) { + // Ssshhh! + p.drawPixmap(SleepingDestRect(rainbowtype), cat_dash_[rainbowtype], SleepingSourceRect(rainbowtype)); + } + else { + p.drawPixmap(DestRect(rainbowtype), cat_dash_[rainbowtype], SourceRect(rainbowtype)); + } + +} + +Rainbow::NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent) + : RainbowAnalyzer(Rainbow::RainbowAnalyzer::Nyancat, parent) {} + +Rainbow::RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget* parent) + : RainbowAnalyzer(Rainbow::RainbowAnalyzer::Dash, parent) {} diff --git a/src/analyzer/rainbowanalyzer.h b/src/analyzer/rainbowanalyzer.h new file mode 100644 index 00000000..ce65e631 --- /dev/null +++ b/src/analyzer/rainbowanalyzer.h @@ -0,0 +1,141 @@ +/* + Strawberry Music Player + This file was part of Clementine. + Copyright 2011, Tyler Rhodes + Copyright 2011-2012, 2014, David Sansome + Copyright 2011, 2014, John Maguire + Copyright 2014, Alibek Omarov + Copyright 2014, Krzysztof Sobiecki + Copyright 2014-2015, Mark Furneaux + Copyright 2015, Arun Narayanankutty + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Strawberry is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Strawberry. If not, see . +*/ + +#ifndef RAINBOWANALYZER_H +#define RAINBOWANALYZER_H + +#include "analyzerbase.h" + +#include +#include +#include +#include +#include + +namespace Rainbow { +class RainbowAnalyzer : public Analyzer::Base { + Q_OBJECT + + public: + enum RainbowType { + Nyancat = 0, + Dash = 1 + }; + + RainbowAnalyzer(const RainbowType& rbtype, QWidget* parent); + + protected: + void transform(Analyzer::Scope&); + void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame); + + void timerEvent(QTimerEvent* e); + void resizeEvent(QResizeEvent* e); + + private: + static const int kHeight[]; + static const int kWidth[]; + static const int kFrameCount[]; + static const int kRainbowHeight[]; + static const int kRainbowOverlap[]; + static const int kSleepingHeight[]; + + static const int kHistorySize = 128; + static const int kRainbowBands = 6; + static const float kPixelScale; + + static const int kFrameIntervalMs = 150; + + static RainbowType rainbowtype; + + inline QRect SourceRect(RainbowType rainbowtype) const { + return QRect(0, kHeight[rainbowtype] * frame_, kWidth[rainbowtype], kHeight[rainbowtype]); + } + + inline QRect SleepingSourceRect(RainbowType rainbowtype) const { + return QRect(0, kHeight[rainbowtype] * kFrameCount[rainbowtype], kWidth[rainbowtype], kSleepingHeight[rainbowtype]); + } + + inline QRect DestRect(RainbowType rainbowtype) const { + return QRect(width() - kWidth[rainbowtype], (height() - kHeight[rainbowtype]) / 2, kWidth[rainbowtype], kHeight[rainbowtype]); + } + + inline QRect SleepingDestRect(RainbowType rainbowtype) const { + return QRect(width() - kWidth[rainbowtype], (height() - kSleepingHeight[rainbowtype]) / 2, kWidth[rainbowtype], kSleepingHeight[rainbowtype]); + } + + private: + // "constants" that get initialised in the constructor + float band_scale_[kRainbowBands]; + QPen colors_[kRainbowBands]; + + // Rainbow Nyancat & Dash + QPixmap cat_dash_[2]; + + // For the cat or dash animation + int timer_id_; + int frame_; + + // The y positions of each point on the rainbow. + float history_[kHistorySize * kRainbowBands]; + + // A cache of the last frame's rainbow, + // so it can be used in the next frame. + QPixmap buffer_[2]; + int current_buffer_; + + // Geometry information that's updated on resize: + // The width of the widget minus the space for the cat + int available_rainbow_width_; + + // X spacing between each point in the polyline. + int px_per_frame_; + + // Amount the buffer_ is shifted to the left (off the edge of the widget) + // to make the rainbow extend from 0 to available_rainbow_width_. + int x_offset_; + + QBrush background_brush_; +}; + +class NyanCatAnalyzer : public RainbowAnalyzer { + Q_OBJECT + + public: + Q_INVOKABLE NyanCatAnalyzer(QWidget* parent); + + static const char* kName; +}; + +class RainbowDashAnalyzer : public RainbowAnalyzer { + Q_OBJECT + + public: + Q_INVOKABLE RainbowDashAnalyzer(QWidget* parent); + + static const char* kName; +}; +} + +#endif // RAINBOWANALYZER_H