Initial commit.
This commit is contained in:
14
src/analyzer/analyzer.cpp
Normal file
14
src/analyzer/analyzer.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "analyzer.h"
|
||||
|
||||
#include "engines/enginebase.h"
|
||||
|
||||
AnalyzerBase::AnalyzerBase(QWidget* parent)
|
||||
: QGLWidget(parent), engine_(nullptr) {}
|
||||
|
||||
void AnalyzerBase::set_engine(Engine::Base* engine) {
|
||||
disconnect(engine_);
|
||||
engine_ = engine;
|
||||
if (engine_) {
|
||||
connect(engine_, SIGNAL(SpectrumAvailable(const QVector<float>&)), SLOT(SpectrumAvailable(const QVector<float>&)));
|
||||
}
|
||||
}
|
||||
223
src/analyzer/analyzerbase.cpp
Executable file
223
src/analyzer/analyzerbase.cpp
Executable file
@@ -0,0 +1,223 @@
|
||||
/***************************************************************************
|
||||
viswidget.cpp - description
|
||||
-------------------
|
||||
begin : Die Jan 7 2003
|
||||
copyright : (C) 2003 by Max Howell
|
||||
email : markey@web.de
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "analyzerbase.h"
|
||||
|
||||
#include <cmath> //interpolate()
|
||||
|
||||
#include <QEvent> //event()
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QtDebug>
|
||||
|
||||
#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 initialise 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 <vector> <qpixmap.h> <qwdiget.h> are pre-included
|
||||
// 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
|
||||
|
||||
// make the linker happy only for gcc < 4.0
|
||||
#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
|
||||
!defined(Q_OS_WIN32)
|
||||
template class Analyzer::Base<QWidget>;
|
||||
#endif
|
||||
|
||||
Analyzer::Base::Base(QWidget* parent, uint scopeSize)
|
||||
: QWidget(parent),
|
||||
m_timeout(40) // msec
|
||||
,
|
||||
m_fht(new FHT(scopeSize)),
|
||||
m_engine(nullptr),
|
||||
m_lastScope(512),
|
||||
current_chunk_(0),
|
||||
new_frame_(false),
|
||||
is_playing_(false) {}
|
||||
|
||||
void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); }
|
||||
|
||||
void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); }
|
||||
|
||||
void Analyzer::Base::transform(Scope& scope) // virtual
|
||||
{
|
||||
|
||||
// this is a standard transformation that should give
|
||||
// an FFT scope that has bands for pretty analyzers
|
||||
|
||||
// NOTE resizing here is redundant as FHT routines only calculate FHT::size()
|
||||
// values
|
||||
// scope.resize( m_fht->size() );
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
void Analyzer::Base::paintEvent(QPaintEvent* e) {
|
||||
|
||||
QPainter p(this);
|
||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||
|
||||
switch (m_engine->state()) {
|
||||
case Engine::Playing: {
|
||||
const Engine::Scope& thescope = m_engine->scope(m_timeout);
|
||||
int i = 0;
|
||||
|
||||
// convert to mono here - our built in analyzers need mono, but the
|
||||
// engines provide interleaved pcm
|
||||
for (uint x = 0; (int)x < m_fht->size(); ++x) {
|
||||
m_lastScope[x] = double(thescope[i] + thescope[i + 1]) / (2 * (1 << 15));
|
||||
i += 2;
|
||||
}
|
||||
|
||||
is_playing_ = true;
|
||||
transform(m_lastScope);
|
||||
analyze(p, m_lastScope, new_frame_);
|
||||
|
||||
// scope.resize( m_fht->size() );
|
||||
|
||||
break;
|
||||
}
|
||||
case Engine::Paused:
|
||||
is_playing_ = false;
|
||||
analyze(p, m_lastScope, new_frame_);
|
||||
break;
|
||||
|
||||
default:
|
||||
is_playing_ = false;
|
||||
demo(p);
|
||||
}
|
||||
|
||||
new_frame_ = false;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
void Analyzer::Base::demo(QPainter& p) // virtual
|
||||
{
|
||||
|
||||
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 = double(t) / 200;
|
||||
for (uint i = 0; i < s.size(); ++i)
|
||||
s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
|
||||
|
||||
analyze(p, s, new_frame_);
|
||||
} else
|
||||
analyze(p, Scope(32, 0), new_frame_);
|
||||
|
||||
++t;
|
||||
|
||||
}
|
||||
|
||||
void Analyzer::Base::polishEvent() {
|
||||
init(); // virtual
|
||||
}
|
||||
|
||||
void Analyzer::interpolate(const Scope& inVec, Scope& outVec) // static
|
||||
{
|
||||
|
||||
double pos = 0.0;
|
||||
const double step = (double)inVec.size() / outVec.size();
|
||||
|
||||
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
|
||||
const double error = pos - std::floor(pos);
|
||||
const unsigned long offset = (unsigned long)pos;
|
||||
|
||||
unsigned long indexLeft = offset + 0;
|
||||
|
||||
if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1;
|
||||
|
||||
unsigned long indexRight = offset + 1;
|
||||
|
||||
if (indexRight >= inVec.size()) indexRight = inVec.size() - 1;
|
||||
|
||||
outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Analyzer::initSin(Scope& v, const uint size) // static
|
||||
{
|
||||
double step = (M_PI * 2) / size;
|
||||
double radian = 0;
|
||||
|
||||
for (uint i = 0; i < size; i++) {
|
||||
v.push_back(sin(radian));
|
||||
radian += step;
|
||||
}
|
||||
}
|
||||
|
||||
void Analyzer::Base::timerEvent(QTimerEvent* e) {
|
||||
QWidget::timerEvent(e);
|
||||
if (e->timerId() != m_timer.timerId()) return;
|
||||
|
||||
new_frame_ = true;
|
||||
update();
|
||||
}
|
||||
89
src/analyzer/analyzerbase.h
Normal file
89
src/analyzer/analyzerbase.h
Normal file
@@ -0,0 +1,89 @@
|
||||
// Maintainer: Max Howell <max.howell@methylblue.com>, (C) 2004
|
||||
// Copyright: See COPYING file that comes with this distribution
|
||||
|
||||
#ifndef ANALYZERBASE_H
|
||||
#define ANALYZERBASE_H
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include "analyzer/fht.h" //stack allocated and convenience
|
||||
#include "engine/engine_fwd.h"
|
||||
#include <QPixmap> //stack allocated and convenience
|
||||
#include <QBasicTimer> //stack allocated
|
||||
#include <QWidget> //baseclass
|
||||
#include <vector> //included for convenience
|
||||
|
||||
#include <QGLWidget> //baseclass
|
||||
#ifdef Q_WS_MACX
|
||||
#include <OpenGL/gl.h> //included for convenience
|
||||
#include <OpenGL/glu.h> //included for convenience
|
||||
#else
|
||||
#include <GL/gl.h> //included for convenience
|
||||
#include <GL/glu.h> //included for convenience
|
||||
#endif
|
||||
|
||||
class QEvent;
|
||||
class QPaintEvent;
|
||||
class QResizeEvent;
|
||||
|
||||
namespace Analyzer {
|
||||
|
||||
typedef std::vector<float> Scope;
|
||||
|
||||
class Base : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~Base() { delete m_fht; }
|
||||
|
||||
uint timeout() const { return m_timeout; }
|
||||
|
||||
void set_engine(EngineBase *engine) { m_engine = engine; }
|
||||
|
||||
void changeTimeout(uint newTimeout) {
|
||||
m_timeout = newTimeout;
|
||||
if (m_timer.isActive()) {
|
||||
m_timer.stop();
|
||||
m_timer.start(m_timeout, this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void framerateChanged() {}
|
||||
|
||||
protected:
|
||||
Base(QWidget*, uint scopeSize = 7);
|
||||
|
||||
void hideEvent(QHideEvent*);
|
||||
void showEvent(QShowEvent*);
|
||||
void paintEvent(QPaintEvent*);
|
||||
void timerEvent(QTimerEvent*);
|
||||
|
||||
void polishEvent();
|
||||
|
||||
int resizeExponent(int);
|
||||
int resizeForBands(int);
|
||||
virtual void init() {}
|
||||
virtual void transform(Scope&);
|
||||
virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0;
|
||||
virtual void demo(QPainter& p);
|
||||
|
||||
protected:
|
||||
QBasicTimer m_timer;
|
||||
uint m_timeout;
|
||||
FHT* m_fht;
|
||||
EngineBase* m_engine;
|
||||
Scope m_lastScope;
|
||||
int current_chunk_;
|
||||
|
||||
bool new_frame_;
|
||||
bool is_playing_;
|
||||
};
|
||||
|
||||
void interpolate(const Scope&, Scope&);
|
||||
void initSin(Scope&, const uint = 6000);
|
||||
|
||||
} // END namespace Analyzer
|
||||
|
||||
#endif
|
||||
220
src/analyzer/analyzercontainer.cpp
Normal file
220
src/analyzer/analyzercontainer.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
Strawberry Music Player
|
||||
This file was part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include "analyzercontainer.h"
|
||||
#include "blockanalyzer.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
const char* AnalyzerContainer::kSettingsGroup = "Analyzer";
|
||||
const char* AnalyzerContainer::kSettingsFramerate = "framerate";
|
||||
|
||||
// Framerates
|
||||
const int AnalyzerContainer::kLowFramerate = 20;
|
||||
const int AnalyzerContainer::kMediumFramerate = 25;
|
||||
const int AnalyzerContainer::kHighFramerate = 30;
|
||||
const int AnalyzerContainer::kSuperHighFramerate = 60;
|
||||
|
||||
AnalyzerContainer::AnalyzerContainer(QWidget* parent)
|
||||
: QWidget(parent),
|
||||
current_framerate_(kMediumFramerate),
|
||||
context_menu_(new QMenu(this)),
|
||||
context_menu_framerate_(new QMenu(tr("Framerate"), this)),
|
||||
group_(new QActionGroup(this)),
|
||||
group_framerate_(new QActionGroup(this)),
|
||||
mapper_(new QSignalMapper(this)),
|
||||
mapper_framerate_(new QSignalMapper(this)),
|
||||
visualisation_action_(nullptr),
|
||||
double_click_timer_(new QTimer(this)),
|
||||
ignore_next_click_(false),
|
||||
current_analyzer_(nullptr),
|
||||
engine_(nullptr) {
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
setLayout(layout);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Init framerate sub-menu
|
||||
AddFramerate(tr("Low (%1 fps)").arg(kLowFramerate), kLowFramerate);
|
||||
AddFramerate(tr("Medium (%1 fps)").arg(kMediumFramerate), kMediumFramerate);
|
||||
AddFramerate(tr("High (%1 fps)").arg(kHighFramerate), kHighFramerate);
|
||||
AddFramerate(tr("Super high (%1 fps)").arg(kSuperHighFramerate), kSuperHighFramerate);
|
||||
connect(mapper_framerate_, SIGNAL(mapped(int)), SLOT(ChangeFramerate(int)));
|
||||
|
||||
context_menu_->addMenu(context_menu_framerate_);
|
||||
context_menu_->addSeparator();
|
||||
|
||||
AddAnalyzerType<BlockAnalyzer>();
|
||||
|
||||
connect(mapper_, SIGNAL(mapped(int)), SLOT(ChangeAnalyzer(int)));
|
||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, SLOT(DisableAnalyzer()));
|
||||
disable_action_->setCheckable(true);
|
||||
group_->addAction(disable_action_);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
// Visualisation action gets added in SetActions
|
||||
|
||||
double_click_timer_->setSingleShot(true);
|
||||
double_click_timer_->setInterval(250);
|
||||
connect(double_click_timer_, SIGNAL(timeout()), SLOT(ShowPopupMenu()));
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
void AnalyzerContainer::SetActions(QAction* visualisation) {
|
||||
visualisation_action_ = visualisation;
|
||||
context_menu_->addAction(visualisation_action_);
|
||||
}
|
||||
|
||||
void AnalyzerContainer::mouseReleaseEvent(QMouseEvent* e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
if (ignore_next_click_) {
|
||||
ignore_next_click_ = false;
|
||||
}
|
||||
else {
|
||||
// Might be the first click in a double click, so wait a while before
|
||||
// actually doing anything
|
||||
double_click_timer_->start();
|
||||
last_click_pos_ = e->globalPos();
|
||||
}
|
||||
}
|
||||
else if (e->button() == Qt::RightButton) {
|
||||
context_menu_->popup(e->globalPos());
|
||||
}
|
||||
}
|
||||
|
||||
void AnalyzerContainer::ShowPopupMenu() {
|
||||
context_menu_->popup(last_click_pos_);
|
||||
}
|
||||
|
||||
void AnalyzerContainer::mouseDoubleClickEvent(QMouseEvent*) {
|
||||
double_click_timer_->stop();
|
||||
ignore_next_click_ = true;
|
||||
|
||||
if (visualisation_action_) visualisation_action_->trigger();
|
||||
}
|
||||
|
||||
void AnalyzerContainer::wheelEvent(QWheelEvent* e) {
|
||||
emit WheelEvent(e->delta());
|
||||
}
|
||||
|
||||
void AnalyzerContainer::SetEngine(EngineBase* engine) {
|
||||
if (current_analyzer_) current_analyzer_->set_engine(engine);
|
||||
engine_ = engine;
|
||||
}
|
||||
|
||||
void AnalyzerContainer::DisableAnalyzer() {
|
||||
delete current_analyzer_;
|
||||
current_analyzer_ = nullptr;
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
void AnalyzerContainer::ChangeAnalyzer(int id) {
|
||||
QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
|
||||
|
||||
if (!instance) {
|
||||
qLog(Warning) << "Couldn't intialise a new"
|
||||
<< analyzer_types_[id]->className();
|
||||
return;
|
||||
}
|
||||
|
||||
delete current_analyzer_;
|
||||
current_analyzer_ = qobject_cast<Analyzer::Base*>(instance);
|
||||
current_analyzer_->set_engine(engine_);
|
||||
// Even if it is not supposed to happen, I don't want to get a dbz error
|
||||
current_framerate_ = current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
|
||||
current_analyzer_->changeTimeout(1000 / current_framerate_);
|
||||
|
||||
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;
|
||||
current_analyzer_->changeTimeout(1000 / new_framerate);
|
||||
|
||||
// notify the current analyzer that the framerate has changed
|
||||
current_analyzer_->framerateChanged();
|
||||
}
|
||||
SaveFramerate(new_framerate);
|
||||
}
|
||||
|
||||
void AnalyzerContainer::Load() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
// Analyzer
|
||||
QString type = s.value("type", "BlockAnalyzer").toString();
|
||||
if (type.isEmpty()) {
|
||||
DisableAnalyzer();
|
||||
disable_action_->setChecked(true);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < analyzer_types_.count(); ++i) {
|
||||
if (type == analyzer_types_[i]->className()) {
|
||||
ChangeAnalyzer(i);
|
||||
actions_[i]->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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_);
|
||||
group_framerate_->actions()[i]->setChecked(true);
|
||||
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_);
|
||||
}
|
||||
|
||||
void AnalyzerContainer::Save() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
s.setValue("type", current_analyzer_ ? current_analyzer_->metaObject()->className() : QVariant());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
106
src/analyzer/analyzercontainer.h
Normal file
106
src/analyzer/analyzercontainer.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Strawberry Music Player
|
||||
This file was part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef ANALYZERCONTAINER_H
|
||||
#define ANALYZERCONTAINER_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMenu>
|
||||
#include <QSignalMapper>
|
||||
|
||||
#include "analyzerbase.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
|
||||
class AnalyzerContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AnalyzerContainer(QWidget* parent);
|
||||
|
||||
void SetEngine(EngineBase *engine);
|
||||
void SetActions(QAction *visualisation);
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
static const char *kSettingsFramerate;
|
||||
|
||||
signals:
|
||||
void WheelEvent(int delta);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
void mouseDoubleClickEvent(QMouseEvent*);
|
||||
void wheelEvent(QWheelEvent *e);
|
||||
|
||||
private slots:
|
||||
void ChangeAnalyzer(int id);
|
||||
void ChangeFramerate(int new_framerate);
|
||||
void DisableAnalyzer();
|
||||
void ShowPopupMenu();
|
||||
|
||||
private:
|
||||
static const int kLowFramerate;
|
||||
static const int kMediumFramerate;
|
||||
static const int kHighFramerate;
|
||||
static const int kSuperHighFramerate;
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
void SaveFramerate(int framerate);
|
||||
template <typename T>
|
||||
void AddAnalyzerType();
|
||||
void AddFramerate(const QString& name, int framerate);
|
||||
|
||||
private:
|
||||
int current_framerate_; // fps
|
||||
QMenu *context_menu_;
|
||||
QMenu *context_menu_framerate_;
|
||||
QActionGroup *group_;
|
||||
QActionGroup *group_framerate_;
|
||||
QSignalMapper *mapper_;
|
||||
QSignalMapper *mapper_framerate_;
|
||||
|
||||
QList<const QMetaObject*> analyzer_types_;
|
||||
QList<int> framerate_list_;
|
||||
QList<QAction*> actions_;
|
||||
QAction *disable_action_;
|
||||
|
||||
QAction *visualisation_action_;
|
||||
QTimer *double_click_timer_;
|
||||
QPoint last_click_pos_;
|
||||
bool ignore_next_click_;
|
||||
|
||||
Analyzer::Base* current_analyzer_;
|
||||
EngineBase *engine_;
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void AnalyzerContainer::AddAnalyzerType() {
|
||||
int id = analyzer_types_.count();
|
||||
analyzer_types_ << &T::staticMetaObject;
|
||||
|
||||
QAction *action = context_menu_->addAction(tr(T::kName), mapper_, SLOT(map()));
|
||||
group_->addAction(action);
|
||||
mapper_->setMapping(action, id);
|
||||
action->setCheckable(true);
|
||||
actions_ << action;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
417
src/analyzer/blockanalyzer.cpp
Normal file
417
src/analyzer/blockanalyzer.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
// Author: Max Howell <max.howell@methylblue.com>, (C) 2003-5
|
||||
// Mark Kretschmann <markey@web.de>, (C) 2005
|
||||
// Copyright: See COPYING file that comes with this distribution
|
||||
//
|
||||
|
||||
#include "blockanalyzer.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <cstdlib>
|
||||
#include <QPainter>
|
||||
|
||||
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 char* BlockAnalyzer::kName =
|
||||
QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||
|
||||
BlockAnalyzer::BlockAnalyzer(QWidget* parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
m_columns(0) // uint
|
||||
,
|
||||
m_rows(0) // uint
|
||||
,
|
||||
m_y(0) // uint
|
||||
,
|
||||
m_barPixmap(1, 1) // null qpixmaps cause crashes
|
||||
,
|
||||
m_topBarPixmap(WIDTH, HEIGHT),
|
||||
m_scope(MIN_COLUMNS) // Scope
|
||||
,
|
||||
m_store(1 << 8, 0) // vector<uint>
|
||||
,
|
||||
m_fade_bars(FADE_SIZE) // vector<QPixmap>
|
||||
,
|
||||
m_fade_pos(1 << 8, 50) // vector<uint>
|
||||
,
|
||||
m_fade_intensity(1 << 8, 32) // vector<uint>
|
||||
{
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
BlockAnalyzer::~BlockAnalyzer() {}
|
||||
|
||||
void BlockAnalyzer::resizeEvent(QResizeEvent* e) {
|
||||
QWidget::resizeEvent(e);
|
||||
|
||||
m_background = QPixmap(size());
|
||||
canvas_ = QPixmap(size());
|
||||
|
||||
const uint oldRows = m_rows;
|
||||
|
||||
// all is explained in analyze()..
|
||||
//+1 to counter -1 in maxSizes, trust me we need this!
|
||||
m_columns = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS);
|
||||
m_rows = uint(double(height() + 1) / (HEIGHT + 1));
|
||||
|
||||
// this is the y-offset for drawing from the top of the widget
|
||||
m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2;
|
||||
|
||||
m_scope.resize(m_columns);
|
||||
|
||||
if (m_rows != oldRows) {
|
||||
m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
|
||||
|
||||
for (uint i = 0; i < FADE_SIZE; ++i)
|
||||
m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
|
||||
|
||||
m_yscale.resize(m_rows + 1);
|
||||
|
||||
const uint PRE = 1,
|
||||
PRO = 1; // PRE and PRO allow us to restrict the range somewhat
|
||||
|
||||
for (uint z = 0; z < m_rows; ++z)
|
||||
m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO));
|
||||
|
||||
m_yscale[m_rows] = 0;
|
||||
|
||||
determineStep();
|
||||
paletteChange(palette());
|
||||
}
|
||||
|
||||
drawBackground();
|
||||
}
|
||||
|
||||
void BlockAnalyzer::determineStep() {
|
||||
// falltime is dependent on rowcount due to our digital resolution (ie we have
|
||||
// boxes/blocks of pixels)
|
||||
// I calculated the value 30 based on some trial and error
|
||||
|
||||
// the fall time of 30 is too slow on framerates above 50fps
|
||||
const double fallTime = timeout() < 20 ? 20 * m_rows : 30 * m_rows;
|
||||
|
||||
m_step = double(m_rows * timeout()) / fallTime;
|
||||
}
|
||||
|
||||
void BlockAnalyzer::framerateChanged() { // virtual
|
||||
determineStep();
|
||||
}
|
||||
|
||||
void BlockAnalyzer::transform(Analyzer::Scope& s) // pure virtual
|
||||
{
|
||||
for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
|
||||
|
||||
float* front = static_cast<float*>(&s.front());
|
||||
|
||||
m_fht->spectrum(front);
|
||||
m_fht->scale(front, 1.0 / 20);
|
||||
|
||||
// the second half is pretty dull, so only show it if the user has a large
|
||||
// analyzer
|
||||
// by setting to m_scope.size() if large we prevent interpolation of large
|
||||
// analyzers, this is good!
|
||||
s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : m_scope.size());
|
||||
}
|
||||
|
||||
void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
||||
bool new_frame) {
|
||||
// y = 2 3 2 1 0 2
|
||||
// . . . . # .
|
||||
// . . . # # .
|
||||
// # . # # # #
|
||||
// # # # # # #
|
||||
//
|
||||
// visual aid for how this analyzer works.
|
||||
// y represents the number of blanks
|
||||
// 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 }
|
||||
// if it contains 6 elements there are 5 rows in the analyzer
|
||||
|
||||
if (!new_frame) {
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter canvas_painter(&canvas_);
|
||||
|
||||
Analyzer::interpolate(s, m_scope);
|
||||
|
||||
// Paint the background
|
||||
canvas_painter.drawPixmap(0, 0, m_background);
|
||||
|
||||
for (uint y, x = 0; x < m_scope.size(); ++x) {
|
||||
// determine y
|
||||
for (y = 0; m_scope[x] < m_yscale[y]; ++y)
|
||||
;
|
||||
|
||||
// this is opposite to what you'd think, higher than y
|
||||
// means the bar is lower than y (physically)
|
||||
if ((float)y > m_store[x])
|
||||
y = int(m_store[x] += m_step);
|
||||
else
|
||||
m_store[x] = y;
|
||||
|
||||
// if y is lower than m_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 <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) {
|
||||
m_fade_pos[x] = y;
|
||||
m_fade_intensity[x] = FADE_SIZE;
|
||||
}
|
||||
|
||||
if (m_fade_intensity[x] > 0) {
|
||||
const uint offset = --m_fade_intensity[x];
|
||||
const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1));
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y);
|
||||
}
|
||||
|
||||
if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows;
|
||||
|
||||
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing,
|
||||
// m_rows means none are
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(),
|
||||
0, y * (HEIGHT + 1), bar()->width(),
|
||||
bar()->height());
|
||||
}
|
||||
|
||||
for (uint x = 0; x < m_store.size(); ++x)
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1), int(m_store[x]) * (HEIGHT + 1) + m_y, m_topBarPixmap);
|
||||
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
}
|
||||
|
||||
static inline void adjustToLimits(int& b, int& f, uint& amount) {
|
||||
// with a range of 0-255 and maximum adjustment of amount,
|
||||
// maximise the difference between f and b
|
||||
|
||||
if (b < f) {
|
||||
if (b > 255 - f) {
|
||||
amount -= f;
|
||||
f = 0;
|
||||
}
|
||||
else {
|
||||
amount -= (255 - f);
|
||||
f = 255;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (f > 255 - b) {
|
||||
amount -= f;
|
||||
f = 0;
|
||||
}
|
||||
else {
|
||||
amount -= (255 - f);
|
||||
f = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clever contrast function
|
||||
*
|
||||
* It will try to adjust the foreground color such that it contrasts well with
|
||||
*the background
|
||||
* 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) {
|
||||
class OutputOnExit {
|
||||
public:
|
||||
OutputOnExit(const QColor& color) : c(color) {}
|
||||
~OutputOnExit() {
|
||||
int h, s, v;
|
||||
c.getHsv(&h, &s, &v);
|
||||
}
|
||||
|
||||
private:
|
||||
const QColor& c;
|
||||
};
|
||||
|
||||
// hack so I don't have to cast everywhere
|
||||
#define amount static_cast<int>(_amount)
|
||||
// #define STAMP debug() << (QValueList<int>() << fh << fs << fv) << endl;
|
||||
// #define STAMP1( string ) debug() << string << ": " <<
|
||||
// (QValueList<int>() << fh << fs << fv) << endl;
|
||||
// #define STAMP2( string, value ) debug() << string << "=" << value << ":
|
||||
// " << (QValueList<int>() << fh << fs << fv) << endl;
|
||||
|
||||
OutputOnExit allocateOnTheStack(fg);
|
||||
|
||||
int bh, bs, bv;
|
||||
int fh, fs, fv;
|
||||
|
||||
bg.getHsv(&bh, &bs, &bv);
|
||||
fg.getHsv(&fh, &fs, &fv);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
// to allow the colours to be visible and not be shades of grey or black
|
||||
|
||||
// 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" );
|
||||
return fg;
|
||||
else if (dv > amount / 2 && (bv > 125 && fv > 125))
|
||||
// STAMP1( "Sufficient value difference, and hues are
|
||||
// compliemtary" );
|
||||
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;
|
||||
else
|
||||
_amount = 0;
|
||||
}
|
||||
|
||||
// test that there is available value to honor our contrast requirement
|
||||
if (255 - dv < 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
|
||||
|
||||
// see if we need to adjust the hue
|
||||
if (amount > 0) fh += amount; // cycles around;
|
||||
|
||||
// STAMP
|
||||
|
||||
return QColor::fromHsv(fh, fs, fv);
|
||||
}
|
||||
|
||||
// STAMP
|
||||
|
||||
if (fv > bv && bv > amount) return QColor::fromHsv(fh, fs, bv - amount);
|
||||
|
||||
// STAMP
|
||||
|
||||
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";
|
||||
|
||||
return Qt::blue;
|
||||
|
||||
#undef amount
|
||||
// #undef STAMP
|
||||
}
|
||||
|
||||
void BlockAnalyzer::paletteChange(const QPalette&) // virtual
|
||||
{
|
||||
const QColor bg = palette().color(QPalette::Background);
|
||||
const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight));
|
||||
|
||||
m_topBarPixmap.fill(fg);
|
||||
|
||||
const double dr = 15 * double(bg.red() - fg.red()) / (m_rows * 16);
|
||||
const double dg = 15 * double(bg.green() - fg.green()) / (m_rows * 16);
|
||||
const double db = 15 * double(bg.blue() - fg.blue()) / (m_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 < m_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)));
|
||||
|
||||
{
|
||||
const QColor bg = palette().color(QPalette::Background).dark(112);
|
||||
|
||||
// make a complimentary fadebar colour
|
||||
// TODO dark is not always correct, dumbo!
|
||||
int h, s, v;
|
||||
palette().color(QPalette::Background).dark(150).getHsv(&h, &s, &v);
|
||||
const QColor fg(QColor::fromHsv(h + 120, s, v));
|
||||
|
||||
const double dr = fg.red() - bg.red();
|
||||
const double dg = fg.green() - bg.green();
|
||||
const double db = fg.blue() - bg.blue();
|
||||
const int r = bg.red(), g = bg.green(), b = bg.blue();
|
||||
|
||||
// Precalculate all fade-bar pixmaps
|
||||
for (uint y = 0; y < FADE_SIZE; ++y) {
|
||||
m_fade_bars[y].fill(palette().color(QPalette::Background));
|
||||
QPainter f(&m_fade_bars[y]);
|
||||
for (int z = 0; (uint)z < m_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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawBackground();
|
||||
}
|
||||
|
||||
void BlockAnalyzer::drawBackground() {
|
||||
const QColor bg = palette().color(QPalette::Background);
|
||||
const QColor bgdark = bg.dark(112);
|
||||
|
||||
m_background.fill(bg);
|
||||
|
||||
QPainter p(&m_background);
|
||||
for (int x = 0; (uint)x < m_columns; ++x)
|
||||
for (int y = 0; (uint)y < m_rows; ++y)
|
||||
p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT, bgdark);
|
||||
}
|
||||
65
src/analyzer/blockanalyzer.h
Normal file
65
src/analyzer/blockanalyzer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Maintainer: Max Howell <mac.howell@methylblue.com>, (C) 2003-5
|
||||
// Copyright: See COPYING file that comes with this distribution
|
||||
//
|
||||
|
||||
#ifndef BLOCKANALYZER_H
|
||||
#define BLOCKANALYZER_H
|
||||
|
||||
#include "analyzerbase.h"
|
||||
#include <qcolor.h>
|
||||
|
||||
class QResizeEvent;
|
||||
class QMouseEvent;
|
||||
class QPalette;
|
||||
|
||||
/**
|
||||
* @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 char *kName;
|
||||
|
||||
protected:
|
||||
virtual void transform(Analyzer::Scope&);
|
||||
virtual void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame);
|
||||
virtual void resizeEvent(QResizeEvent*);
|
||||
virtual void paletteChange(const QPalette&);
|
||||
virtual void framerateChanged();
|
||||
|
||||
void drawBackground();
|
||||
void determineStep();
|
||||
|
||||
private:
|
||||
QPixmap* bar() { return &m_barPixmap; }
|
||||
|
||||
uint m_columns, m_rows; // number of rows and columns of blocks
|
||||
uint m_y; // y-offset from top of widget
|
||||
QPixmap m_barPixmap;
|
||||
QPixmap m_topBarPixmap;
|
||||
QPixmap m_background;
|
||||
QPixmap canvas_;
|
||||
Analyzer::Scope m_scope; // so we don't create a vector every frame
|
||||
std::vector<float> m_store; // current bar heights
|
||||
std::vector<float> m_yscale;
|
||||
|
||||
// FIXME why can't I namespace these? c++ issue?
|
||||
std::vector<QPixmap> m_fade_bars;
|
||||
std::vector<uint> m_fade_pos;
|
||||
std::vector<int> m_fade_intensity;
|
||||
|
||||
float m_step; // rows to fall per frame
|
||||
};
|
||||
|
||||
#endif
|
||||
203
src/analyzer/fht.cpp
Normal file
203
src/analyzer/fht.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
// 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$
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
if (n > 3) {
|
||||
m_buf = new float[m_num];
|
||||
m_tab = new float[m_num * 2];
|
||||
makeCasTable();
|
||||
}
|
||||
}
|
||||
|
||||
FHT::~FHT() {
|
||||
delete[] m_buf;
|
||||
delete[] m_tab;
|
||||
delete[] m_log;
|
||||
}
|
||||
|
||||
void FHT::makeCasTable(void) {
|
||||
float d, *costab, *sintab;
|
||||
int ul, ndiv2 = m_num / 2;
|
||||
|
||||
for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num; ul++) {
|
||||
d = M_PI * ul / ndiv2;
|
||||
*costab = *sintab = cos(d);
|
||||
|
||||
costab += 2, sintab += 2;
|
||||
if (sintab > m_tab + m_num * 2) sintab = m_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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
*r = j >= n ? n - 1 : j;
|
||||
}
|
||||
}
|
||||
semiLogSpectrum(p);
|
||||
*out++ = *p = *p / 100;
|
||||
for (k = i = 1, r = m_log; i < n; i++) {
|
||||
j = *r++;
|
||||
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));
|
||||
*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);
|
||||
}
|
||||
|
||||
void FHT::power(float* p) {
|
||||
power2(p);
|
||||
for (int i = 0; i < (m_num / 2); i++) *p++ *= .5;
|
||||
}
|
||||
|
||||
void FHT::power2(float* p) {
|
||||
int i;
|
||||
float* q;
|
||||
_transform(p, m_num, 0);
|
||||
|
||||
*p = (*p * *p), *p += *p, p++;
|
||||
|
||||
for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q)
|
||||
*p = (*p * *p) + (*q * *q), p++;
|
||||
}
|
||||
|
||||
void FHT::transform(float* p) {
|
||||
if (m_num == 8)
|
||||
transform8(p);
|
||||
else
|
||||
_transform(p, m_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;
|
||||
|
||||
a = *p++, b = *p++, c = *p++, d = *p++;
|
||||
e = *p++, f = *p++, g = *p++, h = *p;
|
||||
b_f2 = (b - f) * M_SQRT2;
|
||||
d_h2 = (d - h) * M_SQRT2;
|
||||
|
||||
a_c_eg = a - c - e + g;
|
||||
a_ce_g = a - c + e - g;
|
||||
ac_e_g = a + c - e - g;
|
||||
aceg = a + c + e + g;
|
||||
|
||||
b_df_h = b - d + f - h;
|
||||
bdfh = b + d + f + h;
|
||||
|
||||
*p = a_c_eg - d_h2;
|
||||
*--p = a_ce_g - b_df_h;
|
||||
*--p = ac_e_g - b_f2;
|
||||
*--p = aceg - bdfh;
|
||||
*--p = a_c_eg + d_h2;
|
||||
*--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) {
|
||||
|
||||
if (n == 8) {
|
||||
transform8(p + k);
|
||||
return;
|
||||
}
|
||||
|
||||
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++)
|
||||
*t1++ = *pp++, *t2++ = *pp++;
|
||||
|
||||
memcpy(p + k, m_buf, sizeof(float) * n);
|
||||
|
||||
_transform(p, ndiv2, k);
|
||||
_transform(p, ndiv2, k + ndiv2);
|
||||
|
||||
j = m_num / ndiv2 - 1;
|
||||
t1 = m_buf;
|
||||
t2 = t1 + ndiv2;
|
||||
t3 = p + k + ndiv2;
|
||||
ptab = m_tab;
|
||||
pp = p + k;
|
||||
|
||||
a = *ptab++ * *t3++;
|
||||
a += *ptab * *pp;
|
||||
ptab += j;
|
||||
|
||||
*t1++ = *pp + a;
|
||||
*t2++ = *pp++ - a;
|
||||
|
||||
for (i = 1, t4 = p + k + n; i < ndiv2; i++, ptab += j) {
|
||||
a = *ptab++ * *t3++;
|
||||
a += *ptab * *--t4;
|
||||
|
||||
*t1++ = *pp + a;
|
||||
*t2++ = *pp++ - a;
|
||||
}
|
||||
memcpy(p + k, m_buf, sizeof(float) * n);
|
||||
}
|
||||
118
src/analyzer/fht.h
Normal file
118
src/analyzer/fht.h
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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$
|
||||
|
||||
#ifndef FHT_H
|
||||
#define FHT_H
|
||||
|
||||
/**
|
||||
* Implementation of the Hartley Transform after Bracewell's discrete
|
||||
* algorithm. The algorithm is subject to US patent No. 4,646,256 (1987)
|
||||
* but was put into public domain by the Board of Trustees of Stanford
|
||||
* University in 1994 and is now freely available[1].
|
||||
*
|
||||
* [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;
|
||||
|
||||
/**
|
||||
* Create a table of "cas" (cosine and sine) values.
|
||||
* Has only to be done in the constructor and saves from
|
||||
* calculating the same values over and over while transforming.
|
||||
*/
|
||||
void makeCasTable();
|
||||
|
||||
/**
|
||||
* Recursive in-place Hartley transform. For internal use only!
|
||||
*/
|
||||
void _transform(float*, int, int);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Prepare transform for data sets with @f$2^n@f$ numbers, whereby @f$n@f$
|
||||
* should be at least 3. Values of more than 3 need a trigonometry table.
|
||||
* @see makeCasTable()
|
||||
*/
|
||||
FHT(int);
|
||||
|
||||
~FHT();
|
||||
inline int sizeExp() const { return m_exp2; }
|
||||
inline int size() const { return m_num; }
|
||||
float* copy(float*, float*);
|
||||
float* clear(float*);
|
||||
void scale(float*, float);
|
||||
|
||||
/**
|
||||
* Exponentially Weighted Moving Average (EWMA) filter.
|
||||
* @param d is the filtered data.
|
||||
* @param s is fresh input.
|
||||
* @param w is the weighting factor.
|
||||
*/
|
||||
void ewma(float* d, float* s, float w);
|
||||
|
||||
/**
|
||||
* Logarithmic audio spectrum. Maps semi-logarithmic spectrum
|
||||
* to logarithmic frequency scale, interpolates missing values.
|
||||
* A logarithmic index map is calculated at the first run only.
|
||||
* @param p is the input array.
|
||||
* @param out is the spectrum.
|
||||
*/
|
||||
void logSpectrum(float* out, float* p);
|
||||
|
||||
/**
|
||||
* Semi-logarithmic audio spectrum.
|
||||
*/
|
||||
void semiLogSpectrum(float*);
|
||||
|
||||
/**
|
||||
* Fourier spectrum.
|
||||
*/
|
||||
void spectrum(float*);
|
||||
|
||||
/**
|
||||
* Calculates a mathematically correct FFT power spectrum.
|
||||
* If further scaling is applied later, use power2 instead
|
||||
* and factor the 0.5 in the final scaling factor.
|
||||
* @see FHT::power2()
|
||||
*/
|
||||
void power(float*);
|
||||
|
||||
/**
|
||||
* Calculates an FFT power spectrum with doubled values as a
|
||||
* result. The values need to be multiplied by 0.5 to be exact.
|
||||
* Note that you only get @f$2^{n-1}@f$ power values for a data set
|
||||
* of @f$2^n@f$ input values. This is the fastest transform.
|
||||
* @see FHT::power()
|
||||
*/
|
||||
void power2(float*);
|
||||
|
||||
/**
|
||||
* Discrete Hartley transform of data sets with 8 values.
|
||||
*/
|
||||
void transform8(float*);
|
||||
|
||||
void transform(float*);
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user