Skip to content

Commit

Permalink
Redesign and improve accuracy
Browse files Browse the repository at this point in the history
+ Remove TunerNote.cpp/TunerNote.h
  • Loading branch information
sakertooth committed Aug 29, 2023
1 parent 874ef43 commit 4d44de9
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 297 deletions.
2 changes: 1 addition & 1 deletion plugins/Tuner/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ FIND_PACKAGE(Aubio REQUIRED)
INCLUDE_DIRECTORIES(${AUBIO_INCLUDE_DIRS})
LINK_LIBRARIES(${AUBIO_LIBRARY})

BUILD_PLUGIN(tuner Tuner.cpp TunerControls.cpp TunerControlDialog.cpp TunerNote.cpp MOCFILES Tuner.h TunerControls.h TunerControlDialog.h TunerNote.h)
BUILD_PLUGIN(tuner Tuner.cpp TunerControls.cpp TunerControlDialog.cpp MOCFILES Tuner.h TunerControls.h TunerControlDialog.h)
48 changes: 14 additions & 34 deletions plugins/Tuner/Tuner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,17 @@ Plugin* PLUGIN_EXPORT lmms_plugin_main(Model* parent, void* _data)
Tuner::Tuner(Model* parent, const Descriptor::SubPluginFeatures::Key* key)
: Effect(&tuner_plugin_descriptor, parent, key)
, m_tunerControls(this)
, m_referenceNote(TunerNote::NoteName::A, 4, 440.0f)
, m_aubioPitch(
new_aubio_pitch("default", m_aubioWindowSize, m_aubioHopSize, Engine::audioEngine()->processingSampleRate()))
, m_aubioInputBuffer(new_fvec(m_aubioHopSize))
, m_aubioOutputBuffer(new_fvec(1))
, m_intervalStart(std::chrono::system_clock::now())
, m_interval(100)
, m_referenceFrequency(440.0f)
, m_aubioPitch(new_aubio_pitch("default", m_windowSize, m_hopSize, Engine::audioEngine()->processingSampleRate()))
, m_inputBuffer(new_fvec(m_hopSize))
, m_outputBuffer(new_fvec(1))
{
}

Tuner::~Tuner()
{
del_fvec(m_aubioInputBuffer);
del_fvec(m_aubioOutputBuffer);
del_fvec(m_inputBuffer);
del_fvec(m_outputBuffer);
del_aubio_pitch(m_aubioPitch);
}

Expand All @@ -74,43 +71,26 @@ bool Tuner::processAudioBuffer(sampleFrame* buf, const fpp_t frames)
outSum += buf[f][0] * buf[f][0] + buf[f][1] * buf[f][1];
}

auto duration
= std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - m_intervalStart);
if (outSum > 0.0f && duration.count() >= m_interval.count())
if (outSum > 0.0f)
{
int numFramesToAdd = std::min(m_aubioHopSize - m_aubioFramesCounter, static_cast<int>(frames));
if (numFramesToAdd > 0)
for (int i = 0; i < frames; ++i)
{
for (int i = 0; i < numFramesToAdd; ++i)
if (m_samplesCounter % m_hopSize == 0 && m_samplesCounter > 0)
{
fvec_set_sample(m_aubioInputBuffer, (buf[i][0] + buf[i][1]) * 0.5f, m_aubioFramesCounter + i);
aubio_pitch_do(m_aubioPitch, m_inputBuffer, m_outputBuffer);
emit frequencyCalculated(fvec_get_sample(m_outputBuffer, 0));
m_samplesCounter = 0;
}

m_aubioFramesCounter += numFramesToAdd;
}
else
{
aubio_pitch_do(m_aubioPitch, m_aubioInputBuffer, m_aubioOutputBuffer);

float pitch = fvec_get_sample(m_aubioOutputBuffer, 0);
auto note = m_referenceNote.calculateNoteFromFrequency(pitch);
m_aubioFramesCounter = 0;

m_tunerControls.updateView(note);
m_intervalStart = std::chrono::system_clock::now();
fvec_set_sample(m_inputBuffer, (buf[i][0] + buf[i][1]) * 0.5f, m_samplesCounter);
++m_samplesCounter;
}
}

checkGate(outSum / frames);
return isRunning();
}

void Tuner::syncReferenceFrequency()
{
float reference_freq = m_tunerControls.m_referenceFreqModel.value();
m_referenceNote.setFrequency(reference_freq);
}

