2019-02-04 21:34:12 +01:00
|
|
|
/*
|
|
|
|
Strawberry Music Player
|
|
|
|
This file was part of Amarok.
|
|
|
|
Copyright 2003-2004, Max Howell <max.howell@methylblue.com>
|
|
|
|
Copyright 2009-2012, David Sansome <me@davidsansome.com>
|
|
|
|
Copyright 2010, 2012, 2014, John Maguire <john.maguire@gmail.com>
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
#include "config.h"
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2019-02-04 21:34:12 +01:00
|
|
|
#include "analyzerbase.h"
|
|
|
|
|
|
|
|
#include <cstdint>
|
2023-07-21 05:55:24 +02:00
|
|
|
#include <cmath>
|
|
|
|
#include <algorithm>
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QWidget>
|
2019-02-04 21:34:12 +01:00
|
|
|
#include <QVector>
|
2018-02-27 18:06:05 +01:00
|
|
|
#include <QPainter>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QPalette>
|
2022-04-01 22:30:00 +02:00
|
|
|
#include <QBasicTimer>
|
|
|
|
#include <QShowEvent>
|
|
|
|
#include <QHideEvent>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QTimerEvent>
|
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
#include "engine/enginebase.h"
|
|
|
|
|
|
|
|
// INSTRUCTIONS Base2D
|
2018-09-30 15:32:21 +02:00
|
|
|
// 1. do anything that depends on height() in init(), Base2D will call it before you are shown
|
2020-10-17 17:29:09 +02:00
|
|
|
// 2. otherwise you can use the constructor to initialize things
|
2018-09-30 15:32:21 +02:00
|
|
|
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
|
2018-02-27 18:06:05 +01:00
|
|
|
// 4. if you want to manipulate the scope, reimplement transform()
|
|
|
|
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
|
2019-02-04 21:34:12 +01:00
|
|
|
//
|
|
|
|
// TODO:
|
|
|
|
// Make an INSTRUCTIONS file
|
2022-01-17 22:45:19 +01:00
|
|
|
// can't mod scope in analyze you have to use transform for 2D use setErasePixmap Qt function insetead of m_background
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scopeSize)
|
2018-02-27 18:06:05 +01:00
|
|
|
: QWidget(parent),
|
2018-09-30 15:32:21 +02:00
|
|
|
fht_(new FHT(scopeSize)),
|
|
|
|
engine_(nullptr),
|
|
|
|
lastscope_(512),
|
2018-02-27 18:06:05 +01:00
|
|
|
new_frame_(false),
|
2022-04-01 22:30:00 +02:00
|
|
|
is_playing_(false),
|
2022-08-24 20:32:08 +02:00
|
|
|
timeout_(40) {
|
|
|
|
|
|
|
|
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
|
|
|
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
AnalyzerBase::~AnalyzerBase() {
|
2022-04-01 22:30:00 +02:00
|
|
|
delete fht_;
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::showEvent(QShowEvent*) {
|
2022-04-01 22:30:00 +02:00
|
|
|
timer_.start(timeout(), this);
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::hideEvent(QHideEvent*) {
|
2022-04-01 22:30:00 +02:00
|
|
|
timer_.stop();
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::ChangeTimeout(const int timeout) {
|
2022-04-01 22:30:00 +02:00
|
|
|
|
|
|
|
timeout_ = timeout;
|
|
|
|
if (timer_.isActive()) {
|
|
|
|
timer_.stop();
|
|
|
|
timer_.start(timeout_, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::transform(Scope &scope) {
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2019-02-04 21:34:12 +01:00
|
|
|
QVector<float> aux(fht_->size());
|
2021-06-20 23:56:33 +02:00
|
|
|
if (static_cast<unsigned long int>(aux.size()) >= scope.size()) {
|
2019-02-04 21:34:12 +01:00
|
|
|
std::copy(scope.begin(), scope.end(), aux.begin());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
std::copy(scope.begin(), scope.begin() + aux.size(), aux.begin());
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2019-02-04 21:34:12 +01:00
|
|
|
fht_->logSpectrum(scope.data(), aux.data());
|
2022-02-06 04:19:45 +01:00
|
|
|
fht_->scale(scope.data(), 1.0F / 20);
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
scope.resize(fht_->size() / 2); // second half of values are rubbish
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::paintEvent(QPaintEvent *e) {
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
QPainter p(this);
|
|
|
|
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
switch (engine_->state()) {
|
2024-04-11 02:56:01 +02:00
|
|
|
case EngineBase::State::Playing:{
|
2023-04-22 19:13:42 +02:00
|
|
|
const EngineBase::Scope &thescope = engine_->scope(timeout_);
|
2018-02-27 18:06:05 +01:00
|
|
|
int i = 0;
|
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
// convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm
|
2020-06-15 17:59:02 +02:00
|
|
|
for (uint x = 0; static_cast<int>(x) < fht_->size(); ++x) {
|
2021-10-11 22:28:28 +02:00
|
|
|
lastscope_[x] = static_cast<float>(thescope[i] + thescope[i + 1]) / (2 * (1U << 15U));
|
2018-02-27 18:06:05 +01:00
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
is_playing_ = true;
|
2018-09-30 15:32:21 +02:00
|
|
|
transform(lastscope_);
|
|
|
|
analyze(p, lastscope_, new_frame_);
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
lastscope_.resize(fht_->size());
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2023-04-22 19:13:42 +02:00
|
|
|
case EngineBase::State::Paused:
|
2018-02-27 18:06:05 +01:00
|
|
|
is_playing_ = false;
|
2018-09-30 15:32:21 +02:00
|
|
|
analyze(p, lastscope_, new_frame_);
|
2018-02-27 18:06:05 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
is_playing_ = false;
|
|
|
|
demo(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
new_frame_ = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
int AnalyzerBase::resizeExponent(int exp) {
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2021-08-23 21:21:08 +02:00
|
|
|
if (exp < 3) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 3;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else if (exp > 9) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 9;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
if (exp != fht_->sizeExp()) {
|
|
|
|
delete fht_;
|
|
|
|
fht_ = new FHT(exp);
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
return exp;
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
int AnalyzerBase::resizeForBands(const int bands) {
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2021-03-26 21:30:13 +01:00
|
|
|
int exp = 0;
|
2021-08-23 21:21:08 +02:00
|
|
|
if (bands <= 8) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 4;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else if (bands <= 16) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 5;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else if (bands <= 32) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 6;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else if (bands <= 64) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 7;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else if (bands <= 128) {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 8;
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
|
|
|
else {
|
2018-02-27 18:06:05 +01:00
|
|
|
exp = 9;
|
2022-03-22 21:09:05 +01:00
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
resizeExponent(exp);
|
2018-09-30 15:32:21 +02:00
|
|
|
return fht_->size() / 2;
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::demo(QPainter &p) {
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
static int t = 201; // FIXME make static to namespace perhaps
|
|
|
|
|
2021-08-23 21:21:08 +02:00
|
|
|
if (t > 999) {
|
|
|
|
t = 1; // 0 = wasted calculations
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
if (t < 201) {
|
|
|
|
Scope s(32);
|
|
|
|
|
2021-10-11 22:28:28 +02:00
|
|
|
const double dt = static_cast<double>(t) / 200;
|
2021-08-23 21:21:08 +02:00
|
|
|
for (uint i = 0; i < s.size(); ++i) {
|
2021-10-30 02:21:29 +02:00
|
|
|
s[i] = static_cast<float>(dt * (sin(M_PI + (i * M_PI) / static_cast<double>(s.size())) + 1.0));
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
analyze(p, s, new_frame_);
|
2018-09-30 15:32:21 +02:00
|
|
|
}
|
2021-08-23 21:21:08 +02:00
|
|
|
else {
|
2018-02-27 18:06:05 +01:00
|
|
|
analyze(p, Scope(32, 0), new_frame_);
|
2021-08-23 21:21:08 +02:00
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
++t;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::interpolate(const Scope &inVec, Scope &outVec) {
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
double pos = 0.0;
|
2021-10-30 02:21:29 +02:00
|
|
|
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
|
|
|
|
const double error = pos - std::floor(pos);
|
2020-06-15 17:59:02 +02:00
|
|
|
const uint64_t offset = static_cast<uint64_t>(pos);
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
uint64_t indexLeft = offset + 0;
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2021-08-23 21:21:08 +02:00
|
|
|
if (indexLeft >= inVec.size()) {
|
|
|
|
indexLeft = inVec.size() - 1;
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-09-30 15:32:21 +02:00
|
|
|
uint64_t indexRight = offset + 1;
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2021-08-23 21:21:08 +02:00
|
|
|
if (indexRight >= inVec.size()) {
|
|
|
|
indexRight = inVec.size() - 1;
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2021-10-30 02:21:29 +02:00
|
|
|
outVec[i] = inVec[indexLeft] * (1.0F - static_cast<float>(error)) + inVec[indexRight] * static_cast<float>(error);
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::initSin(Scope &v, const uint size) {
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
double step = (M_PI * 2) / size;
|
|
|
|
double radian = 0;
|
|
|
|
|
|
|
|
for (uint i = 0; i < size; i++) {
|
2021-10-30 02:21:29 +02:00
|
|
|
v.push_back(static_cast<float>(sin(radian)));
|
2018-02-27 18:06:05 +01:00
|
|
|
radian += step;
|
|
|
|
}
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 19:45:21 +02:00
|
|
|
void AnalyzerBase::timerEvent(QTimerEvent *e) {
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
QWidget::timerEvent(e);
|
2021-08-23 21:21:08 +02:00
|
|
|
if (e->timerId() != timer_.timerId()) {
|
|
|
|
return;
|
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
|
|
|
|
new_frame_ = true;
|
|
|
|
update();
|
2018-09-30 15:32:21 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|