2014-11-29 20:07:01 +01:00
|
|
|
/* This file is part of Clementine.
|
|
|
|
Copyright 2003, Max Howell <max.howell@methylblue.com>
|
|
|
|
Copyright 2009, 2011-2012, David Sansome <me@davidsansome.com>
|
|
|
|
Copyright 2010, 2012, 2014, John Maguire <john.maguire@gmail.com>
|
|
|
|
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
|
|
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
|
|
|
|
|
|
|
Clementine 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.
|
|
|
|
|
|
|
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Original Author: Max Howell <max.howell@methylblue.com> 2003
|
|
|
|
*/
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
#include "analyzerbase.h"
|
2012-11-13 14:41:15 +01:00
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
#include <cmath>
|
2014-11-29 21:05:59 +01:00
|
|
|
#include <cstdint>
|
2012-11-13 14:41:15 +01:00
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
#include <QEvent>
|
2009-12-24 20:16:07 +01:00
|
|
|
#include <QPainter>
|
|
|
|
#include <QPaintEvent>
|
|
|
|
#include <QtDebug>
|
|
|
|
|
2012-11-13 14:41:15 +01:00
|
|
|
#include "engines/enginebase.h"
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
// INSTRUCTIONS Base2D
|
2014-02-07 16:34:20 +01:00
|
|
|
// 1. do anything that depends on height() in init(), Base2D will call it before
|
|
|
|
// you are shown
|
2009-12-24 20:16:07 +01:00
|
|
|
// 2. otherwise you can use the constructor to initialise things
|
2014-02-07 16:34:20 +01:00
|
|
|
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the
|
|
|
|
// widget when you return control to it
|
2009-12-24 20:16:07 +01:00
|
|
|
// 4. if you want to manipulate the scope, reimplement transform()
|
|
|
|
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
|
2014-11-29 20:07:01 +01:00
|
|
|
// TODO(David Sansome): make an INSTRUCTIONS file
|
2014-02-07 16:34:20 +01:00
|
|
|
// can't mod scope in analyze you have to use transform
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
// TODO(John Maguire): for 2D use setErasePixmap Qt function insetead of m_background
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
// make the linker happy only for gcc < 4.0
|
2014-02-07 16:34:20 +01:00
|
|
|
#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
|
|
|
|
!defined(Q_OS_WIN32)
|
2009-12-24 20:16:07 +01:00
|
|
|
template class Analyzer::Base<QWidget>;
|
|
|
|
#endif
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
Analyzer::Base::Base(QWidget* parent, uint scopeSize)
|
|
|
|
: QWidget(parent),
|
|
|
|
m_timeout(40) // msec
|
|
|
|
,
|
|
|
|
m_fht(new FHT(scopeSize)),
|
|
|
|
m_engine(nullptr),
|
|
|
|
m_lastScope(512),
|
2014-05-03 15:14:15 +02:00
|
|
|
current_chunk_(0),
|
2014-02-07 16:34:20 +01:00
|
|
|
new_frame_(false),
|
|
|
|
is_playing_(false) {}
|
|
|
|
|
|
|
|
void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); }
|
|
|
|
|
|
|
|
void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); }
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
void Analyzer::Base::transform(Scope& scope) {
|
2014-02-07 16:34:20 +01:00
|
|
|
// this is a standard transformation that should give
|
|
|
|
// an FFT scope that has bands for pretty analyzers
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
// NOTE resizing here is redundant as FHT routines only calculate FHT::size()
|
|
|
|
// values
|
|
|
|
// scope.resize( m_fht->size() );
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
float* front = static_cast<float*>(&scope.front());
|
|
|
|
|
|
|
|
float* f = new float[m_fht->size()];
|
|
|
|
m_fht->copy(&f[0], front);
|
|
|
|
m_fht->logSpectrum(front, &f[0]);
|
|
|
|
m_fht->scale(front, 1.0 / 20);
|
|
|
|
|
|
|
|
scope.resize(m_fht->size() / 2); // second half of values are rubbish
|
|
|
|
delete[] f;
|
2010-08-28 20:48:16 +02:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void Analyzer::Base::paintEvent(QPaintEvent* e) {
|
|
|
|
QPainter p(this);
|
|
|
|
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
2010-08-28 20:48:16 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
switch (m_engine->state()) {
|
|
|
|
case Engine::Playing: {
|
2014-04-30 03:38:21 +02:00
|
|
|
const Engine::Scope& thescope = m_engine->scope(m_timeout);
|
2014-02-07 16:34:20 +01:00
|
|
|
int i = 0;
|
2010-08-28 20:48:16 +02:00
|
|
|
|
2014-04-30 03:38:21 +02:00
|
|
|
// convert to mono here - our built in analyzers need mono, but the
|
2014-02-07 16:34:20 +01:00
|
|
|
// engines provide interleaved pcm
|
2014-11-29 20:07:01 +01:00
|
|
|
for (uint x = 0; static_cast<int>(x) < m_fht->size(); ++x) {
|
2014-02-07 16:34:20 +01:00
|
|
|
m_lastScope[x] =
|
2014-11-29 20:07:01 +01:00
|
|
|
static_cast<double>(thescope[i] + thescope[i + 1]) / (2 * (1 << 15));
|
2014-02-07 16:34:20 +01:00
|
|
|
i += 2;
|
|
|
|
}
|
2010-08-28 20:48:16 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
is_playing_ = true;
|
|
|
|
transform(m_lastScope);
|
|
|
|
analyze(p, m_lastScope, new_frame_);
|
2010-08-28 20:48:16 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
// scope.resize( m_fht->size() );
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
break;
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
case Engine::Paused:
|
2014-02-07 16:34:20 +01:00
|
|
|
is_playing_ = false;
|
|
|
|
analyze(p, m_lastScope, new_frame_);
|
|
|
|
break;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
|
|
|
default:
|
2014-02-07 16:34:20 +01:00
|
|
|
is_playing_ = false;
|
|
|
|
demo(p);
|
|
|
|
}
|
2012-10-16 12:20:56 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
new_frame_ = false;
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
int Analyzer::Base::resizeExponent(int exp) {
|
|
|
|
if (exp < 3)
|
|
|
|
exp = 3;
|
|
|
|
else if (exp > 9)
|
|
|
|
exp = 9;
|
|
|
|
|
|
|
|
if (exp != m_fht->sizeExp()) {
|
|
|
|
delete m_fht;
|
|
|
|
m_fht = new FHT(exp);
|
|
|
|
}
|
|
|
|
return exp;
|
2010-08-28 20:48:16 +02:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
int Analyzer::Base::resizeForBands(int bands) {
|
|
|
|
int exp;
|
|
|
|
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 m_fht->size() / 2;
|
2010-08-28 20:48:16 +02:00
|
|
|
}
|
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
void Analyzer::Base::demo(QPainter& p) {
|
2014-02-07 16:34:20 +01:00
|
|
|
static int t = 201; // FIXME make static to namespace perhaps
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
if (t > 999) t = 1; // 0 = wasted calculations
|
|
|
|
if (t < 201) {
|
|
|
|
Scope s(32);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
const double dt = static_cast<double>(t) / 200;
|
2014-02-07 16:34:20 +01:00
|
|
|
for (uint i = 0; i < s.size(); ++i)
|
|
|
|
s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
analyze(p, s, new_frame_);
|
2014-11-29 20:07:01 +01:00
|
|
|
} else {
|
2014-02-07 16:34:20 +01:00
|
|
|
analyze(p, Scope(32, 0), new_frame_);
|
2014-11-29 20:07:01 +01:00
|
|
|
}
|
2014-02-07 16:34:20 +01:00
|
|
|
++t;
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void Analyzer::Base::polishEvent() {
|
|
|
|
init(); // virtual
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
void Analyzer::interpolate(const Scope& inVec, Scope& outVec) {
|
2014-02-07 16:34:20 +01:00
|
|
|
double pos = 0.0;
|
2014-11-29 20:07:01 +01:00
|
|
|
const double step = static_cast<double>(inVec.size()) / outVec.size();
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
|
|
|
|
const double error = pos - std::floor(pos);
|
2014-11-29 21:05:59 +01:00
|
|
|
const uint64_t offset = static_cast<uint64_t>(pos);
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-11-29 21:05:59 +01:00
|
|
|
uint64_t indexLeft = offset + 0;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-11-29 21:05:59 +01:00
|
|
|
uint64_t indexRight = offset + 1;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
if (indexRight >= inVec.size()) indexRight = inVec.size() - 1;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
|
|
|
|
}
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
|
|
|
|
2014-11-29 20:07:01 +01:00
|
|
|
void Analyzer::initSin(Scope& v, const uint size) {
|
2014-02-07 16:34:20 +01:00
|
|
|
double step = (M_PI * 2) / size;
|
|
|
|
double radian = 0;
|
2009-12-24 20:16:07 +01:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (uint i = 0; i < size; i++) {
|
|
|
|
v.push_back(sin(radian));
|
|
|
|
radian += step;
|
|
|
|
}
|
2009-12-24 20:16:07 +01:00
|
|
|
}
|
2010-08-28 20:48:16 +02:00
|
|
|
|
|
|
|
void Analyzer::Base::timerEvent(QTimerEvent* e) {
|
|
|
|
QWidget::timerEvent(e);
|
2014-02-07 16:34:20 +01:00
|
|
|
if (e->timerId() != m_timer.timerId()) return;
|
2010-08-28 20:48:16 +02:00
|
|
|
|
2011-06-23 22:36:14 +02:00
|
|
|
new_frame_ = true;
|
2010-08-28 20:48:16 +02:00
|
|
|
update();
|
|
|
|
}
|