diff --git a/Tuner.pro b/Tuner.pro index 4d3f892..c542b86 100644 --- a/Tuner.pro +++ b/Tuner.pro @@ -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 diff --git a/harbour-sailtuner.pro b/harbour-sailtuner.pro index 20a1a4b..dc9ce70 100644 --- a/harbour-sailtuner.pro +++ b/harbour-sailtuner.pro @@ -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 diff --git a/qml/SimpleDisplay.qml b/qml/SimpleDisplay.qml index 2d556ca..dc3fb5e 100644 --- a/qml/SimpleDisplay.qml +++ b/qml/SimpleDisplay.qml @@ -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) }) } } diff --git a/src/Tuner.cpp b/src/Tuner.cpp index b6398f1..f4fe208 100644 --- a/src/Tuner.cpp +++ b/src/Tuner.cpp @@ -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(3, a10, b10); ZeroCross::Config cross_config({rate, defaultNbFrame, defaultFreqMin, defaultFreqMax}); cross = new ZeroCross(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); +} diff --git a/src/Tuner.hpp b/src/Tuner.hpp index 3925d41..e9196ba 100644 --- a/src/Tuner.hpp +++ b/src/Tuner.hpp @@ -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 *high_filter; ZeroCross *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(); }; diff --git a/src/scale/Scale.cpp b/src/scale/Scale.cpp new file mode 100644 index 0000000..de3f597 --- /dev/null +++ b/src/scale/Scale.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#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(); +} diff --git a/src/scale/Scale.hpp b/src/scale/Scale.hpp new file mode 100644 index 0000000..62b76e0 --- /dev/null +++ b/src/scale/Scale.hpp @@ -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