diff --git a/plugins/Tuner/CMakeLists.txt b/plugins/Tuner/CMakeLists.txt index 2fc4bf2b237..b8d4c8c9197 100644 --- a/plugins/Tuner/CMakeLists.txt +++ b/plugins/Tuner/CMakeLists.txt @@ -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) diff --git a/plugins/Tuner/Tuner.cpp b/plugins/Tuner/Tuner.cpp index e4d47bac250..e449196506d 100644 --- a/plugins/Tuner/Tuner.cpp +++ b/plugins/Tuner/Tuner.cpp @@ -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); } @@ -74,30 +71,19 @@ 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::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(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; } } @@ -105,12 +91,6 @@ bool Tuner::processAudioBuffer(sampleFrame* buf, const fpp_t 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; diff --git a/plugins/Tuner/Tuner.h b/plugins/Tuner/Tuner.h index 820583bb104..39ed3085e73 100644 --- a/plugins/Tuner/Tuner.h +++ b/plugins/Tuner/Tuner.h @@ -31,10 +31,10 @@ #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; @@ -42,23 +42,20 @@ class Tuner : public Effect 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 m_intervalStart; - std::chrono::milliseconds m_interval; - + fvec_t* m_inputBuffer; + fvec_t* m_outputBuffer; friend class TunerControls; }; diff --git a/plugins/Tuner/TunerControlDialog.cpp b/plugins/Tuner/TunerControlDialog.cpp index 63e988d4be6..210a0ce26f8 100644 --- a/plugins/Tuner/TunerControlDialog.cpp +++ b/plugins/Tuner/TunerControlDialog.cpp @@ -25,36 +25,146 @@ #include "TunerControlDialog.h" #include +#include +#include +#include #include "LcdFloatSpinBox.h" #include "LcdSpinBox.h" #include "Tuner.h" #include "TunerControls.h" +#include +#include + 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(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(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; }"); + } } \ No newline at end of file diff --git a/plugins/Tuner/TunerControlDialog.h b/plugins/Tuner/TunerControlDialog.h index 3689c65ec80..ed21d4b2d7b 100644 --- a/plugins/Tuner/TunerControlDialog.h +++ b/plugins/Tuner/TunerControlDialog.h @@ -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; }; diff --git a/plugins/Tuner/TunerControls.cpp b/plugins/Tuner/TunerControls.cpp index 66f34329e26..f046d392838 100644 --- a/plugins/Tuner/TunerControls.cpp +++ b/plugins/Tuner/TunerControls.cpp @@ -22,18 +22,20 @@ * */ +#include "Tuner.h" #include "TunerControls.h" #include -#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) @@ -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); } \ No newline at end of file diff --git a/plugins/Tuner/TunerControls.h b/plugins/Tuner/TunerControls.h index 033f57e6c61..6687272e1df 100644 --- a/plugins/Tuner/TunerControls.h +++ b/plugins/Tuner/TunerControls.h @@ -28,7 +28,6 @@ #include "EffectControls.h" #include "LcdSpinBox.h" #include "TunerControlDialog.h" -#include "TunerNote.h" class Tuner; class TunerControls : public EffectControls @@ -43,7 +42,6 @@ class TunerControls : public EffectControls int controlCount() override; EffectControlDialog* createView() override; - void updateView(TunerNote note); private: Tuner* m_tuner = nullptr; diff --git a/plugins/Tuner/TunerNote.cpp b/plugins/Tuner/TunerNote.cpp deleted file mode 100644 index 9b445057c00..00000000000 --- a/plugins/Tuner/TunerNote.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * TunerNote.cpp - * - * Copyright (c) 2022 sakertooth - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "TunerNote.h" - -#include -#include -#include - -TunerNote::TunerNote(NoteName name, int octave, float frequency) - : m_name(name) - , m_octave(octave) - , m_frequency(frequency) -{ -} - -TunerNote TunerNote::calculateNoteFromFrequency(float frequency) -{ - int approximateCentDistance = static_cast(std::roundf(1200 * std::log2(frequency / m_frequency))); - TunerNote note(*this); - note.shiftByCents(approximateCentDistance); - note.setFrequency(frequency); - return note; -} - -void TunerNote::shiftByCents(int cents) -{ - auto [semitonesToShift, remainingCents] = std::div(cents, 100); - m_name = static_cast((((static_cast(m_name) + semitonesToShift) % 12) + 12) % 12); - m_octave += cents / 1200; - m_cents += remainingCents; -} - -void TunerNote::shiftBySemitones(int semitones) -{ - shiftByCents(semitones * 100); -} - -void TunerNote::shiftByOctaves(int octaves) -{ - shiftByCents(octaves * 1200); -} - -TunerNote::NoteName TunerNote::name() const -{ - return m_name; -} - -int TunerNote::octave() const -{ - return m_octave; -} - -int TunerNote::cents() const -{ - return m_cents; -} - -float TunerNote::frequency() const -{ - return m_frequency; -} - -std::string TunerNote::nameToStr() const -{ - switch (m_name) - { - case NoteName::A: - return "A"; - case NoteName::ASharp: - return "A#"; - case NoteName::B: - return "B"; - case NoteName::C: - return "C"; - case NoteName::CSharp: - return "C#"; - case NoteName::D: - return "D"; - case NoteName::DSharp: - return "D#"; - case NoteName::E: - return "E"; - case NoteName::F: - return "F"; - case NoteName::FSharp: - return "F#"; - case NoteName::G: - return "G"; - case NoteName::GSharp: - return "G#"; - default: - return "N/A"; - }; -} - -std::string TunerNote::fullNoteName() const -{ - return nameToStr() + std::to_string(m_octave); -} - -void TunerNote::setName(NoteName name) -{ - m_name = name; -} - -void TunerNote::setOctave(int octave) -{ - m_octave = octave; -} - -void TunerNote::setCents(int cents) -{ - m_cents = cents; -} - -void TunerNote::setFrequency(float frequency) -{ - m_frequency = frequency; -} \ No newline at end of file diff --git a/plugins/Tuner/TunerNote.h b/plugins/Tuner/TunerNote.h deleted file mode 100644 index 48404108d88..00000000000 --- a/plugins/Tuner/TunerNote.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * TunerNote.h - * - * Copyright (c) 2022 sakertooth - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include - -#ifndef TUNER_NOTE_H -#define TUNER_NOTE_H - -class TunerNote -{ -public: - enum class NoteName - { - A, - ASharp, - B, - C, - CSharp, - D, - DSharp, - E, - F, - FSharp, - G, - GSharp, - Count - }; - - TunerNote(NoteName name, int octave, float frequency); - - TunerNote calculateNoteFromFrequency(float frequency); - - void shiftByCents(int cents); - void shiftBySemitones(int semitones); - void shiftByOctaves(int octaves); - - NoteName name() const; - int octave() const; - int cents() const; - float frequency() const; - - std::string nameToStr() const; - std::string fullNoteName() const; - - void setName(NoteName name); - void setOctave(int octave); - void setCents(int cents); - void setFrequency(float frequency); - -private: - NoteName m_name = NoteName::A; - int m_octave = 0; - int m_cents = 0; - float m_frequency = 0.0f; -}; - -#endif \ No newline at end of file diff --git a/plugins/Tuner/tuner_knob.png b/plugins/Tuner/tuner_knob.png new file mode 100644 index 00000000000..929cf55c8b2 Binary files /dev/null and b/plugins/Tuner/tuner_knob.png differ