PitchDetection and TunerWorker different objects
This commit is contained in:
parent
5a341979df
commit
1d857fd25d
10 changed files with 436 additions and 290 deletions
|
@ -7,6 +7,7 @@ CONFIG += c++11 debug
|
|||
|
||||
SOURCES += \
|
||||
src/desktop.cpp \
|
||||
src/PitchDetection.cpp \
|
||||
src/Tuner.cpp \
|
||||
src/TunerWorker.cpp \
|
||||
src/audio/LinearFilter.cpp \
|
||||
|
@ -15,6 +16,7 @@ SOURCES += \
|
|||
src/scale/Temperaments.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/PitchDetection.hpp \
|
||||
src/Tuner.hpp \
|
||||
src/TunerWorker.hpp \
|
||||
src/audio/LinearFilter.hpp \
|
||||
|
|
|
@ -12,6 +12,7 @@ RESOURCES += \
|
|||
|
||||
SOURCES += \
|
||||
src/sailfish.cpp \
|
||||
src/PitchDetection.cpp \
|
||||
src/Tuner.cpp \
|
||||
src/TunerWorker.cpp \
|
||||
src/audio/LinearFilter.cpp \
|
||||
|
@ -20,6 +21,8 @@ SOURCES += \
|
|||
src/scale/Temperaments.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/PitchDetection.hpp \
|
||||
src/Tuner.cpp \
|
||||
src/Tuner.hpp \
|
||||
src/TunerWorker.hpp \
|
||||
src/audio/LinearFilter.hpp \
|
||||
|
|
242
src/PitchDetection.cpp
Normal file
242
src/PitchDetection.cpp
Normal file
|
@ -0,0 +1,242 @@
|
|||
/* 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 <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "PitchDetection.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 * PitchDetection::filename_record = NULL;
|
||||
|
||||
/// Pitch detection result methods
|
||||
|
||||
PitchDetection::PitchResult::PitchResult() :
|
||||
found(false),
|
||||
deviation(0),
|
||||
frequency(0),
|
||||
note(0),
|
||||
octave(0)
|
||||
{
|
||||
}
|
||||
|
||||
void PitchDetection::PitchResult::Set(int n, int o, double d, double f)
|
||||
{
|
||||
note = n;
|
||||
octave = o;
|
||||
deviation = d;
|
||||
frequency = f;
|
||||
found = true;
|
||||
}
|
||||
|
||||
/// Pitch detection methods
|
||||
|
||||
PitchDetection::PitchDetection()
|
||||
{
|
||||
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();
|
||||
temperaments = new Temperaments(":/data");
|
||||
|
||||
if (temperaments->SetTemperament(0)) {
|
||||
scale->SetNotesFrequencies(temperaments->NotesFrequencies());
|
||||
}
|
||||
else {
|
||||
scale->ConstructEqualTemperament();
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
PitchDetection::~PitchDetection()
|
||||
{
|
||||
if (filename_record && file_record.is_open()) file_record.close();
|
||||
delete high_filter;
|
||||
delete cross;
|
||||
delete scale;
|
||||
delete temperaments;
|
||||
}
|
||||
|
||||
/// reset analyse values
|
||||
void PitchDetection::Reset()
|
||||
{
|
||||
result.found = false;
|
||||
updated = false;
|
||||
count_found = count_not_found = 0;
|
||||
note_found = octave_found = -1;
|
||||
ResetDeviation();
|
||||
high_filter->Clear();
|
||||
cross->Clear();
|
||||
}
|
||||
|
||||
void PitchDetection::ComputeFrame(int16_t v)
|
||||
{
|
||||
v = (*high_filter)(v);
|
||||
(*cross)(v);
|
||||
}
|
||||
|
||||
void PitchDetection::ResetDeviation()
|
||||
{
|
||||
// reset deviation values
|
||||
nb_deviation = 0;
|
||||
deviation_start = 0;
|
||||
deviation_sum = 0;
|
||||
}
|
||||
|
||||
void PitchDetection::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 PitchDetection::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;
|
||||
UpdateDeviation(d);
|
||||
|
||||
result.Set(n, o, deviation_sum / nb_deviation, cross->Freq());
|
||||
updated = true;
|
||||
}
|
||||
else {
|
||||
UpdateDeviation(d);
|
||||
}
|
||||
}
|
||||
|
||||
void PitchDetection::SetNotFound()
|
||||
{
|
||||
if (count_not_found++ >= nbDefect) {
|
||||
count_found = 0;
|
||||
if (result.found) {
|
||||
result.found = false;
|
||||
ResetDeviation();
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PitchDetection::SetLa(double la)
|
||||
{
|
||||
scale->SetLa(la);
|
||||
}
|
||||
|
||||
void PitchDetection::SetTemperament(int idx)
|
||||
{
|
||||
if (idx < 0) return;
|
||||
if (temperaments->SetTemperament(idx)) {
|
||||
scale->SetNotesFrequencies(temperaments->NotesFrequencies());
|
||||
}
|
||||
}
|
||||
|
||||
void PitchDetection::AudioAnalyse(const int16_t *ptr, int 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();
|
||||
}
|
||||
}
|
||||
|
||||
bool PitchDetection::GetResultUpdated(PitchResult &res)
|
||||
{
|
||||
if (!updated) return false;
|
||||
res = this->result;
|
||||
updated = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList PitchDetection::GetTemperamentList() const
|
||||
{
|
||||
return temperaments->GetNames();
|
||||
}
|
||||
|
||||
/// Set a filename to record raw audio stream
|
||||
|
||||
void PitchDetection::set_record(const char *f)
|
||||
{
|
||||
filename_record = f;
|
||||
}
|
||||
|
||||
/// for analyse_file console logs
|
||||
static void display_results(const PitchDetection::PitchResult &res)
|
||||
{
|
||||
if (res.found)
|
||||
cout << res.frequency << " " << Scale::NoteName(res.note) << " " << res.octave << " " << res.deviation << endl;
|
||||
else
|
||||
cout << res.frequency << endl;
|
||||
}
|
||||
|
||||
/// analyse a file (static function)
|
||||
void PitchDetection::analyse_file(const char *filename)
|
||||
{
|
||||
cout << "analyse file " << filename << endl;
|
||||
ifstream fin;
|
||||
fin.open(filename);
|
||||
|
||||
const int nb_frame = 1024;
|
||||
PitchDetection *pitch = new PitchDetection();
|
||||
int16_t buffer[nb_frame];
|
||||
PitchResult result;
|
||||
|
||||
while (1) {
|
||||
fin.read((char*) buffer, sizeof(buffer));
|
||||
pitch->AudioAnalyse(buffer, sizeof(buffer) >> 1);
|
||||
|
||||
if (pitch->GetResultUpdated(result)) display_results(result);
|
||||
else cout << "." << endl;
|
||||
|
||||
if (fin.eof()) break;
|
||||
}
|
||||
fin.close();
|
||||
delete pitch;
|
||||
}
|
107
src/PitchDetection.hpp
Normal file
107
src/PitchDetection.hpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* 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 _PITCH_DETECTION_HPP
|
||||
#define _PITCH_DETECTION_HPP
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "audio/LinearFilter.hpp"
|
||||
#include "audio/ZeroCross.hpp"
|
||||
#include "scale/Scale.hpp"
|
||||
#include "scale/Temperaments.hpp"
|
||||
|
||||
/**
|
||||
* Pitch detection algorithm
|
||||
*
|
||||
* Implements the pitch algorithm
|
||||
* and note finding, using audio and scale objects
|
||||
*/
|
||||
class PitchDetection {
|
||||
public:
|
||||
/**
|
||||
* Structure for pitch detection result
|
||||
*/
|
||||
struct PitchResult {
|
||||
bool found;
|
||||
double deviation, frequency;
|
||||
int note, octave;
|
||||
|
||||
PitchResult();
|
||||
void Set(int n, int o, double d, double f);
|
||||
};
|
||||
|
||||
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;
|
||||
Temperaments *temperaments;
|
||||
std::ofstream file_record;
|
||||
|
||||
PitchResult result;
|
||||
bool updated;
|
||||
int note_found, octave_found, count_found, count_not_found;
|
||||
int nb_deviation, deviation_start;
|
||||
double deviation_sum;
|
||||
double deviation_values[nbDeviationValues];
|
||||
|
||||
void SetFound(int note, int octave, double deviation);
|
||||
void SetNotFound();
|
||||
void ResetDeviation();
|
||||
void UpdateDeviation(double d);
|
||||
|
||||
/// Compute an audio sample
|
||||
inline void ComputeFrame(int16_t v);
|
||||
|
||||
public:
|
||||
PitchDetection();
|
||||
~PitchDetection();
|
||||
|
||||
/// compute an audio buffer
|
||||
void AudioAnalyse(const int16_t *buffer, int size);
|
||||
/// Get results if updated
|
||||
bool GetResultUpdated(PitchResult &result);
|
||||
/// reset computing
|
||||
void Reset();
|
||||
/// Set la4 freq reference
|
||||
void SetLa(double freq);
|
||||
/// Set temperament
|
||||
void SetTemperament(int idx);
|
||||
/// Get temperament list
|
||||
QStringList GetTemperamentList() const;
|
||||
|
||||
/// 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);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -32,32 +32,24 @@ using namespace std;
|
|||
Tuner::Tuner()
|
||||
{
|
||||
running = false;
|
||||
freq = deviation = 0;
|
||||
note = octave = 0;
|
||||
found = false;
|
||||
temperament_idx = 0;
|
||||
la = Scale::defaultLa;
|
||||
|
||||
worker = new TunerWorker();
|
||||
|
||||
temperaments = new Temperaments(":/data");
|
||||
|
||||
if (temperaments->SetTemperament(0)) {
|
||||
worker->SetNotesFrequencies(temperaments->NotesFrequencies());
|
||||
}
|
||||
|
||||
worker->moveToThread(&workerThread);
|
||||
|
||||
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
|
||||
connect(&workerThread, &QThread::started, worker, &TunerWorker::Entry);
|
||||
connect(this, &Tuner::quit, worker, &TunerWorker::Quit, Qt::DirectConnection);
|
||||
|
||||
connect(this, &Tuner::quit, worker, &TunerWorker::Quit, Qt::DirectConnection);
|
||||
connect(this, &Tuner::start, worker, &TunerWorker::Start);
|
||||
connect(this, &Tuner::stop, worker, &TunerWorker::Stop);
|
||||
connect(this, &Tuner::setNotesFrequencies, worker, &TunerWorker::SetNotesFrequencies);
|
||||
connect(this, &Tuner::setTemperamentIndex, worker, &TunerWorker::SetTemperament);
|
||||
connect(this, &Tuner::setLa, worker, &TunerWorker::SetLa);
|
||||
|
||||
connect(worker, &TunerWorker::resultFound, this, &Tuner::ResultFound);
|
||||
connect(worker, &TunerWorker::resultNotFound, this, &Tuner::ResultNotFound);
|
||||
connect(worker, &TunerWorker::resultUpdated, this, &Tuner::ResultUpdated);
|
||||
connect(worker, &TunerWorker::temperamentListUpdated, this, &Tuner::TemperamentListUpdated);
|
||||
|
||||
workerThread.start();
|
||||
}
|
||||
|
@ -67,7 +59,6 @@ Tuner::~Tuner()
|
|||
quit();
|
||||
// workerThread.quit();
|
||||
workerThread.wait(10);
|
||||
delete temperaments;
|
||||
}
|
||||
|
||||
bool Tuner::GetRunning()
|
||||
|
@ -80,42 +71,42 @@ void Tuner::SetRunning(bool r)
|
|||
if (running == r) return;
|
||||
|
||||
running = r;
|
||||
if (r) start();
|
||||
else stop();
|
||||
if (r) emit start();
|
||||
else emit stop();
|
||||
|
||||
runningChanged();
|
||||
emit runningChanged();
|
||||
}
|
||||
|
||||
double Tuner::GetFreq()
|
||||
{
|
||||
return freq;
|
||||
return result.frequency;
|
||||
}
|
||||
|
||||
int Tuner::GetNote()
|
||||
{
|
||||
return note;
|
||||
return result.note;
|
||||
}
|
||||
|
||||
double Tuner::GetDeviation()
|
||||
{
|
||||
return deviation;
|
||||
return result.deviation;
|
||||
}
|
||||
|
||||
int Tuner::GetOctave()
|
||||
{
|
||||
return octave;
|
||||
return result.octave;
|
||||
}
|
||||
|
||||
bool Tuner::GetFound()
|
||||
{
|
||||
return found;
|
||||
return result.found;
|
||||
}
|
||||
|
||||
void Tuner::SetLa(double la)
|
||||
{
|
||||
this->la = la;
|
||||
setLa(la);
|
||||
laChanged();
|
||||
emit setLa(la);
|
||||
emit laChanged();
|
||||
}
|
||||
|
||||
double Tuner::GetLa()
|
||||
|
@ -125,39 +116,33 @@ double Tuner::GetLa()
|
|||
|
||||
unsigned int Tuner::GetTemperamentIndex()
|
||||
{
|
||||
return temperaments->GetCurrentIndex();
|
||||
return temperament_idx;
|
||||
}
|
||||
|
||||
void Tuner::SetTemperamentIndex(unsigned int idx)
|
||||
void Tuner::SetTemperamentIndex(int idx)
|
||||
{
|
||||
if (temperaments->SetTemperament(idx)) {
|
||||
setNotesFrequencies(temperaments->NotesFrequencies());
|
||||
temperamentChanged();
|
||||
}
|
||||
temperament_idx = idx;
|
||||
emit setTemperamentIndex(idx);
|
||||
emit temperamentChanged();
|
||||
}
|
||||
|
||||
QStringList Tuner::GetTemperamentList() const
|
||||
{
|
||||
return temperaments->GetNames();
|
||||
return temperament_list;
|
||||
}
|
||||
|
||||
void Tuner::ResultFound(int note, int octave, double deviation, double freq)
|
||||
void Tuner::ResultUpdated(const PitchDetection::PitchResult &result)
|
||||
{
|
||||
this->note = note;
|
||||
this->octave = octave;
|
||||
this->deviation = deviation;
|
||||
this->freq = freq;
|
||||
resultChanged();
|
||||
const bool changed = (this->result.found ^ result.found);
|
||||
|
||||
if (!found) {
|
||||
foundChanged();
|
||||
found = true;
|
||||
}
|
||||
this->result = result;
|
||||
|
||||
if (result.found) emit resultChanged();
|
||||
if (changed) emit foundChanged();
|
||||
}
|
||||
|
||||
void Tuner::ResultNotFound(double freq)
|
||||
void Tuner::TemperamentListUpdated(const QStringList &list)
|
||||
{
|
||||
this->freq = freq;
|
||||
found = false;
|
||||
foundChanged();
|
||||
temperament_list = list;
|
||||
emit temperamentListChanged();
|
||||
}
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
#ifndef _TUNER_HPP
|
||||
#define _TUNER_HPP
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "PitchDetection.hpp"
|
||||
#include "TunerWorker.hpp"
|
||||
#include "scale/Temperaments.hpp"
|
||||
|
||||
class Tuner : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -30,17 +32,18 @@ class Tuner : public QObject {
|
|||
Q_PROPERTY(int octave READ GetOctave NOTIFY resultChanged)
|
||||
Q_PROPERTY(bool found READ GetFound NOTIFY foundChanged)
|
||||
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 NOTIFY temperamentListChanged)
|
||||
Q_PROPERTY(double la READ GetLa WRITE SetLa NOTIFY laChanged)
|
||||
|
||||
private:
|
||||
Temperaments *temperaments;
|
||||
TunerWorker *worker;
|
||||
QThread workerThread;
|
||||
|
||||
bool running, found;
|
||||
double freq, deviation, la;
|
||||
int note, octave;
|
||||
PitchDetection::PitchResult result;
|
||||
QStringList temperament_list;
|
||||
bool running;
|
||||
double la;
|
||||
int temperament_idx;
|
||||
|
||||
public:
|
||||
Tuner();
|
||||
|
@ -54,27 +57,30 @@ class Tuner : public QObject {
|
|||
double GetDeviation();
|
||||
bool GetFound();
|
||||
unsigned int GetTemperamentIndex();
|
||||
void SetTemperamentIndex(unsigned int idx);
|
||||
void SetTemperamentIndex(int idx);
|
||||
QStringList GetTemperamentList() const;
|
||||
double GetLa();
|
||||
void SetLa(double la);
|
||||
|
||||
public slots:
|
||||
void ResultFound(int note, int octave, double deviation, double frequency);
|
||||
void ResultNotFound(double freq);
|
||||
// slots from worker
|
||||
void ResultUpdated(const PitchDetection::PitchResult &result);
|
||||
void TemperamentListUpdated(const QStringList &list);
|
||||
|
||||
signals:
|
||||
// signals to UI
|
||||
void runningChanged();
|
||||
void foundChanged();
|
||||
void temperamentChanged();
|
||||
void laChanged();
|
||||
void resultChanged();
|
||||
void temperamentChanged();
|
||||
void temperamentListChanged();
|
||||
|
||||
// signals to worker
|
||||
void quit();
|
||||
void start();
|
||||
void stop();
|
||||
void setNotesFrequencies(const double *freq);
|
||||
void setTemperamentIndex(int idx);
|
||||
void setLa(double la_freq);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,18 +19,11 @@
|
|||
#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)
|
||||
|
@ -47,41 +40,16 @@ static void blank_prevent(bool prevent)
|
|||
}
|
||||
|
||||
TunerWorker::TunerWorker() :
|
||||
high_filter(NULL),
|
||||
cross(NULL),
|
||||
scale(NULL),
|
||||
running(false),
|
||||
quit(false),
|
||||
la_to_update(0),
|
||||
freq_to_update(NULL)
|
||||
temperament_to_update(-1)
|
||||
{
|
||||
// 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();
|
||||
if (pitchDetection) delete pitchDetection;
|
||||
}
|
||||
|
||||
void TunerWorker::Start()
|
||||
|
@ -109,13 +77,6 @@ void TunerWorker::Quit()
|
|||
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();
|
||||
|
@ -123,107 +84,18 @@ void TunerWorker::SetLa(double la)
|
|||
mutex.unlock();
|
||||
}
|
||||
|
||||
void TunerWorker::ComputeFrame(int16_t v)
|
||||
void TunerWorker::SetTemperament(int idx)
|
||||
{
|
||||
v = (*high_filter)(v);
|
||||
(*cross)(v);
|
||||
mutex.lock();
|
||||
temperament_to_update = idx;
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
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();
|
||||
cerr << __func__ << endl;
|
||||
pitchDetection = new PitchDetection();
|
||||
emit temperamentListUpdated(pitchDetection->GetTemperamentList());
|
||||
|
||||
while (1) {
|
||||
// wait for running
|
||||
|
@ -231,8 +103,9 @@ void TunerWorker::Entry()
|
|||
if (!running) {
|
||||
blank_prevent(false);
|
||||
while (!running && !quit) condition.wait(&mutex);
|
||||
cerr << "wake-up" << endl;
|
||||
// reset operations on start
|
||||
if (!quit) Reset();
|
||||
if (!quit) pitchDetection->Reset();
|
||||
}
|
||||
if (quit) {
|
||||
mutex.unlock();
|
||||
|
@ -240,58 +113,22 @@ void TunerWorker::Entry()
|
|||
}
|
||||
// update config
|
||||
if (la_to_update) {
|
||||
scale->SetLa(la_to_update);
|
||||
pitchDetection->SetLa(la_to_update);
|
||||
la_to_update = 0;
|
||||
}
|
||||
if (freq_to_update) {
|
||||
scale->SetNotesFrequencies(freq_to_update);
|
||||
freq_to_update = NULL;
|
||||
if (temperament_to_update != -1) {
|
||||
pitchDetection->SetTemperament(temperament_to_update);
|
||||
temperament_to_update = -1;
|
||||
}
|
||||
mutex.unlock();
|
||||
|
||||
std::cout << __func__ << " do job" << std::endl;
|
||||
}
|
||||
/*
|
||||
// prevent screen blanking
|
||||
if (nb_sample_running >= nbSamplePreventRunning && running) {
|
||||
nb_sample_running = 0;
|
||||
blank_prevent(true);
|
||||
}*/
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
|
|
@ -22,70 +22,32 @@
|
|||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
#include <QStringList>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "audio/LinearFilter.hpp"
|
||||
#include "audio/ZeroCross.hpp"
|
||||
#include "scale/Scale.hpp"
|
||||
#include "PitchDetection.hpp"
|
||||
|
||||
/**
|
||||
* Worker class to work with audio datas
|
||||
* Worker class to receive audio and
|
||||
* do pitch detection in a thread
|
||||
*
|
||||
* 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;
|
||||
PitchDetection *pitchDetection;
|
||||
|
||||
bool running, 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);
|
||||
int temperament_to_update;
|
||||
|
||||
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();
|
||||
|
@ -93,14 +55,14 @@ class TunerWorker : public QObject {
|
|||
public slots:
|
||||
void Start();
|
||||
void Stop();
|
||||
void SetNotesFrequencies(const double *notes_freq);
|
||||
void SetTemperament(int idx);
|
||||
void SetLa(double la);
|
||||
void Entry();
|
||||
void Quit();
|
||||
|
||||
signals:
|
||||
void resultFound(int note, int octave, double deviation, double frequency);
|
||||
void resultNotFound(double freq);
|
||||
void resultUpdated(const PitchDetection::PitchResult &result);
|
||||
void temperamentListUpdated(const QStringList &list);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -120,6 +120,7 @@ template<typename A> ZeroCross<A>::ZeroCross(const Config &config)
|
|||
|
||||
template<typename sample_t> void ZeroCross<sample_t>::Clear()
|
||||
{
|
||||
last_sample = 0;
|
||||
pattern_current.time = -1;
|
||||
nb_frame_analysed = 0;
|
||||
freq = 0;
|
||||
|
|
|
@ -22,12 +22,13 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "PitchDetection.hpp"
|
||||
#include "Tuner.hpp"
|
||||
|
||||
Q_DECL_EXPORT int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc == 2) {
|
||||
TunerWorker::analyse_file(argv[1]);
|
||||
PitchDetection::analyse_file(argv[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue