First commit for SailTuner
Intergration of elements: - base from SailDBMeter - audio filters from personnal repos
This commit is contained in:
commit
d41035ecc2
11 changed files with 453 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.swo
|
||||||
|
*.swp
|
18
Tuner.pro
Normal file
18
Tuner.pro
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
QT += qml quick gui multimedia
|
||||||
|
TARGET = Tuner
|
||||||
|
|
||||||
|
CONFIG += c++11
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
src/desktop.cpp \
|
||||||
|
src/Tuner.cpp \
|
||||||
|
src/audio/LinearFilter.cpp \
|
||||||
|
src/audio/ZeroCross.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
src/Tuner.hpp \
|
||||||
|
src/audio/LinearFilter.hpp \
|
||||||
|
src/audio/ZeroCross.hpp
|
||||||
|
|
||||||
|
RESOURCES += \
|
||||||
|
qml/desktop.qrc
|
4
qml/Desktop.qml
Normal file
4
qml/Desktop.qml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import QtQuick 2.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
}
|
5
qml/desktop.qrc
Normal file
5
qml/desktop.qrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource prefix="/qml/">
|
||||||
|
<file>Desktop.qml</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
98
src/Tuner.cpp
Normal file
98
src/Tuner.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "Tuner.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// high 10hz / 16k
|
||||||
|
static double a10[] = { 1 , -2.99214602, 2.98432286, -0.99217678 };
|
||||||
|
static double b10[] = { 0.99608071, -2.98824212, 2.98824212, -0.99608071 };
|
||||||
|
|
||||||
|
Tuner::Tuner()
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
high_filter = new LinearFilter<int16_t>(3, a10, b10);
|
||||||
|
|
||||||
|
ZeroCross<int16_t>::Config cross_config({rate, defaultNbFrame, defaultFreqMin, defaultFreqMax});
|
||||||
|
cross = new ZeroCross<int16_t>(cross_config);
|
||||||
|
|
||||||
|
settings.setCodec("audio/PCM");
|
||||||
|
settings.setChannelCount(1);
|
||||||
|
settings.setSampleRate(rate);
|
||||||
|
|
||||||
|
recorder = new QAudioRecorder(this);
|
||||||
|
recorder->setAudioInput("pulseaudio:");
|
||||||
|
recorder->setAudioSettings(settings);
|
||||||
|
|
||||||
|
QUrl url = QString("/dev/null");
|
||||||
|
recorder->setOutputLocation(url);
|
||||||
|
|
||||||
|
probe = new QAudioProbe(this);
|
||||||
|
connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(AudioCb(QAudioBuffer)));
|
||||||
|
probe->setSource(recorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuner::~Tuner()
|
||||||
|
{
|
||||||
|
delete high_filter;
|
||||||
|
delete cross;
|
||||||
|
delete recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuner::Start()
|
||||||
|
{
|
||||||
|
high_filter->Clear();
|
||||||
|
cross->Clear();
|
||||||
|
recorder->record();
|
||||||
|
running = true;
|
||||||
|
runningChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuner::Stop()
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
recorder->stop();
|
||||||
|
runningChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuner::ComputeFrame(int16_t v)
|
||||||
|
{
|
||||||
|
v = (*high_filter)(v);
|
||||||
|
(*cross)(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuner::AudioCb(const QAudioBuffer &buffer)
|
||||||
|
{
|
||||||
|
const int16_t *ptr = buffer.constData<int16_t>();
|
||||||
|
int nbFrame = buffer.sampleCount();
|
||||||
|
|
||||||
|
while (nbFrame--) ComputeFrame(*ptr++);
|
||||||
|
|
||||||
|
if (freq != cross->Freq()) {
|
||||||
|
freq = cross->Freq();
|
||||||
|
freqChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Tuner::GetRunning()
|
||||||
|
{
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuner::SetRunning(bool r)
|
||||||
|
{
|
||||||
|
if (running == r) return;
|
||||||
|
|
||||||
|
if (r) Start();
|
||||||
|
else Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
double Tuner::GetFreq()
|
||||||
|
{
|
||||||
|
return freq;
|
||||||
|
}
|
47
src/Tuner.hpp
Normal file
47
src/Tuner.hpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#include <QAudioRecorder>
|
||||||
|
#include <QAudioProbe>
|
||||||
|
|
||||||
|
#include "audio/LinearFilter.hpp"
|
||||||
|
#include "audio/ZeroCross.hpp"
|
||||||
|
|
||||||
|
class Tuner : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(bool running READ GetRunning WRITE SetRunning NOTIFY runningChanged)
|
||||||
|
Q_PROPERTY(double freq READ GetFreq NOTIFY freqChanged)
|
||||||
|
|
||||||
|
private:
|
||||||
|
QAudioRecorder *recorder;
|
||||||
|
QAudioEncoderSettings settings;
|
||||||
|
QAudioProbe *probe;
|
||||||
|
|
||||||
|
LinearFilter<int16_t> *high_filter;
|
||||||
|
ZeroCross<int16_t> *cross;
|
||||||
|
|
||||||
|
bool running;
|
||||||
|
double freq;
|
||||||
|
|
||||||
|
static const int rate = 16000;
|
||||||
|
static const int defaultNbFrame = 1024;
|
||||||
|
static const int defaultFreqMin = 50;
|
||||||
|
static const int defaultFreqMax = 2000;
|
||||||
|
|
||||||
|
inline void ComputeFrame(int16_t v);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void AudioCb(const QAudioBuffer &buffer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Tuner();
|
||||||
|
~Tuner();
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
bool GetRunning();
|
||||||
|
void SetRunning(bool r);
|
||||||
|
double GetFreq();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void runningChanged();
|
||||||
|
void freqChanged();
|
||||||
|
};
|
64
src/audio/LinearFilter.cpp
Normal file
64
src/audio/LinearFilter.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "LinearFilter.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
template<typename A> LinearFilter<A>::LinearFilter(int order, double *a, double *b)
|
||||||
|
{
|
||||||
|
assert(order > 0);
|
||||||
|
assert(a);
|
||||||
|
assert(b);
|
||||||
|
assert(a[0]);
|
||||||
|
|
||||||
|
this->a = a;
|
||||||
|
this->b = b;
|
||||||
|
this->order = order;
|
||||||
|
|
||||||
|
backx = new double[order];
|
||||||
|
backy = new double[order];
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A> LinearFilter<A>::~LinearFilter()
|
||||||
|
{
|
||||||
|
delete [] backx;
|
||||||
|
delete [] backy;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A> void LinearFilter<A>::Clear()
|
||||||
|
{
|
||||||
|
fill(backx, backx + order, 0);
|
||||||
|
fill(backy, backy + order, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A> A LinearFilter<A>::operator() (A x)
|
||||||
|
{
|
||||||
|
double y = (double) x * b[0];
|
||||||
|
|
||||||
|
for (int i = 1; i <= order; i++) {
|
||||||
|
y += (double) b[i] * backx[i-1];
|
||||||
|
y -= (double) a[i] * backy[i-1];
|
||||||
|
}
|
||||||
|
y /= a[0];
|
||||||
|
|
||||||
|
for (int i = order - 1; i > 0; i--) {
|
||||||
|
backx[i] = backx[i-1];
|
||||||
|
backy[i] = backy[i-1];
|
||||||
|
}
|
||||||
|
backx[0] = x;
|
||||||
|
backy[0] = y;
|
||||||
|
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A> void LinearFilter<A>::operator() (A *ptr, int nbFrame)
|
||||||
|
{
|
||||||
|
while(nbFrame--) {
|
||||||
|
*ptr = (*this)(*ptr);
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template class LinearFilter<int16_t>;
|
21
src/audio/LinearFilter.hpp
Normal file
21
src/audio/LinearFilter.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef LINEARFILTER_H
|
||||||
|
#define LINEARFILTER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
template<typename A> class LinearFilter {
|
||||||
|
private:
|
||||||
|
int order;
|
||||||
|
double *a, *b;
|
||||||
|
double *backx, *backy;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LinearFilter(int order, double *a, double *b);
|
||||||
|
~LinearFilter();
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
A operator() (A x);
|
||||||
|
void operator() (A *ptr, int nbFrame);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
146
src/audio/ZeroCross.cpp
Normal file
146
src/audio/ZeroCross.cpp
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#include "ZeroCross.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/// Maximum rest of division to be a multiple of
|
||||||
|
#define EPS_DIVISOR 0.1
|
||||||
|
/// Consider it is a correct pattern to avoid multiples of
|
||||||
|
#define CORRECT_DEVIATION 0.05
|
||||||
|
|
||||||
|
// local functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return average and standart deviation of pattern approx. interval in values
|
||||||
|
*/
|
||||||
|
pair<double,double> IsPattern(const vector<double> &values, double interval)
|
||||||
|
{
|
||||||
|
double sum = 0, deviation = 0, current = 0;
|
||||||
|
int nb = 0;
|
||||||
|
double diff = interval;
|
||||||
|
|
||||||
|
for (double v : values) {
|
||||||
|
if (fabs(diff - v) < fabs(diff)) {
|
||||||
|
current += v;
|
||||||
|
diff = interval - current;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sum += current;
|
||||||
|
deviation += diff * diff;
|
||||||
|
current = 0;
|
||||||
|
diff = interval;
|
||||||
|
nb++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nb < 2) return pair<double,double>(0,0);
|
||||||
|
sum /= nb;
|
||||||
|
deviation = sqrt(deviation) / nb;
|
||||||
|
return pair<double,double>(sum, deviation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find best matching pattern beetween min and max
|
||||||
|
* from an integer list
|
||||||
|
*
|
||||||
|
* @return best interval pattern, standart deviation for best pattern
|
||||||
|
*/
|
||||||
|
pair<double,double> FindPattern(const vector<double> &values, double pattern_min, double pattern_max)
|
||||||
|
{
|
||||||
|
auto best = pair<double,double>(0,0);
|
||||||
|
double interval = 0;
|
||||||
|
|
||||||
|
for (double v : values) {
|
||||||
|
interval += v;
|
||||||
|
if (interval < pattern_min) continue;
|
||||||
|
if (interval > pattern_max) break;
|
||||||
|
|
||||||
|
auto res = IsPattern(values, interval);
|
||||||
|
// cout << " " << res.first << " " << res.second << endl;
|
||||||
|
if (res.first && (res.second < best.second || best.first == 0)) {
|
||||||
|
if (best.first && best.second < CORRECT_DEVIATION) {
|
||||||
|
double div = res.first / best.first;
|
||||||
|
// it is a multiple of previous
|
||||||
|
if (fabs(div - round(div)) < EPS_DIVISOR) continue;
|
||||||
|
// cout << "... " << div - floor(div) << endl;
|
||||||
|
}
|
||||||
|
best = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best.second > CORRECT_DEVIATION) return pair<double,double>(0,0);
|
||||||
|
cout << " -> " << best.first << " " << best.second << " ";
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Methods implementation
|
||||||
|
|
||||||
|
template<typename A> ZeroCross<A>::ZeroCross(const Config &config)
|
||||||
|
{
|
||||||
|
rate = config.rate;
|
||||||
|
nb_frame_to_analyse = config.nb_frame;
|
||||||
|
freq = 0;
|
||||||
|
sample_max = (double) rate / config.tonie_min;
|
||||||
|
sample_min = (double) rate / config.tonie_max;
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename sample_t> void ZeroCross<sample_t>::Clear()
|
||||||
|
{
|
||||||
|
nb_frame_current = -1;
|
||||||
|
nb_frame_analysed = 0;
|
||||||
|
freq = 0;
|
||||||
|
last_zero = false;
|
||||||
|
pattern.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename sample_t> void ZeroCross<sample_t>::ComputeFrame(sample_t x)
|
||||||
|
{
|
||||||
|
double delta;
|
||||||
|
|
||||||
|
// count frame only if ever crossed
|
||||||
|
if (nb_frame_current != -1) nb_frame_current++;
|
||||||
|
// get only from negative to positive
|
||||||
|
if (x > 0 && last_sample < 0) {
|
||||||
|
if (last_zero) delta = 1;
|
||||||
|
else delta = (double) x / (x - last_sample);
|
||||||
|
|
||||||
|
if (nb_frame_current > 0) {
|
||||||
|
nb_frame_current -= delta;
|
||||||
|
pattern.push_back(nb_frame_current);
|
||||||
|
//cout << nb_frame_current << " ";
|
||||||
|
}
|
||||||
|
nb_frame_current = delta;
|
||||||
|
}
|
||||||
|
// drop 0 values to know sign change
|
||||||
|
if (x) last_sample = x, last_zero = false;
|
||||||
|
else last_zero = true;
|
||||||
|
// compute if window suffisent
|
||||||
|
if (nb_frame_analysed++ == nb_frame_to_analyse) {
|
||||||
|
auto res = FindPattern(pattern, sample_min, sample_max);
|
||||||
|
if (res.first) freq = rate / res.first;
|
||||||
|
else freq = 0;
|
||||||
|
nb_frame_analysed = 0;
|
||||||
|
pattern.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename sample_t> double ZeroCross<sample_t>::operator() (sample_t *ptr, int nbFrame)
|
||||||
|
{
|
||||||
|
while(nbFrame--) ComputeFrame(*ptr++);
|
||||||
|
return freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename sample_t> void ZeroCross<sample_t>::operator() (sample_t v)
|
||||||
|
{
|
||||||
|
ComputeFrame(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename sample_t> double ZeroCross<sample_t>::Freq()
|
||||||
|
{
|
||||||
|
return freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
// instanciations
|
||||||
|
template class ZeroCross<int16_t>;
|
34
src/audio/ZeroCross.hpp
Normal file
34
src/audio/ZeroCross.hpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef ZEROCROSS_H
|
||||||
|
#define ZEROCROSS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
template<typename sample_t> class ZeroCross {
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
uint16_t rate;
|
||||||
|
int nb_frame;
|
||||||
|
int tonie_min, tonie_max;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
int rate, nb_frame_to_analyse, nb_frame_analysed, diff_x;
|
||||||
|
double nb_frame_current;
|
||||||
|
int16_t last_sample;
|
||||||
|
double freq, sample_min, sample_max;
|
||||||
|
bool last_zero;
|
||||||
|
std::vector<double> pattern;
|
||||||
|
|
||||||
|
inline void ComputeFrame(sample_t x);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ZeroCross(const Config &config);
|
||||||
|
double operator() (sample_t *ptr, int nbFrame);
|
||||||
|
void operator() (sample_t v);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
double Freq();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
14
src/desktop.cpp
Normal file
14
src/desktop.cpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QQuickView>
|
||||||
|
#include <QtQml>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// qmlRegisterType<DBMeter>("LJDBMeter", 1, 0, "DBMeter");
|
||||||
|
|
||||||
|
QGuiApplication app(argc, argv);
|
||||||
|
QQuickView view;
|
||||||
|
view.setSource(QUrl("qrc:///qml/Desktop.qml"));
|
||||||
|
view.show();
|
||||||
|
return app.exec();
|
||||||
|
}
|
Loading…
Reference in a new issue