EffectControls* Tuner::controls()
{
return &m_tunerControls;
Expand Down
21 changes: 9 additions & 12 deletions plugins/Tuner/Tuner.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,31 @@

#include "Effect.h"
#include "TunerControls.h"
#include "TunerNote.h"

class Tuner : public Effect
{
Q_OBJECT
public:
Tuner(Model* parent, const Descriptor::SubPluginFeatures::Key* key);
~Tuner() override;

bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override;
EffectControls* controls() override;

void syncReferenceFrequency();
signals:
void frequencyCalculated(float frequency);

private:
TunerControls m_tunerControls;
TunerNote m_referenceNote;
float m_referenceFrequency = 0;

int m_aubioFramesCounter = 0;
int m_aubioWindowSize = 8192;
int m_aubioHopSize = 4096;
const int m_windowSize = 8192;
const int m_hopSize = 128;
int m_samplesCounter = 0;

aubio_pitch_t* m_aubioPitch;
fvec_t* m_aubioInputBuffer;
fvec_t* m_aubioOutputBuffer;

std::chrono::time_point<std::chrono::system_clock> m_intervalStart;
std::chrono::milliseconds m_interval;

fvec_t* m_inputBuffer;
fvec_t* m_outputBuffer;
friend class TunerControls;
};

Expand Down
142 changes: 126 additions & 16 deletions plugins/Tuner/TunerControlDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,146 @@
#include "TunerControlDialog.h"

#include <QLabel>
#include <QPainter>
#include <QVBoxLayout>
#include <QSizePolicy>

#include "LcdFloatSpinBox.h"
#include "LcdSpinBox.h"
#include "Tuner.h"
#include "TunerControls.h"

#include <cmath>
#include <iostream>

TunerControlDialog::TunerControlDialog(TunerControls* controls)
: EffectControlDialog(controls)
, m_centsWidget(new LcdWidget(3, this, tr("Cents")))
, m_freqWidget(new LcdWidget(4, this, tr("Frequency")))
, m_noteLabel(new QLabel(this))
{
setFixedSize(240, 240);
setAutoFillBackground(true);

auto pal = QPalette();
pal.setColor(QPalette::Window, QColor{18, 19, 22});
setPalette(pal);

m_centsWidget->setValue(0);
m_centsWidget->setLabel(tr("Cents"));
m_centsWidget->move(7, 210);
m_noteLabel = new QLabel();
m_noteLabel->setFont(QFont{"Arial", 32, QFont::Bold});

m_freqWidget->setValue(0);
m_freqWidget->setLabel(tr("Frequency"));
m_freqWidget->move(100, 210);
m_octaveLabel = new QLabel();
m_octaveLabel->setFont(QFont{"Arial", 8, QFont::Bold});

m_noteLabel->setFont(QFont("Arial", 32));
m_noteLabel->setText("-");
m_noteLabel->setFixedWidth(width());
m_noteLabel->setAlignment(Qt::AlignCenter);
m_noteLabel->move(0, height() / 2 - m_noteLabel->font().pointSize());
m_centsLabel = new QLabel();
m_centsLabel->setFont(QFont{"Arial", 16, QFont::Bold});

LcdSpinBox* referenceFreqSpinBox = new LcdSpinBox(3, this, tr("Reference"));
auto noteLayout = new QHBoxLayout();
noteLayout->addStretch(1);
noteLayout->addWidget(m_noteLabel, 0, Qt::AlignRight);
noteLayout->addWidget(m_octaveLabel);
noteLayout->addWidget(m_centsLabel, 1);

auto referenceFreqSpinBox = new LcdSpinBox(3, this, tr("Reference"));
referenceFreqSpinBox->setModel(&controls->m_referenceFreqModel);
referenceFreqSpinBox->setLabel(tr("Reference"));
referenceFreqSpinBox->move(193, 210);

auto layout = new QVBoxLayout(this);
layout->setSpacing(1);
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(noteLayout);
layout->addWidget(referenceFreqSpinBox, 0, Qt::AlignRight);

QObject::connect(controls->m_tuner, &Tuner::frequencyCalculated, this, [this](float frequency){ frequencyCalculated(frequency); });
}

void TunerControlDialog::paintEvent(QPaintEvent* event)
{
auto painter = QPainter(this);
painter.setPen(Qt::white);
painter.drawArc(QRectF{20.0, 50.0, 200.0, 60.0}, 30 * 16, 120 * 16);

painter.setBrush(Qt::white);
painter.drawEllipse(QPoint{120, 50}, 8, 8);
}

void TunerControlDialog::frequencyCalculated(float frequency)
{
// A4 = referenceFrequency
const float referenceFrequency = static_cast<TunerControls*>(m_effectControls)->m_referenceFreqModel.value();
const int centsFromReference = std::round(1200.0f * std::log2f(frequency / referenceFrequency));

const int octavesFromReference = std::round(centsFromReference / 1200.0f);
const int octaveOfNote = 4 + octavesFromReference;

int centsRemaining = (centsFromReference - (octavesFromReference * 1200));
int semitonesFromReference = centsRemaining / 100;
int centsOfNote = centsRemaining % 100;

// If it is over 50ct, we can roll over to the nearest semitone
if (centsOfNote >= 50 || centsOfNote <= -50)
{
const auto centsOfNoteNormalized = centsOfNote / std::abs(centsOfNote);
semitonesFromReference += centsOfNoteNormalized;
centsOfNote = 100 - (centsOfNote * centsOfNoteNormalized);
}

if (semitonesFromReference < 0) { semitonesFromReference += 12; }
std::string note = "";

switch (static_cast<NoteName>(semitonesFromReference))
{
case NoteName::A:
note = "A";
break;
case NoteName::ASharp:
note = "A#";
break;
case NoteName::B:
note = "B";
break;
case NoteName::C:
note = "C";
break;
case NoteName::CSharp:
note = "C#";
break;
case NoteName::D:
note = "D";
break;
case NoteName::DSharp:
note = "D#";
break;
case NoteName::E:
note = "E";
break;
case NoteName::F:
note = "F";
break;
case NoteName::FSharp:
note = "F#";
break;
case NoteName::G:
note = "G";
break;
case NoteName::GSharp:
note = "G#";
break;
default:
note = "";
};

m_noteLabel->setText(QString::fromStdString(note));
if (octaveOfNote >= -1) { m_octaveLabel->setText(QString::number(octaveOfNote)); };
m_centsLabel->setText((centsOfNote >= 0 ? "+" : "") + QString::number(centsOfNote) + "ct");

if (centsOfNote >= 0 && centsOfNote <= 10)
{
m_centsLabel->setStyleSheet("QLabel { color : green; }");
}
else if (centsOfNote > 10 && centsOfNote <= 30)
{
m_centsLabel->setStyleSheet("QLabel { color : yellow; }");
}
else if (centsOfNote > 30)
{
m_centsLabel->setStyleSheet("QLabel { color : red; }");
}
}
21 changes: 20 additions & 1 deletion plugins/Tuner/TunerControlDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,32 @@ class TunerControls;
class TunerControlDialog : public EffectControlDialog
{
public:
enum class NoteName
{
A,
ASharp,
B,
C,
CSharp,
D,
DSharp,
E,
F,
FSharp,
G,
GSharp
};

TunerControlDialog(TunerControls* controls);
void paintEvent(QPaintEvent* event) override;
void frequencyCalculated(float frequency);

private:
LcdWidget* m_centsWidget;
LcdWidget* m_freqWidget;
QLabel* m_noteLabel;

QLabel* m_octaveLabel;
QLabel* m_centsLabel;
friend class TunerControls;
};

Expand Down
18 changes: 6 additions & 12 deletions plugins/Tuner/TunerControls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@
*
*/

#include "Tuner.h"
#include "TunerControls.h"

#include <QDomElement>

#include "Tuner.h"

TunerControls::TunerControls(Tuner* tuner)
: EffectControls(tuner)
, m_tuner(tuner)
, m_referenceFreqModel(440, 0, 999)
{
QObject::connect(&m_referenceFreqModel, &LcdSpinBoxModel::dataChanged, tuner, &Tuner::syncReferenceFrequency);
QObject::connect(&m_referenceFreqModel, &LcdSpinBoxModel::dataChanged, tuner, [this, tuner]
{
tuner->m_referenceFrequency = m_referenceFreqModel.value();
});
}

void TunerControls::saveSettings(QDomDocument& domDocument, QDomElement& domElement)
Expand All @@ -58,13 +60,5 @@ int TunerControls::controlCount()

EffectControlDialog* TunerControls::createView()
{
m_tunerDialog = new TunerControlDialog(this);
return m_tunerDialog;
}

void TunerControls::updateView(TunerNote note)
{
m_tunerDialog->m_noteLabel->setText(QString::fromStdString(note.fullNoteName()));
m_tunerDialog->m_freqWidget->setValue(note.frequency());
m_tunerDialog->m_centsWidget->setValue(note.cents());
return new TunerControlDialog(this);
}
2 changes: 0 additions & 2 deletions plugins/Tuner/TunerControls.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include "EffectControls.h"
#include "LcdSpinBox.h"
#include "TunerControlDialog.h"
#include "TunerNote.h"

class Tuner;
class TunerControls : public EffectControls
Expand All @@ -43,7 +42,6 @@ class TunerControls : public EffectControls
int controlCount() override;

EffectControlDialog* createView() override;
void updateView(TunerNote note);

private:
Tuner* m_tuner = nullptr;
Expand Down
Loading

0 comments on commit 4d44de9

Please sign in to comment.