Scale: find note, octave and deviation in a temperament
This commit is contained in:
parent
4ec3a83db1
commit
6e6e0f7191
7 changed files with 222 additions and 5 deletions
|
@ -7,12 +7,14 @@ SOURCES += \
|
||||||
src/desktop.cpp \
|
src/desktop.cpp \
|
||||||
src/Tuner.cpp \
|
src/Tuner.cpp \
|
||||||
src/audio/LinearFilter.cpp \
|
src/audio/LinearFilter.cpp \
|
||||||
src/audio/ZeroCross.cpp
|
src/audio/ZeroCross.cpp \
|
||||||
|
src/scale/Scale.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/Tuner.hpp \
|
src/Tuner.hpp \
|
||||||
src/audio/LinearFilter.hpp \
|
src/audio/LinearFilter.hpp \
|
||||||
src/audio/ZeroCross.hpp
|
src/audio/ZeroCross.hpp \
|
||||||
|
src/scale/Scale.hpp
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
qml/desktop.qrc
|
qml/desktop.qrc
|
||||||
|
|
|
@ -12,9 +12,11 @@ SOURCES += \
|
||||||
src/sailfish.cpp \
|
src/sailfish.cpp \
|
||||||
src/Tuner.cpp \
|
src/Tuner.cpp \
|
||||||
src/audio/LinearFilter.cpp \
|
src/audio/LinearFilter.cpp \
|
||||||
src/audio/ZeroCross.cpp
|
src/audio/ZeroCross.cpp \
|
||||||
|
src/scale/Scale.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/Tuner.hpp \
|
src/Tuner.hpp \
|
||||||
src/audio/LinearFilter.hpp \
|
src/audio/LinearFilter.hpp \
|
||||||
src/audio/ZeroCross.hpp
|
src/audio/ZeroCross.hpp \
|
||||||
|
src/scale/Scale.hpp
|
||||||
|
|
|
@ -23,5 +23,14 @@ Column {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var freq = simpleText.createObject(this)
|
var freq = simpleText.createObject(this)
|
||||||
freq.text = Qt.binding(function () { return tuner ? tuner.freq.toFixed(2) + " Hz" : "NaN" })
|
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) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,17 @@ static double b10[] = { 0.99608071, -2.98824212, 2.98824212, -0.99608071 };
|
||||||
Tuner::Tuner()
|
Tuner::Tuner()
|
||||||
{
|
{
|
||||||
running = false;
|
running = false;
|
||||||
|
freq = deviation = 0;
|
||||||
|
note = octave = 0;
|
||||||
|
|
||||||
high_filter = new LinearFilter<int16_t>(3, a10, b10);
|
high_filter = new LinearFilter<int16_t>(3, a10, b10);
|
||||||
|
|
||||||
ZeroCross<int16_t>::Config cross_config({rate, defaultNbFrame, defaultFreqMin, defaultFreqMax});
|
ZeroCross<int16_t>::Config cross_config({rate, defaultNbFrame, defaultFreqMin, defaultFreqMax});
|
||||||
cross = new ZeroCross<int16_t>(cross_config);
|
cross = new ZeroCross<int16_t>(cross_config);
|
||||||
|
|
||||||
|
scale = new Scale();
|
||||||
|
scale->ConstructEqualTemperament();
|
||||||
|
|
||||||
settings.setCodec("audio/PCM");
|
settings.setCodec("audio/PCM");
|
||||||
settings.setChannelCount(1);
|
settings.setChannelCount(1);
|
||||||
settings.setSampleRate(rate);
|
settings.setSampleRate(rate);
|
||||||
|
@ -42,6 +47,7 @@ Tuner::~Tuner()
|
||||||
delete high_filter;
|
delete high_filter;
|
||||||
delete cross;
|
delete cross;
|
||||||
delete recorder;
|
delete recorder;
|
||||||
|
delete scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuner::Start()
|
void Tuner::Start()
|
||||||
|
@ -78,6 +84,15 @@ void Tuner::AudioCb(const QAudioBuffer &buffer)
|
||||||
if (freq != cross->Freq()) {
|
if (freq != cross->Freq()) {
|
||||||
freq = cross->Freq();
|
freq = cross->Freq();
|
||||||
freqChanged();
|
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;
|
return freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Tuner::GetNote()
|
||||||
|
{
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Tuner::GetDeviation()
|
||||||
|
{
|
||||||
|
return deviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Tuner::GetOctave()
|
||||||
|
{
|
||||||
|
return octave;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Tuner::GetNoteName()
|
||||||
|
{
|
||||||
|
return scale->NoteName(note);
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
|
|
||||||
#include "audio/LinearFilter.hpp"
|
#include "audio/LinearFilter.hpp"
|
||||||
#include "audio/ZeroCross.hpp"
|
#include "audio/ZeroCross.hpp"
|
||||||
|
#include "scale/Scale.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 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:
|
private:
|
||||||
QAudioRecorder *recorder;
|
QAudioRecorder *recorder;
|
||||||
|
@ -16,9 +21,11 @@ class Tuner : public QObject {
|
||||||
|
|
||||||
LinearFilter<int16_t> *high_filter;
|
LinearFilter<int16_t> *high_filter;
|
||||||
ZeroCross<int16_t> *cross;
|
ZeroCross<int16_t> *cross;
|
||||||
|
Scale *scale;
|
||||||
|
|
||||||
bool running;
|
bool running;
|
||||||
double freq;
|
double freq, deviation;
|
||||||
|
int note, octave;
|
||||||
|
|
||||||
static const int rate = 16000;
|
static const int rate = 16000;
|
||||||
static const int defaultNbFrame = 1024;
|
static const int defaultNbFrame = 1024;
|
||||||
|
@ -40,8 +47,16 @@ class Tuner : public QObject {
|
||||||
bool GetRunning();
|
bool GetRunning();
|
||||||
void SetRunning(bool r);
|
void SetRunning(bool r);
|
||||||
double GetFreq();
|
double GetFreq();
|
||||||
|
int GetNote();
|
||||||
|
int GetOctave();
|
||||||
|
double GetDeviation();
|
||||||
|
QString GetNoteName();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void runningChanged();
|
void runningChanged();
|
||||||
void freqChanged();
|
void freqChanged();
|
||||||
|
void noteChanged();
|
||||||
|
void noteNameChanged();
|
||||||
|
void octaveChanged();
|
||||||
|
void deviationChanged();
|
||||||
};
|
};
|
||||||
|
|
105
src/scale/Scale.cpp
Normal file
105
src/scale/Scale.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include "Scale.hpp"
|
||||||
|
|
||||||
|
const char * Scale::noteNames[] = {
|
||||||
|
"do", "do#", "ré", "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
49
src/scale/Scale.hpp
Normal 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
|
Loading…
Reference in a new issue