Scale: find note, octave and deviation in a temperament

This commit is contained in:
Louis-Joseph Fournier 2015-12-27 18:31:26 +01:00
parent 4ec3a83db1
commit 6e6e0f7191
7 changed files with 222 additions and 5 deletions

View file

@ -7,12 +7,14 @@ SOURCES += \
src/desktop.cpp \
src/Tuner.cpp \
src/audio/LinearFilter.cpp \
src/audio/ZeroCross.cpp
src/audio/ZeroCross.cpp \
src/scale/Scale.cpp
HEADERS += \
src/Tuner.hpp \
src/audio/LinearFilter.hpp \
src/audio/ZeroCross.hpp
src/audio/ZeroCross.hpp \
src/scale/Scale.hpp
RESOURCES += \
qml/desktop.qrc

View file

@ -12,9 +12,11 @@ SOURCES += \
src/sailfish.cpp \
src/Tuner.cpp \
src/audio/LinearFilter.cpp \
src/audio/ZeroCross.cpp
src/audio/ZeroCross.cpp \
src/scale/Scale.cpp
HEADERS += \
src/Tuner.hpp \
src/audio/LinearFilter.hpp \
src/audio/ZeroCross.hpp
src/audio/ZeroCross.hpp \
src/scale/Scale.hpp

View file

@ -23,5 +23,14 @@ Column {
Component.onCompleted: {
var freq = simpleText.createObject(this)
freq.text = Qt.binding(function () { return tuner ? tuner.freq.toFixed(2) + " Hz" : "NaN" })
var note = simpleText.createObject(this)
note.text = Qt.binding(function() { return tuner.noteName })
var octave = simpleText.createObject(this)
octave.text = Qt.binding(function() { return tuner.octave })
var deviation = simpleText.createObject(this)
deviation.text = Qt.binding(function() { return tuner.deviation.toFixed(3) })
}
}

View file

@ -15,12 +15,17 @@ static double b10[] = { 0.99608071, -2.98824212, 2.98824212, -0.99608071 };
Tuner::Tuner()
{
running = false;
freq = deviation = 0;
note = octave = 0;
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();
scale->ConstructEqualTemperament();
settings.setCodec("audio/PCM");
settings.setChannelCount(1);
settings.setSampleRate(rate);
@ -42,6 +47,7 @@ Tuner::~Tuner()
delete high_filter;
delete cross;
delete recorder;
delete scale;
}
void Tuner::Start()
@ -78,6 +84,15 @@ void Tuner::AudioCb(const QAudioBuffer &buffer)
if (freq != cross->Freq()) {
freq = cross->Freq();
freqChanged();
if (freq) {
note = scale->FindNote(freq, octave, deviation);
noteChanged();
noteNameChanged();
octaveChanged();
deviationChanged();
//std::cerr << note << " " << scale->NoteName(note) << std::endl;
}
}
}
@ -98,3 +113,23 @@ double Tuner::GetFreq()
{
return freq;
}
int Tuner::GetNote()
{
return note;
}
double Tuner::GetDeviation()
{
return deviation;
}
int Tuner::GetOctave()
{
return octave;
}
QString Tuner::GetNoteName()
{
return scale->NoteName(note);
}

View file

@ -3,11 +3,16 @@
#include "audio/LinearFilter.hpp"
#include "audio/ZeroCross.hpp"
#include "scale/Scale.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)
Q_PROPERTY(double deviation READ GetDeviation NOTIFY deviationChanged)
Q_PROPERTY(int note READ GetNote NOTIFY noteChanged)
Q_PROPERTY(int octave READ GetOctave NOTIFY octaveChanged)
Q_PROPERTY(QString noteName READ GetNoteName NOTIFY noteNameChanged)
private:
QAudioRecorder *recorder;
@ -16,9 +21,11 @@ class Tuner : public QObject {
LinearFilter<int16_t> *high_filter;
ZeroCross<int16_t> *cross;
Scale *scale;
bool running;
double freq;
double freq, deviation;
int note, octave;
static const int rate = 16000;
static const int defaultNbFrame = 1024;
@ -40,8 +47,16 @@ class Tuner : public QObject {
bool GetRunning();
void SetRunning(bool r);
double GetFreq();
int GetNote();
int GetOctave();
double GetDeviation();
QString GetNoteName();
signals:
void runningChanged();
void freqChanged();
void noteChanged();
void noteNameChanged();
void octaveChanged();
void deviationChanged();
};

105
src/scale/Scale.cpp Normal file
View file

@ -0,0 +1,105 @@
#include <assert.h>
#include <math.h>
#include <iostream>
#include "Scale.hpp"
const char * Scale::noteNames[] = {
"do", "do#", "", "mib", "mi", "fa", "fa#", "sol",
"sol#", "la", "sib", "si"
};
const char * Scale::NoteName(int note)
{
if (note < 0 || note >= nbNote) return "";
return noteNames[note];
}
Scale::Scale()
{
actualLa = defaultLa;
}
void Scale::updateScale()
{
actualFactor = actualLa / defaultLa;
for (int i = 0; i < nbNote; i++) actualNoteFreq[i] = actualFactor * noteFreq[i];
actualRange[0] = actualNoteFreq[0] * pow(halfToneFactor(0, -1),0.5);
actualRange[1] = actualNoteFreq[nbNote - 1] * pow(halfToneFactor(nbNote - 1, 1), 0.5);
//std::cerr << actualRange[0] << " " << actualRange[1] << std::endl;
}
double Scale::halfToneFactor(int note, int sign)
{
double a, b;
a = noteFreq[note];
if (sign < 0) {
if (note == 0) b = noteFreq[nbNote - 1] / 2;
else b = noteFreq[note - 1];
}
else {
if (note == nbNote - 1) b = noteFreq[0] * 2;
else b = noteFreq[note + 1];
}
return b / a;
}
int Scale::findOctave(double &freq)
{
int octave = cmpOctave;
while (freq > actualRange[1]) freq /= 2, octave++;
while (freq < actualRange[0]) freq *= 2, octave--;
return octave;
}
int Scale::FindNote(double freq, int &octave, double &deviation)
{
assert (freq > 0);
int note = 0;
octave = findOctave(freq);
std::cerr << octave << " " << freq << std::endl;
assert(freq >= actualRange[0] && freq <= actualRange[1]);
while (actualNoteFreq[note] < freq && note < nbNote - 1) note++;
if (note > 0) {
if (freq / actualNoteFreq[note - 1] < actualNoteFreq[note] / freq) note--;
}
int sign = freq > actualNoteFreq[note] ? 1 : -1;
if (sign == 1) deviation = log(freq / actualNoteFreq[note]) / log(halfToneFactor(note, sign));
else deviation = log(actualNoteFreq[note] / freq) / log(halfToneFactor(note, sign));
return note;
}
void Scale::ConstructEqualTemperament()
{
const int la = 9;
int i;
noteFreq[la] = defaultLa;
for (i = 0; i < nbNote; i++) if (i != la) {
noteFreq[i] = defaultLa * pow(2, (double) (i - la) / 12);
//std::cerr << noteFreq[i] << std::endl;
}
updateScale();
}
double Scale::GetLa()
{
return actualLa;
}
void Scale::SetLa(double la)
{
assert(la > 0);
actualLa = la;
updateScale();
}

49
src/scale/Scale.hpp Normal file
View file

@ -0,0 +1,49 @@
#ifndef _SCALE_HPP
#define _SCALE_HPP
/**
* Note recognition within a temperament
*/
class Scale {
private:
static const int nbNote = 12;
static const int cmpOctave = 4;
static const int defaultLa = 440;
static const char *noteNames[nbNote];
double noteFreq[nbNote], actualNoteFreq[nbNote];
double actualLa, actualFactor;
double actualRange[2]; // freq range for default octave
/// update scale after temperament or default la change
void updateScale();
/**
* give the half-tone frequency factor
* beetween note and previous or next
*/
double halfToneFactor(int note, int sign);
/// find octave and update freq in range
int findOctave(double &freq);
public:
Scale();
~Scale() {}
void ConstructEqualTemperament();
double GetLa();
void SetLa(double la);
/**
* Find nearest note, octave, and deviation
*/
int FindNote(double freq, int &octave, double &deviation);
static const char * NoteName(int note);
};
#endif