Skip to content

Commit

Permalink
Add Tap Tempo (#6375)
Browse files Browse the repository at this point in the history
* Add Tap Tempo Feature (#6375)
Resolves #5181

* Update formatting, use namespaces, etc.

* Use Qt Designer .ui file to handle the UI
Thanks to irrenhaus for the idea
Also added a few buttons I will add functionality for

* Play metronome sound when tapped

* Improve stabilization speed by comparing differences in length between intervals
To improve the speed at which the BPM counter stabilizes at a
certain number, we now compare the differences in length between
the last two taps and the most recent two taps and reset the counter
if the threshold is passed.
I made it so that a difference of 500 ms resets the counter.

* Remove duplicate m_ui
An artifact from my battle with Git and merge conflicts..

* Format TapTempoUi header file

* Add lmms namespace in UI file

* Remove taptempo.ui XML file
Not needed

* Add LMMS_EXPORT to SamplePlayHandle

* Use std::chrono::duration<double, std::milli> for intervals
Co-authored-by: irrenhaus3 <[email protected]>

* Use alias for steady_clock
Co-authored-by: irrenhaus3 <[email protected]>

* Speed up convergence by accounting for recent intervals

This uses a combination of keeping track of a more accurate BPM,
while also using a BPM difference threshold to move towards the true BPM
more quickly.

After three taps, a "recent interval" is calculated, which is similar
to the latest interval, but less fluctuating since it accounts for
three taps instead of one. This allows for comparing between
the BPM on the display with the recent BPM more closely.

We then compare the difference in magnitude of both BPM's
with the threshold. If the threshold is passed, the counter gets reset.

* Remove semicolon from "QOBJECT;" in plugins/TapTempo/TapTempo.h

Co-authored-by: Hyunjin Song <[email protected]>

* Add newline between using alias and constructor

* Cleanup header files

* Add // namespace lmms::gui comment

* Rename header guards in plugins/TapTempo/TapTempo.h

Co-authored-by: Dalton Messmer <[email protected]>

* Rename header guards in plugins/TapTempo/TapTempo.h

Co-authored-by: Dalton Messmer <[email protected]>

* Rename header guards in plugins/TapTempo/TapTempoUi.h

Will merge this file with ``TapTempoView`` soon.

Co-authored-by: Dalton Messmer <[email protected]>

* Rename header guards in plugins/TapTempo/TapTempoUi.h

Co-authored-by: Dalton Messmer <[email protected]>

* Update plugins/TapTempo/TapTempo.h

Co-authored-by: Dalton Messmer <[email protected]>

* Replace virtual with override in plugins/TapTempo/TapTempo.h

Co-authored-by: Dalton Messmer <[email protected]>

* Merge UI file into TapTempoView

* Pass TapTempo* directly into constructor in plugins/TapTempo/TapTempoView.h

Co-authored-by: Dalton Messmer <[email protected]>

* Update style in plugins/TapTempo/TapTempoView.h

Co-authored-by: Dalton Messmer <[email protected]>

* Add parameter name for keyPressEvent function in plugins/TapTempo/TapTempoView.h

Also reorder the function declarations to match the order in the source file.

Co-authored-by: Dalton Messmer <[email protected]>

* Remove dynamic_cast to TapTempo* in plugins/TapTempo/TapTempoView.cpp

Co-authored-by: Dalton Messmer <[email protected]>

* Restrict C linkage to only lmms_plugin_main and plugin descriptor
Co-authored-by: Dalton Messmer <[email protected]>

* Simplify algorithm

* Update labels when calling closeEvent

* Add reset button

* Adjust layout

* Format document

* Only allow the tap button to gain focus

* Use icon provided by LMMS

* Add sync button and adjust formatting

* Make the metronome downbeat the first beat

Also simplify code

Co-authored-by: Dalton Messmer <[email protected]>

* Round BPM values when syncing with project tempo

Co-authored-by: Dalton Messmer <[email protected]>

* Change Plugin::Tool to Plugin::Type::Tool

---------

Co-authored-by: Hyunjin Song <[email protected]>
Co-authored-by: Dalton Messmer <[email protected]>
  • Loading branch information
3 people authored Aug 25, 2023
1 parent f102777 commit a311eed
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmake/modules/PluginList.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ SET(LMMS_PLUGIN_LIST
StereoEnhancer
StereoMatrix
Stk
TapTempo
VstBase
Vestige
VstEffect
Expand Down
2 changes: 1 addition & 1 deletion include/SamplePlayHandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Track;
class AudioPort;


class SamplePlayHandle : public PlayHandle
class LMMS_EXPORT SamplePlayHandle : public PlayHandle
{
public:
SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort = true );
Expand Down
3 changes: 3 additions & 0 deletions plugins/TapTempo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INCLUDE(BuildPlugin)

BUILD_PLUGIN(taptempo TapTempo.cpp TapTempoView.cpp MOCFILES TapTempo.h TapTempoView.h EMBEDDED_RESOURCES logo.png)
85 changes: 85 additions & 0 deletions plugins/TapTempo/TapTempo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* TapTempo.cpp - Plugin to count beats per minute
*
*
* Copyright (c) 2022 saker <[email protected]>
*
*
* 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 "TapTempo.h"

#include <QFont>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <cfloat>
#include <cmath>
#include <string>

#include "AudioEngine.h"
#include "Engine.h"
#include "LedCheckBox.h"
#include "SamplePlayHandle.h"
#include "Song.h"
#include "embed.h"
#include "plugin_export.h"

namespace lmms {

extern "C" {
Plugin::Descriptor PLUGIN_EXPORT taptempo_plugin_descriptor
= {LMMS_STRINGIFY(PLUGIN_NAME), "Tap Tempo", QT_TRANSLATE_NOOP("PluginBrowser", "Tap to the beat"),
"saker <[email protected]>", 0x0100, Plugin::Type::Tool, new PluginPixmapLoader("logo"), nullptr, nullptr};

PLUGIN_EXPORT Plugin* lmms_plugin_main(Model*, void*)
{
return new TapTempo;
}
}

TapTempo::TapTempo()
: ToolPlugin(&taptempo_plugin_descriptor, nullptr)
{
}

void TapTempo::onBpmClick()
{
const auto currentTime = clock::now();
if (m_numTaps == 0)
{
m_startTime = currentTime;
}
else
{
using namespace std::chrono_literals;
const auto secondsElapsed = (currentTime - m_startTime) / 1.0s;
if (m_numTaps >= m_tapsNeededToDisplay) { m_bpm = m_numTaps / secondsElapsed * 60; }
}

++m_numTaps;
}

QString TapTempo::nodeName() const
{
return taptempo_plugin_descriptor.name;
}
} // namespace lmms
61 changes: 61 additions & 0 deletions plugins/TapTempo/TapTempo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* TapTempo.h - Plugin to count beats per minute
*
* Copyright (c) 2022 saker <[email protected]>
*
* 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.
*
*/

#ifndef LMMS_TAP_TEMPO_H
#define LMMS_TAP_TEMPO_H

#include <chrono>

#include "TapTempoView.h"
#include "ToolPlugin.h"

namespace lmms {

class TapTempo : public ToolPlugin
{
Q_OBJECT
public:
using clock = std::chrono::steady_clock;

TapTempo();
void onBpmClick();

QString nodeName() const override;
void saveSettings(QDomDocument&, QDomElement&) override {}
void loadSettings(const QDomElement&) override {}

gui::PluginView* instantiateView(QWidget*) override { return new gui::TapTempoView(this); }

private:
std::chrono::time_point<clock> m_startTime;
int m_numTaps = 0;
int m_tapsNeededToDisplay = 2;
double m_bpm = 0.0;
bool m_showDecimal = false;

friend class gui::TapTempoView;
};
} // namespace lmms

#endif // LMMS_TAP_TEMPO_H
168 changes: 168 additions & 0 deletions plugins/TapTempo/TapTempoView.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* TapTempoView.cpp - Plugin to count beats per minute
*
*
* Copyright (c) 2022 saker <[email protected]>
*
*
* 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 "TapTempoView.h"

#include <QCheckBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QVariant>
#include <QWidget>

#include "Engine.h"
#include "SamplePlayHandle.h"
#include "Song.h"
#include "TapTempo.h"

namespace lmms::gui {
TapTempoView::TapTempoView(TapTempo* plugin)
: ToolPluginView(plugin)
, m_plugin(plugin)
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

auto font = QFont();
font.setPointSize(24);

m_tapButton = new QPushButton();
m_tapButton->setFixedSize(200, 200);
m_tapButton->setFont(font);
m_tapButton->setText(tr("0"));

auto precisionCheckBox = new QCheckBox(tr("Precision"));
precisionCheckBox->setFocusPolicy(Qt::NoFocus);
precisionCheckBox->setToolTip(tr("Display in high precision"));
precisionCheckBox->setText(tr("Precision"));

auto muteCheckBox = new QCheckBox(tr("0.0 ms"));
muteCheckBox->setFocusPolicy(Qt::NoFocus);
muteCheckBox->setToolTip(tr("Mute metronome"));
muteCheckBox->setText(tr("Mute"));

m_msLabel = new QLabel();
m_msLabel->setFocusPolicy(Qt::NoFocus);
m_msLabel->setToolTip(tr("BPM in milliseconds"));
m_msLabel->setText(tr("0 ms"));

m_hzLabel = new QLabel();
m_hzLabel->setFocusPolicy(Qt::NoFocus);
m_hzLabel->setToolTip(tr("Frequency of BPM"));
m_hzLabel->setText(tr("0.0000 hz"));

auto resetButton = new QPushButton(tr("Reset"));
resetButton->setFocusPolicy(Qt::NoFocus);
resetButton->setToolTip(tr("Reset counter and sidebar information"));

auto syncButton = new QPushButton(tr("Sync"));
syncButton->setFocusPolicy(Qt::NoFocus);
syncButton->setToolTip(tr("Sync with project tempo"));

auto optionLayout = new QVBoxLayout();
optionLayout->addWidget(precisionCheckBox);
optionLayout->addWidget(muteCheckBox);

auto bpmInfoLayout = new QVBoxLayout();
bpmInfoLayout->addWidget(m_msLabel, 0, Qt::AlignHCenter);
bpmInfoLayout->addWidget(m_hzLabel, 0, Qt::AlignHCenter);

auto sidebarLayout = new QHBoxLayout();
sidebarLayout->addLayout(optionLayout);
sidebarLayout->addLayout(bpmInfoLayout);

auto buttonsLayout = new QHBoxLayout();
buttonsLayout->addWidget(resetButton, 0, Qt::AlignCenter);
buttonsLayout->addWidget(syncButton, 0, Qt::AlignCenter);

auto mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(m_tapButton, 0, Qt::AlignCenter);
mainLayout->addLayout(buttonsLayout);
mainLayout->addLayout(sidebarLayout);

connect(m_tapButton, &QPushButton::pressed, this, [this, muteCheckBox]() {
if (!muteCheckBox->isChecked())
{
const auto timeSigNumerator = Engine::getSong()->getTimeSigModel().getNumerator();
Engine::audioEngine()->addPlayHandle(new SamplePlayHandle(
m_plugin->m_numTaps % timeSigNumerator == 0 ? "misc/metronome02.ogg" : "misc/metronome01.ogg"));
}

m_plugin->onBpmClick();
updateLabels();
});

connect(resetButton, &QPushButton::pressed, this, [this]() { closeEvent(nullptr); });

connect(precisionCheckBox, &QCheckBox::toggled, [this](bool checked) {
m_plugin->m_showDecimal = checked;
updateLabels();
});

connect(syncButton, &QPushButton::clicked, this, [this]() {
const auto& tempoModel = Engine::getSong()->tempoModel();
if (m_plugin->m_bpm < tempoModel.minValue() || m_plugin->m_bpm > tempoModel.maxValue()) { return; }
Engine::getSong()->setTempo(std::round(m_plugin->m_bpm));
});

hide();
if (parentWidget())
{
parentWidget()->hide();
parentWidget()->layout()->setSizeConstraint(QLayout::SetFixedSize);

Qt::WindowFlags flags = parentWidget()->windowFlags();
flags |= Qt::MSWindowsFixedSizeDialogHint;
flags &= ~Qt::WindowMaximizeButtonHint;
parentWidget()->setWindowFlags(flags);
}
}

void TapTempoView::updateLabels()
{
const double bpm = m_plugin->m_showDecimal ? m_plugin->m_bpm : std::round(m_plugin->m_bpm);
const double hz = bpm / 60;
const double ms = bpm > 0 ? 1 / hz * 1000 : 0;

m_tapButton->setText(QString::number(bpm, 'f', m_plugin->m_showDecimal ? 1 : 0));
m_msLabel->setText(tr("%1 ms").arg(ms, 0, 'f', m_plugin->m_showDecimal ? 1 : 0));
m_hzLabel->setText(tr("%1 hz").arg(hz, 0, 'f', 4));
}

void TapTempoView::keyPressEvent(QKeyEvent* event)
{
QWidget::keyPressEvent(event);
if (!event->isAutoRepeat()) { m_plugin->onBpmClick(); }
}

void TapTempoView::closeEvent(QCloseEvent*)
{
m_plugin->m_numTaps = 0;
m_plugin->m_bpm = 0;
updateLabels();
}

} // namespace lmms::gui
Loading

0 comments on commit a311eed

Please sign in to comment.