Tuner: worker thread for analyse (don't work)
This commit is contained in:
parent
e75e79bb2d
commit
5a341979df
12 changed files with 507 additions and 299 deletions
|
@ -1,13 +1,14 @@
|
||||||
QT += qml quick gui multimedia dbus
|
QT += qml quick gui multimedia dbus
|
||||||
TARGET = Tuner
|
TARGET = Tuner
|
||||||
|
|
||||||
CONFIG += c++11
|
CONFIG += c++11 debug
|
||||||
|
|
||||||
# PKGCONFIG += libpulse
|
# PKGCONFIG += libpulse
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/desktop.cpp \
|
src/desktop.cpp \
|
||||||
src/Tuner.cpp \
|
src/Tuner.cpp \
|
||||||
|
src/TunerWorker.cpp \
|
||||||
src/audio/LinearFilter.cpp \
|
src/audio/LinearFilter.cpp \
|
||||||
src/audio/ZeroCross.cpp \
|
src/audio/ZeroCross.cpp \
|
||||||
src/scale/Scale.cpp \
|
src/scale/Scale.cpp \
|
||||||
|
@ -15,6 +16,7 @@ SOURCES += \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/Tuner.hpp \
|
src/Tuner.hpp \
|
||||||
|
src/TunerWorker.hpp \
|
||||||
src/audio/LinearFilter.hpp \
|
src/audio/LinearFilter.hpp \
|
||||||
src/audio/ZeroCross.hpp \
|
src/audio/ZeroCross.hpp \
|
||||||
src/scale/Scale.hpp \
|
src/scale/Scale.hpp \
|
||||||
|
|
|
@ -13,6 +13,7 @@ RESOURCES += \
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/sailfish.cpp \
|
src/sailfish.cpp \
|
||||||
src/Tuner.cpp \
|
src/Tuner.cpp \
|
||||||
|
src/TunerWorker.cpp \
|
||||||
src/audio/LinearFilter.cpp \
|
src/audio/LinearFilter.cpp \
|
||||||
src/audio/ZeroCross.cpp \
|
src/audio/ZeroCross.cpp \
|
||||||
src/scale/Scale.cpp \
|
src/scale/Scale.cpp \
|
||||||
|
@ -20,6 +21,7 @@ SOURCES += \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/Tuner.hpp \
|
src/Tuner.hpp \
|
||||||
|
src/TunerWorker.hpp \
|
||||||
src/audio/LinearFilter.hpp \
|
src/audio/LinearFilter.hpp \
|
||||||
src/audio/ZeroCross.hpp \
|
src/audio/ZeroCross.hpp \
|
||||||
src/scale/Scale.hpp \
|
src/scale/Scale.hpp \
|
||||||
|
|
286
src/Tuner.cpp
286
src/Tuner.cpp
|
@ -19,229 +19,57 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDBusConnection>
|
|
||||||
#include <QDBusInterface>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include "Tuner.hpp"
|
#include "Tuner.hpp"
|
||||||
|
|
||||||
using namespace std;
|
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 };
|
|
||||||
|
|
||||||
const char * Tuner::filename_record = NULL;
|
|
||||||
|
|
||||||
// function to prevent screen blank
|
|
||||||
|
|
||||||
static void blank_prevent(bool prevent)
|
|
||||||
{
|
|
||||||
cerr << __func__ << endl;
|
|
||||||
QDBusConnection system = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "system");
|
|
||||||
QDBusInterface interface("com.nokia.mce", "/com/nokia/mce/request", "com.nokia.mce.request", system);
|
|
||||||
|
|
||||||
if (prevent) {
|
|
||||||
interface.call(QLatin1String("req_display_blanking_pause"));
|
|
||||||
} else {
|
|
||||||
interface.call(QLatin1String("req_display_cancel_blanking_pause"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tuner::Tuner()
|
Tuner::Tuner()
|
||||||
{
|
{
|
||||||
running = false;
|
running = false;
|
||||||
freq = deviation = 0;
|
freq = deviation = 0;
|
||||||
note = octave = 0;
|
note = octave = 0;
|
||||||
found = false;
|
found = false;
|
||||||
count_found = count_not_found = 0;
|
la = Scale::defaultLa;
|
||||||
nb_sample_running = 0;
|
|
||||||
note_found = octave_found = -1;
|
|
||||||
|
|
||||||
if (filename_record) file_record.open(filename_record);
|
worker = new TunerWorker();
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
scale = new Scale();
|
|
||||||
|
|
||||||
temperaments = new Temperaments(":/data");
|
temperaments = new Temperaments(":/data");
|
||||||
|
|
||||||
if (temperaments->SetTemperament(0)) {
|
if (temperaments->SetTemperament(0)) {
|
||||||
scale->SetNotesFrequencies(temperaments->NotesFrequencies());
|
worker->SetNotesFrequencies(temperaments->NotesFrequencies());
|
||||||
}
|
|
||||||
else {
|
|
||||||
scale->ConstructEqualTemperament();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.setCodec("audio/PCM");
|
worker->moveToThread(&workerThread);
|
||||||
settings.setChannelCount(1);
|
|
||||||
settings.setSampleRate(rate);
|
|
||||||
|
|
||||||
recorder = new QAudioRecorder(this);
|
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
|
||||||
recorder->setAudioInput("pulseaudio:");
|
connect(&workerThread, &QThread::started, worker, &TunerWorker::Entry);
|
||||||
recorder->setAudioSettings(settings);
|
connect(this, &Tuner::quit, worker, &TunerWorker::Quit, Qt::DirectConnection);
|
||||||
|
|
||||||
QUrl url = QString("/dev/null");
|
connect(this, &Tuner::start, worker, &TunerWorker::Start);
|
||||||
recorder->setOutputLocation(url);
|
connect(this, &Tuner::stop, worker, &TunerWorker::Stop);
|
||||||
|
connect(this, &Tuner::setNotesFrequencies, worker, &TunerWorker::SetNotesFrequencies);
|
||||||
|
connect(this, &Tuner::setLa, worker, &TunerWorker::SetLa);
|
||||||
|
|
||||||
probe = new QAudioProbe(this);
|
connect(worker, &TunerWorker::resultFound, this, &Tuner::ResultFound);
|
||||||
connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this, SLOT(AudioCb(QAudioBuffer)));
|
connect(worker, &TunerWorker::resultNotFound, this, &Tuner::ResultNotFound);
|
||||||
probe->setSource(recorder);
|
|
||||||
|
workerThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuner::~Tuner()
|
Tuner::~Tuner()
|
||||||
{
|
{
|
||||||
if (filename_record && file_record.is_open()) file_record.close();
|
quit();
|
||||||
delete high_filter;
|
// workerThread.quit();
|
||||||
delete cross;
|
workerThread.wait(10);
|
||||||
delete recorder;
|
|
||||||
delete probe;
|
|
||||||
delete scale;
|
|
||||||
delete temperaments;
|
delete temperaments;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuner::Start()
|
|
||||||
{
|
|
||||||
cerr << __func__ << endl;
|
|
||||||
count_found = count_not_found = 0;
|
|
||||||
nb_sample_running = 0;
|
|
||||||
note_found = octave_found = -1;
|
|
||||||
ResetDeviation();
|
|
||||||
blank_prevent(true);
|
|
||||||
high_filter->Clear();
|
|
||||||
cross->Clear();
|
|
||||||
recorder->record();
|
|
||||||
running = true;
|
|
||||||
runningChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::Stop()
|
|
||||||
{
|
|
||||||
cerr << __func__ << endl;
|
|
||||||
running = false;
|
|
||||||
recorder->stop();
|
|
||||||
runningChanged();
|
|
||||||
blank_prevent(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>();
|
|
||||||
const int nbFrame = buffer.sampleCount();
|
|
||||||
AudioAnalyse(ptr, nbFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::ResetDeviation()
|
|
||||||
{
|
|
||||||
// reset deviation values
|
|
||||||
nb_deviation = 0;
|
|
||||||
deviation_start = 0;
|
|
||||||
deviation_sum = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::UpdateDeviation(double d)
|
|
||||||
{
|
|
||||||
if (nb_deviation == nbDeviationValues) {
|
|
||||||
deviation_sum -= deviation_values[deviation_start];
|
|
||||||
deviation_start = (deviation_start + 1) % nbDeviationValues;
|
|
||||||
nb_deviation--;
|
|
||||||
}
|
|
||||||
deviation_values[(deviation_start + nb_deviation) % nbDeviationValues] = d;
|
|
||||||
nb_deviation++;
|
|
||||||
deviation_sum += d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::SetFound(int n, int o, double d)
|
|
||||||
{
|
|
||||||
if (n != note_found || o != octave_found) {
|
|
||||||
note_found = n;
|
|
||||||
octave_found = o;
|
|
||||||
count_found = 0;
|
|
||||||
SetNotFound();
|
|
||||||
|
|
||||||
ResetDeviation();
|
|
||||||
UpdateDeviation(d);
|
|
||||||
}
|
|
||||||
else if (count_found++ >= nbConfirm) {
|
|
||||||
count_not_found = 0;
|
|
||||||
if (!found) {
|
|
||||||
found = true;
|
|
||||||
foundChanged();
|
|
||||||
}
|
|
||||||
if (note != n) {
|
|
||||||
note = n;
|
|
||||||
noteChanged();
|
|
||||||
}
|
|
||||||
if (octave != o) {
|
|
||||||
octave = o;
|
|
||||||
octaveChanged();
|
|
||||||
}
|
|
||||||
UpdateDeviation(d);
|
|
||||||
deviation = deviation_sum / nb_deviation;
|
|
||||||
deviationChanged();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
UpdateDeviation(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::SetNotFound()
|
|
||||||
{
|
|
||||||
if (count_not_found++ >= nbDefect) {
|
|
||||||
count_found = 0;
|
|
||||||
if (found) {
|
|
||||||
found = false;
|
|
||||||
ResetDeviation();
|
|
||||||
foundChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tuner::AudioAnalyse(const int16_t *ptr, int nb_frame)
|
|
||||||
{
|
|
||||||
nb_sample_running += nb_frame;
|
|
||||||
|
|
||||||
// record in file is needed
|
|
||||||
if (filename_record && file_record.is_open()) file_record.write((char*) ptr, nb_frame * sizeof(int16_t));
|
|
||||||
|
|
||||||
// compute every audio frame
|
|
||||||
while (nb_frame--) ComputeFrame(*ptr++);
|
|
||||||
|
|
||||||
// update frequency
|
|
||||||
if (freq != cross->Freq()) {
|
|
||||||
freq = cross->Freq();
|
|
||||||
freqChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// find note, octave, deviation
|
|
||||||
if (freq) {
|
|
||||||
int n, o = 0;
|
|
||||||
double d = 0;
|
|
||||||
n = scale->FindNote(freq, o, d);
|
|
||||||
SetFound(n, o, d);
|
|
||||||
}
|
|
||||||
else { // no freq
|
|
||||||
SetNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent screen blanking
|
|
||||||
if (nb_sample_running >= nbSamplePreventRunning && running) {
|
|
||||||
nb_sample_running = 0;
|
|
||||||
blank_prevent(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tuner::GetRunning()
|
bool Tuner::GetRunning()
|
||||||
{
|
{
|
||||||
return running;
|
return running;
|
||||||
|
@ -251,8 +79,11 @@ void Tuner::SetRunning(bool r)
|
||||||
{
|
{
|
||||||
if (running == r) return;
|
if (running == r) return;
|
||||||
|
|
||||||
if (r) Start();
|
running = r;
|
||||||
else Stop();
|
if (r) start();
|
||||||
|
else stop();
|
||||||
|
|
||||||
|
runningChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
double Tuner::GetFreq()
|
double Tuner::GetFreq()
|
||||||
|
@ -275,16 +106,23 @@ int Tuner::GetOctave()
|
||||||
return octave;
|
return octave;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Tuner::GetNoteName()
|
|
||||||
{
|
|
||||||
return scale->NoteName(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tuner::GetFound()
|
bool Tuner::GetFound()
|
||||||
{
|
{
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tuner::SetLa(double la)
|
||||||
|
{
|
||||||
|
this->la = la;
|
||||||
|
setLa(la);
|
||||||
|
laChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
double Tuner::GetLa()
|
||||||
|
{
|
||||||
|
return la;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int Tuner::GetTemperamentIndex()
|
unsigned int Tuner::GetTemperamentIndex()
|
||||||
{
|
{
|
||||||
return temperaments->GetCurrentIndex();
|
return temperaments->GetCurrentIndex();
|
||||||
|
@ -293,7 +131,7 @@ unsigned int Tuner::GetTemperamentIndex()
|
||||||
void Tuner::SetTemperamentIndex(unsigned int idx)
|
void Tuner::SetTemperamentIndex(unsigned int idx)
|
||||||
{
|
{
|
||||||
if (temperaments->SetTemperament(idx)) {
|
if (temperaments->SetTemperament(idx)) {
|
||||||
scale->SetNotesFrequencies(temperaments->NotesFrequencies());
|
setNotesFrequencies(temperaments->NotesFrequencies());
|
||||||
temperamentChanged();
|
temperamentChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,35 +141,23 @@ QStringList Tuner::GetTemperamentList() const
|
||||||
return temperaments->GetNames();
|
return temperaments->GetNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a filename to record raw audio stream
|
void Tuner::ResultFound(int note, int octave, double deviation, double freq)
|
||||||
|
|
||||||
void Tuner::set_record(const char *f)
|
|
||||||
{
|
{
|
||||||
filename_record = f;
|
this->note = note;
|
||||||
}
|
this->octave = octave;
|
||||||
|
this->deviation = deviation;
|
||||||
|
this->freq = freq;
|
||||||
|
resultChanged();
|
||||||
|
|
||||||
/// analyse a file (static function)
|
if (!found) {
|
||||||
void Tuner::analyse_file(const char *filename)
|
foundChanged();
|
||||||
{
|
found = true;
|
||||||
cout << "analyse file " << filename << endl;
|
|
||||||
ifstream fin;
|
|
||||||
fin.open(filename);
|
|
||||||
|
|
||||||
const int nb_frame = 1024;
|
|
||||||
Tuner *tuner = new Tuner();
|
|
||||||
int16_t buffer[nb_frame];
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
fin.read((char*) buffer, sizeof(buffer));
|
|
||||||
tuner->AudioAnalyse(buffer, sizeof(buffer) >> 1);
|
|
||||||
|
|
||||||
cout << tuner->GetFreq() << " ";
|
|
||||||
if (tuner->GetFound())
|
|
||||||
cout << tuner->GetNoteName() << " " << tuner->GetOctave() << " " << tuner->GetDeviation();
|
|
||||||
cout << endl;
|
|
||||||
|
|
||||||
if (fin.eof()) break;
|
|
||||||
}
|
}
|
||||||
fin.close();
|
}
|
||||||
delete tuner;
|
|
||||||
|
void Tuner::ResultNotFound(double freq)
|
||||||
|
{
|
||||||
|
this->freq = freq;
|
||||||
|
found = false;
|
||||||
|
foundChanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,79 +15,37 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QAudioRecorder>
|
#ifndef _TUNER_HPP
|
||||||
#include <QAudioProbe>
|
#define _TUNER_HPP
|
||||||
|
|
||||||
#include <fstream>
|
#include "TunerWorker.hpp"
|
||||||
|
|
||||||
#include "audio/LinearFilter.hpp"
|
|
||||||
#include "audio/ZeroCross.hpp"
|
|
||||||
#include "scale/Scale.hpp"
|
|
||||||
#include "scale/Temperaments.hpp"
|
#include "scale/Temperaments.hpp"
|
||||||
|
|
||||||
class Tuner : public QObject {
|
class Tuner : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool running READ GetRunning WRITE SetRunning NOTIFY runningChanged)
|
Q_PROPERTY(bool running READ GetRunning WRITE SetRunning NOTIFY runningChanged)
|
||||||
Q_PROPERTY(double freq READ GetFreq NOTIFY freqChanged)
|
Q_PROPERTY(double freq READ GetFreq NOTIFY resultChanged)
|
||||||
Q_PROPERTY(double deviation READ GetDeviation NOTIFY deviationChanged)
|
Q_PROPERTY(double deviation READ GetDeviation NOTIFY resultChanged)
|
||||||
Q_PROPERTY(int note READ GetNote NOTIFY noteChanged)
|
Q_PROPERTY(int note READ GetNote NOTIFY resultChanged)
|
||||||
Q_PROPERTY(int octave READ GetOctave NOTIFY octaveChanged)
|
Q_PROPERTY(int octave READ GetOctave NOTIFY resultChanged)
|
||||||
Q_PROPERTY(bool found READ GetFound NOTIFY foundChanged)
|
Q_PROPERTY(bool found READ GetFound NOTIFY foundChanged)
|
||||||
Q_PROPERTY(QString noteName READ GetNoteName NOTIFY noteNameChanged)
|
|
||||||
Q_PROPERTY(int temperament_idx READ GetTemperamentIndex WRITE SetTemperamentIndex NOTIFY temperamentChanged)
|
Q_PROPERTY(int temperament_idx READ GetTemperamentIndex WRITE SetTemperamentIndex NOTIFY temperamentChanged)
|
||||||
Q_PROPERTY(QStringList temperament_list READ GetTemperamentList)
|
Q_PROPERTY(QStringList temperament_list READ GetTemperamentList)
|
||||||
|
Q_PROPERTY(double la READ GetLa WRITE SetLa NOTIFY laChanged)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QAudioRecorder *recorder;
|
|
||||||
QAudioEncoderSettings settings;
|
|
||||||
QAudioProbe *probe;
|
|
||||||
|
|
||||||
LinearFilter<int16_t> *high_filter;
|
|
||||||
ZeroCross<int16_t> *cross;
|
|
||||||
Scale *scale;
|
|
||||||
Temperaments *temperaments;
|
Temperaments *temperaments;
|
||||||
static const char *filename_record;
|
TunerWorker *worker;
|
||||||
std::ofstream file_record;
|
QThread workerThread;
|
||||||
|
|
||||||
bool running, found;
|
bool running, found;
|
||||||
double freq, deviation;
|
double freq, deviation, la;
|
||||||
int note, octave, nb_sample_running;
|
int note, octave;
|
||||||
int note_found, octave_found, count_found, count_not_found;
|
|
||||||
int nb_deviation, deviation_start;
|
|
||||||
double deviation_sum;
|
|
||||||
|
|
||||||
static const int rate = 16000;
|
|
||||||
static const int defaultNbFrame = 1024;
|
|
||||||
static const int defaultFreqMin = 50;
|
|
||||||
static const int defaultFreqMax = 2000;
|
|
||||||
static const int nbSamplePreventRunning = rate * 40; // 40 seconds
|
|
||||||
/// number of analyses to confirm a note
|
|
||||||
static const int nbConfirm = 3;
|
|
||||||
/// number of analyses to drop a note
|
|
||||||
static const int nbDefect = 20;
|
|
||||||
/// number of deviation values for average
|
|
||||||
static const int nbDeviationValues = 8;
|
|
||||||
|
|
||||||
double deviation_values[nbDeviationValues];
|
|
||||||
|
|
||||||
inline void ComputeFrame(int16_t v);
|
|
||||||
void SetFound(int note, int octave, double deviation);
|
|
||||||
void SetNotFound();
|
|
||||||
void ResetDeviation();
|
|
||||||
void UpdateDeviation(double d);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void AudioCb(const QAudioBuffer &buffer);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Tuner();
|
Tuner();
|
||||||
~Tuner();
|
~Tuner();
|
||||||
|
|
||||||
void Start();
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
void AudioAnalyse(const int16_t *buffer, int size);
|
|
||||||
|
|
||||||
bool GetRunning();
|
bool GetRunning();
|
||||||
void SetRunning(bool r);
|
void SetRunning(bool r);
|
||||||
double GetFreq();
|
double GetFreq();
|
||||||
|
@ -95,23 +53,29 @@ class Tuner : public QObject {
|
||||||
int GetOctave();
|
int GetOctave();
|
||||||
double GetDeviation();
|
double GetDeviation();
|
||||||
bool GetFound();
|
bool GetFound();
|
||||||
const char* GetNoteName();
|
|
||||||
unsigned int GetTemperamentIndex();
|
unsigned int GetTemperamentIndex();
|
||||||
void SetTemperamentIndex(unsigned int idx);
|
void SetTemperamentIndex(unsigned int idx);
|
||||||
QStringList GetTemperamentList() const;
|
QStringList GetTemperamentList() const;
|
||||||
|
double GetLa();
|
||||||
|
void SetLa(double la);
|
||||||
|
|
||||||
/// analyse a file for debug
|
public slots:
|
||||||
static void analyse_file(const char *filename);
|
void ResultFound(int note, int octave, double deviation, double frequency);
|
||||||
/// write a file with raw audio
|
void ResultNotFound(double freq);
|
||||||
static void set_record(const char *filename_record);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void runningChanged();
|
void runningChanged();
|
||||||
void freqChanged();
|
|
||||||
void noteChanged();
|
|
||||||
void noteNameChanged();
|
|
||||||
void octaveChanged();
|
|
||||||
void deviationChanged();
|
|
||||||
void foundChanged();
|
void foundChanged();
|
||||||
void temperamentChanged();
|
void temperamentChanged();
|
||||||
|
void laChanged();
|
||||||
|
void resultChanged();
|
||||||
|
|
||||||
|
// signals to worker
|
||||||
|
void quit();
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void setNotesFrequencies(const double *freq);
|
||||||
|
void setLa(double la_freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
297
src/TunerWorker.cpp
Normal file
297
src/TunerWorker.cpp
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
/* Copyright 2016 (C) Louis-Joseph Fournier
|
||||||
|
* louisjoseph.fournier@gmail.com
|
||||||
|
*
|
||||||
|
* This file is part of SailTuner.
|
||||||
|
*
|
||||||
|
* SailTuner 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.
|
||||||
|
*
|
||||||
|
* SailTuner 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "TunerWorker.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 };
|
||||||
|
|
||||||
|
const char * TunerWorker::filename_record = NULL;
|
||||||
|
|
||||||
|
/// function to prevent screen blank on Sailfish OS
|
||||||
|
|
||||||
|
static void blank_prevent(bool prevent)
|
||||||
|
{
|
||||||
|
cerr << __func__ << endl;
|
||||||
|
QDBusConnection system = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "system");
|
||||||
|
QDBusInterface interface("com.nokia.mce", "/com/nokia/mce/request", "com.nokia.mce.request", system);
|
||||||
|
|
||||||
|
if (prevent) {
|
||||||
|
interface.call(QLatin1String("req_display_blanking_pause"));
|
||||||
|
} else {
|
||||||
|
interface.call(QLatin1String("req_display_cancel_blanking_pause"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TunerWorker::TunerWorker() :
|
||||||
|
high_filter(NULL),
|
||||||
|
cross(NULL),
|
||||||
|
scale(NULL),
|
||||||
|
running(false),
|
||||||
|
quit(false),
|
||||||
|
la_to_update(0),
|
||||||
|
freq_to_update(NULL)
|
||||||
|
{
|
||||||
|
// part of reset
|
||||||
|
found = false;
|
||||||
|
count_found = count_not_found = 0;
|
||||||
|
nb_sample_running = 0;
|
||||||
|
note_found = octave_found = -1;
|
||||||
|
ResetDeviation();
|
||||||
|
}
|
||||||
|
|
||||||
|
TunerWorker::~TunerWorker()
|
||||||
|
{
|
||||||
|
if (filename_record && file_record.is_open()) file_record.close();
|
||||||
|
if (high_filter) delete high_filter;
|
||||||
|
if (cross) delete cross;
|
||||||
|
if (scale) delete scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reset analyse values
|
||||||
|
void TunerWorker::Reset()
|
||||||
|
{
|
||||||
|
found = false;
|
||||||
|
count_found = count_not_found = 0;
|
||||||
|
nb_sample_running = 0;
|
||||||
|
note_found = octave_found = -1;
|
||||||
|
ResetDeviation();
|
||||||
|
blank_prevent(true);
|
||||||
|
high_filter->Clear();
|
||||||
|
cross->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::Start()
|
||||||
|
{
|
||||||
|
cerr << __func__ << endl;
|
||||||
|
mutex.lock();
|
||||||
|
running = true;
|
||||||
|
condition.wakeOne();
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::Stop()
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
running = false;
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::Quit()
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
running = false;
|
||||||
|
quit = true;
|
||||||
|
condition.wakeOne();
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::SetNotesFrequencies(const double *notes_freq)
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
freq_to_update = notes_freq;
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::SetLa(double la)
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
la_to_update = la;
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::ComputeFrame(int16_t v)
|
||||||
|
{
|
||||||
|
v = (*high_filter)(v);
|
||||||
|
(*cross)(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::ResetDeviation()
|
||||||
|
{
|
||||||
|
// reset deviation values
|
||||||
|
nb_deviation = 0;
|
||||||
|
deviation_start = 0;
|
||||||
|
deviation_sum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::UpdateDeviation(double d)
|
||||||
|
{
|
||||||
|
if (nb_deviation == nbDeviationValues) {
|
||||||
|
deviation_sum -= deviation_values[deviation_start];
|
||||||
|
deviation_start = (deviation_start + 1) % nbDeviationValues;
|
||||||
|
nb_deviation--;
|
||||||
|
}
|
||||||
|
deviation_values[(deviation_start + nb_deviation) % nbDeviationValues] = d;
|
||||||
|
nb_deviation++;
|
||||||
|
deviation_sum += d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::SetFound(int n, int o, double d)
|
||||||
|
{
|
||||||
|
if (n != note_found || o != octave_found) {
|
||||||
|
note_found = n;
|
||||||
|
octave_found = o;
|
||||||
|
count_found = 0;
|
||||||
|
SetNotFound();
|
||||||
|
|
||||||
|
ResetDeviation();
|
||||||
|
UpdateDeviation(d);
|
||||||
|
}
|
||||||
|
else if (count_found++ >= nbConfirm) {
|
||||||
|
found = true;
|
||||||
|
count_not_found = 0;
|
||||||
|
UpdateDeviation(d);
|
||||||
|
|
||||||
|
resultFound(n, o, deviation_sum / nb_deviation, cross->Freq());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
UpdateDeviation(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::SetNotFound()
|
||||||
|
{
|
||||||
|
if (count_not_found++ >= nbDefect) {
|
||||||
|
count_found = 0;
|
||||||
|
if (found) {
|
||||||
|
found = false;
|
||||||
|
ResetDeviation();
|
||||||
|
resultNotFound(cross->Freq());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TunerWorker::AudioAnalyse(const int16_t *ptr, int nb_frame)
|
||||||
|
{
|
||||||
|
nb_sample_running += nb_frame;
|
||||||
|
|
||||||
|
// record in file is needed
|
||||||
|
if (filename_record && file_record.is_open()) file_record.write((char*) ptr, nb_frame * sizeof(int16_t));
|
||||||
|
|
||||||
|
// compute every audio frame
|
||||||
|
while (nb_frame--) ComputeFrame(*ptr++);
|
||||||
|
|
||||||
|
// find note, octave, deviation
|
||||||
|
if (cross->Freq()) {
|
||||||
|
int n, o = 0;
|
||||||
|
double d = 0;
|
||||||
|
n = scale->FindNote(cross->Freq(), o, d);
|
||||||
|
SetFound(n, o, d);
|
||||||
|
}
|
||||||
|
else { // no freq
|
||||||
|
SetNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent screen blanking
|
||||||
|
if (nb_sample_running >= nbSamplePreventRunning && running) {
|
||||||
|
nb_sample_running = 0;
|
||||||
|
blank_prevent(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TunerWorker::Entry()
|
||||||
|
{
|
||||||
|
// initialisations
|
||||||
|
if (filename_record) file_record.open(filename_record);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
scale = new Scale();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
// wait for running
|
||||||
|
mutex.lock();
|
||||||
|
if (!running) {
|
||||||
|
blank_prevent(false);
|
||||||
|
while (!running && !quit) condition.wait(&mutex);
|
||||||
|
// reset operations on start
|
||||||
|
if (!quit) Reset();
|
||||||
|
}
|
||||||
|
if (quit) {
|
||||||
|
mutex.unlock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// update config
|
||||||
|
if (la_to_update) {
|
||||||
|
scale->SetLa(la_to_update);
|
||||||
|
la_to_update = 0;
|
||||||
|
}
|
||||||
|
if (freq_to_update) {
|
||||||
|
scale->SetNotesFrequencies(freq_to_update);
|
||||||
|
freq_to_update = NULL;
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
|
||||||
|
std::cout << __func__ << " do job" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a filename to record raw audio stream
|
||||||
|
|
||||||
|
void TunerWorker::set_record(const char *f)
|
||||||
|
{
|
||||||
|
filename_record = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// for analyse_file console logs
|
||||||
|
static void display_results(int note, int octave, double deviation, double frequency)
|
||||||
|
{
|
||||||
|
cout << frequency << " " << Scale::NoteName(note) << " " << octave << " " << deviation << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_no_results(double freq)
|
||||||
|
{
|
||||||
|
cout << freq << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// analyse a file (static function)
|
||||||
|
void TunerWorker::analyse_file(const char *filename)
|
||||||
|
{
|
||||||
|
cout << "analyse file " << filename << endl;
|
||||||
|
ifstream fin;
|
||||||
|
fin.open(filename);
|
||||||
|
|
||||||
|
const int nb_frame = 1024;
|
||||||
|
TunerWorker *tuner = new TunerWorker();
|
||||||
|
int16_t buffer[nb_frame];
|
||||||
|
|
||||||
|
connect(tuner, &TunerWorker::resultFound, NULL, display_results);
|
||||||
|
connect(tuner, &TunerWorker::resultNotFound, NULL, display_no_results);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
fin.read((char*) buffer, sizeof(buffer));
|
||||||
|
tuner->AudioAnalyse(buffer, sizeof(buffer) >> 1);
|
||||||
|
// cout << "." << endl;
|
||||||
|
|
||||||
|
if (fin.eof()) break;
|
||||||
|
}
|
||||||
|
fin.close();
|
||||||
|
delete tuner;
|
||||||
|
}
|
106
src/TunerWorker.hpp
Normal file
106
src/TunerWorker.hpp
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/* Copyright 2016 (C) Louis-Joseph Fournier
|
||||||
|
* louisjoseph.fournier@gmail.com
|
||||||
|
*
|
||||||
|
* This file is part of SailTuner.
|
||||||
|
*
|
||||||
|
* SailTuner 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.
|
||||||
|
*
|
||||||
|
* SailTuner 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TUNER_WORKER_HPP
|
||||||
|
#define _TUNER_WORKER_HPP
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QWaitCondition>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "audio/LinearFilter.hpp"
|
||||||
|
#include "audio/ZeroCross.hpp"
|
||||||
|
#include "scale/Scale.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker class to work with audio datas
|
||||||
|
*
|
||||||
|
* Implements the pitch algorithm
|
||||||
|
* and note finding
|
||||||
|
*/
|
||||||
|
class TunerWorker : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const int rate = 16000;
|
||||||
|
static const int defaultNbFrame = 1024;
|
||||||
|
static const int defaultFreqMin = 50;
|
||||||
|
static const int defaultFreqMax = 2000;
|
||||||
|
static const int nbSamplePreventRunning = rate * 40; // 40 seconds
|
||||||
|
/// number of analyses to confirm a note
|
||||||
|
static const int nbConfirm = 3;
|
||||||
|
/// number of analyses to drop a note
|
||||||
|
static const int nbDefect = 20;
|
||||||
|
/// number of deviation values for average
|
||||||
|
static const int nbDeviationValues = 8;
|
||||||
|
|
||||||
|
static const char *filename_record;
|
||||||
|
|
||||||
|
LinearFilter<int16_t> *high_filter;
|
||||||
|
ZeroCross<int16_t> *cross;
|
||||||
|
Scale *scale;
|
||||||
|
std::ofstream file_record;
|
||||||
|
|
||||||
|
QMutex mutex;
|
||||||
|
QWaitCondition condition;
|
||||||
|
|
||||||
|
bool running, found, quit;
|
||||||
|
int nb_sample_running;
|
||||||
|
int note_found, octave_found, count_found, count_not_found;
|
||||||
|
int nb_deviation, deviation_start;
|
||||||
|
double deviation_sum;
|
||||||
|
double deviation_values[nbDeviationValues];
|
||||||
|
|
||||||
|
// to update vars
|
||||||
|
double la_to_update;
|
||||||
|
const double *freq_to_update;
|
||||||
|
|
||||||
|
inline void ComputeFrame(int16_t v);
|
||||||
|
void SetFound(int note, int octave, double deviation);
|
||||||
|
void SetNotFound();
|
||||||
|
void Reset();
|
||||||
|
void ResetDeviation();
|
||||||
|
void UpdateDeviation(double d);
|
||||||
|
void AudioAnalyse(const int16_t *buffer, int size);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// analyse a file for debug
|
||||||
|
static void analyse_file(const char *filename);
|
||||||
|
/// write a file with raw audio
|
||||||
|
static void set_record(const char *filename_record);
|
||||||
|
|
||||||
|
/// constructor
|
||||||
|
TunerWorker();
|
||||||
|
~TunerWorker();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
void SetNotesFrequencies(const double *notes_freq);
|
||||||
|
void SetLa(double la);
|
||||||
|
void Entry();
|
||||||
|
void Quit();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void resultFound(int note, int octave, double deviation, double frequency);
|
||||||
|
void resultNotFound(double freq);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -27,7 +27,7 @@
|
||||||
Q_DECL_EXPORT int main(int argc, char* argv[])
|
Q_DECL_EXPORT int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
if (argc == 2) {
|
if (argc == 2) {
|
||||||
Tuner::analyse_file(argv[1]);
|
TunerWorker::analyse_file(argv[1]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Main {
|
||||||
Q_DECL_EXPORT int main(int argc, char* argv[])
|
Q_DECL_EXPORT int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
if (argc == 2) {
|
if (argc == 2) {
|
||||||
Tuner::analyse_file(argv[1]);
|
TunerWorker::analyse_file(argv[1]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (argc == 3 && strcmp(argv[1], "record") == 0) {
|
else if (argc == 3 && strcmp(argv[1], "record") == 0) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ const char * Scale::NoteName(int note)
|
||||||
Scale::Scale()
|
Scale::Scale()
|
||||||
{
|
{
|
||||||
actualLa = defaultLa;
|
actualLa = defaultLa;
|
||||||
|
freq_setted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scale::updateScale()
|
void Scale::updateScale()
|
||||||
|
@ -80,6 +81,10 @@ int Scale::findOctave(double &freq)
|
||||||
int Scale::FindNote(double freq, int &octave, double &deviation)
|
int Scale::FindNote(double freq, int &octave, double &deviation)
|
||||||
{
|
{
|
||||||
assert (freq > 0);
|
assert (freq > 0);
|
||||||
|
if (!freq_setted) {
|
||||||
|
std::cerr << "Scale " << __func__ << ": notes not setted" << std::endl;
|
||||||
|
ConstructEqualTemperament();
|
||||||
|
}
|
||||||
|
|
||||||
int note = 0;
|
int note = 0;
|
||||||
octave = findOctave(freq);
|
octave = findOctave(freq);
|
||||||
|
@ -111,6 +116,7 @@ void Scale::ConstructEqualTemperament()
|
||||||
noteFreq[i] = defaultLa * pow(2, (double) (i - la) / 12);
|
noteFreq[i] = defaultLa * pow(2, (double) (i - la) / 12);
|
||||||
//std::cerr << noteFreq[i] << std::endl;
|
//std::cerr << noteFreq[i] << std::endl;
|
||||||
}
|
}
|
||||||
|
freq_setted = true;
|
||||||
updateScale();
|
updateScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +127,8 @@ double Scale::GetLa()
|
||||||
|
|
||||||
void Scale::SetNotesFrequencies(const double freq[nbNote])
|
void Scale::SetNotesFrequencies(const double freq[nbNote])
|
||||||
{
|
{
|
||||||
|
std::cout << __func__ << std::endl;
|
||||||
|
freq_setted = true;
|
||||||
memcpy(noteFreq, freq, sizeof(double) * nbNote);
|
memcpy(noteFreq, freq, sizeof(double) * nbNote);
|
||||||
updateScale();
|
updateScale();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,18 @@
|
||||||
* Note recognition within a temperament
|
* Note recognition within a temperament
|
||||||
*/
|
*/
|
||||||
class Scale {
|
class Scale {
|
||||||
|
public:
|
||||||
|
static const int defaultLa = 440;
|
||||||
private:
|
private:
|
||||||
static const int nbNote = 12;
|
static const int nbNote = 12;
|
||||||
static const int cmpOctave = 4;
|
static const int cmpOctave = 4;
|
||||||
static const int defaultLa = 440;
|
|
||||||
|
|
||||||
static const char *noteNames[nbNote];
|
static const char *noteNames[nbNote];
|
||||||
|
|
||||||
double noteFreq[nbNote], actualNoteFreq[nbNote];
|
double noteFreq[nbNote], actualNoteFreq[nbNote];
|
||||||
double actualLa, actualFactor;
|
double actualLa, actualFactor;
|
||||||
double actualRange[2]; // freq range for default octave
|
double actualRange[2]; // freq range for default octave
|
||||||
|
bool freq_setted;
|
||||||
|
|
||||||
/// update scale after temperament or default la change
|
/// update scale after temperament or default la change
|
||||||
void updateScale();
|
void updateScale();
|
||||||
|
|
|
@ -69,7 +69,7 @@ void Temperaments::CheckFile(const QString & filename)
|
||||||
}
|
}
|
||||||
// add temperament
|
// add temperament
|
||||||
name = line.left(offset);
|
name = line.left(offset);
|
||||||
qDebug() << " -> add temperament" << name;
|
//qDebug() << " -> add temperament" << name;
|
||||||
list.push_back(temp_t{name, filename, f_pos});
|
list.push_back(temp_t{name, filename, f_pos});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#define __TEMPERAMENTS_HPP
|
#define __TEMPERAMENTS_HPP
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue