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/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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
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