/* 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 . */ #include "config.h" #include "analyzerbase.h" #include #include #include #include #include #include #include #include #include #include #include #include "engine/enginebase.h" // INSTRUCTIONS Base2D // 1. do anything that depends on height() in init(), Base2D will call it before you are shown // 2. otherwise you can use the constructor to initialize things // 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 // can't mod scope in analyze you have to use transform for 2D use setErasePixmap Qt function insetead of m_background AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scopeSize) : QWidget(parent), fht_(new FHT(scopeSize)), engine_(nullptr), lastscope_(512), new_frame_(false), is_playing_(false), timeout_(40) { setAttribute(Qt::WA_OpaquePaintEvent, true); } AnalyzerBase::~AnalyzerBase() { delete fht_; } void AnalyzerBase::showEvent(QShowEvent *e) { Q_UNUSED(e) timer_.start(timeout(), this); } void AnalyzerBase::hideEvent(QHideEvent *e) { Q_UNUSED(e) timer_.stop(); } void AnalyzerBase::ChangeTimeout(const int timeout) { timeout_ = timeout; if (timer_.isActive()) { timer_.stop(); timer_.start(timeout_, this); } } void AnalyzerBase::transform(Scope &scope) { QList aux(fht_->size()); if (static_cast(aux.size()) >= scope.size()) { std::copy(scope.begin(), scope.end(), aux.begin()); } else { std::copy(scope.begin(), scope.begin() + aux.size(), aux.begin()); } fht_->logSpectrum(scope.data(), aux.data()); fht_->scale(scope.data(), 1.0F / 20); scope.resize(fht_->size() / 2); // second half of values are rubbish } void AnalyzerBase::paintEvent(QPaintEvent *e) { QPainter p(this); p.fillRect(e->rect(), palette().color(QPalette::Window)); switch (engine_->state()) { case EngineBase::State::Playing:{ const EngineBase::Scope &thescope = engine_->scope(timeout_); int i = 0; // convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm for (uint x = 0; static_cast(x) < fht_->size(); ++x) { lastscope_[x] = static_cast(thescope[i] + thescope[i + 1]) / (2 * (1U << 15U)); i += 2; } is_playing_ = true; transform(lastscope_); analyze(p, lastscope_, new_frame_); lastscope_.resize(fht_->size()); break; } case EngineBase::State::Paused: is_playing_ = false; analyze(p, lastscope_, new_frame_); break; default: is_playing_ = false; demo(p); } new_frame_ = false; } int AnalyzerBase::resizeExponent(int exp) { if (exp < 3) { exp = 3; } else if (exp > 9) { exp = 9; } if (exp != fht_->sizeExp()) { delete fht_; fht_ = new FHT(exp); } return exp; } int AnalyzerBase::resizeForBands(const int bands) { int exp = 0; if (bands <= 8) { exp = 4; } else if (bands <= 16) { exp = 5; } else if (bands <= 32) { exp = 6; } else if (bands <= 64) { exp = 7; } else if (bands <= 128) { exp = 8; } else { exp = 9; } resizeExponent(exp); return fht_->size() / 2; } void AnalyzerBase::demo(QPainter &p) { static int t = 201; // FIXME make static to namespace perhaps if (t > 999) { t = 1; // 0 = wasted calculations } if (t < 201) { Scope s(32); const double dt = static_cast(t) / 200; for (uint i = 0; i < s.size(); ++i) { s[i] = static_cast(dt * (sin(M_PI + (i * M_PI) / static_cast(s.size())) + 1.0)); } analyze(p, s, new_frame_); } else { analyze(p, Scope(32, 0), new_frame_); } ++t; } void AnalyzerBase::interpolate(const Scope &inVec, Scope &outVec) { double pos = 0.0; const double step = static_cast(inVec.size()) / static_cast(outVec.size()); for (uint i = 0; i < outVec.size(); ++i, pos += step) { const double error = pos - std::floor(pos); const uint64_t offset = static_cast(pos); uint64_t indexLeft = offset + 0; if (indexLeft >= inVec.size()) { indexLeft = inVec.size() - 1; } uint64_t indexRight = offset + 1; if (indexRight >= inVec.size()) { indexRight = inVec.size() - 1; } outVec[i] = inVec[indexLeft] * (1.0F - static_cast(error)) + inVec[indexRight] * static_cast(error); } } void AnalyzerBase::initSin(Scope &v, const uint size) { double step = (M_PI * 2) / size; double radian = 0; for (uint i = 0; i < size; i++) { v.push_back(static_cast(sin(radian))); radian += step; } } void AnalyzerBase::timerEvent(QTimerEvent *e) { QWidget::timerEvent(e); if (e->timerId() != timer_.timerId()) { return; } new_frame_ = true; update(); }