From 4fa8e93b882e4b8a047983a8b2da32a3367415fe Mon Sep 17 00:00:00 2001 From: sakertooth Date: Wed, 13 Apr 2022 19:27:18 -0400 Subject: [PATCH] Add Tap Tempo Feature (#6375) Resolves #5181 --- cmake/modules/PluginList.cmake | 1 + plugins/TapTempo/CMakeLists.txt | 3 + plugins/TapTempo/TapTempo.cpp | 181 ++++++++++++++++++++++++++++++++ plugins/TapTempo/TapTempo.h | 90 ++++++++++++++++ plugins/TapTempo/logo.png | Bin 0 -> 9472 bytes 5 files changed, 275 insertions(+) create mode 100644 plugins/TapTempo/CMakeLists.txt create mode 100644 plugins/TapTempo/TapTempo.cpp create mode 100644 plugins/TapTempo/TapTempo.h create mode 100644 plugins/TapTempo/logo.png diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index 6b2c7519af0..0a4686fb2c6 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -63,6 +63,7 @@ SET(LMMS_PLUGIN_LIST StereoEnhancer StereoMatrix Stk + TapTempo VstBase Vestige VstEffect diff --git a/plugins/TapTempo/CMakeLists.txt b/plugins/TapTempo/CMakeLists.txt new file mode 100644 index 00000000000..c32eff52a5b --- /dev/null +++ b/plugins/TapTempo/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(taptempo TapTempo.cpp MOCFILES TapTempo.h EMBEDDED_RESOURCES logo.png) \ No newline at end of file diff --git a/plugins/TapTempo/TapTempo.cpp b/plugins/TapTempo/TapTempo.cpp new file mode 100644 index 00000000000..6702457854e --- /dev/null +++ b/plugins/TapTempo/TapTempo.cpp @@ -0,0 +1,181 @@ +/* + * TapTempo.cpp - plugin to count beats per minute + * + * + * 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 "TapTempo.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "embed.h" +#include "LedCheckBox.h" +#include "plugin_export.h" + + +double asSeconds(std::chrono::duration duration) +{ + return duration.count(); +} + + +extern "C" +{ +Plugin::Descriptor PLUGIN_EXPORT taptempo_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "Tap Tempo", + QT_TRANSLATE_NOOP( "PluginBrowser", + "Tap to the beat" ), + "sakertooth ", + 0x0100, + Plugin::Tool, + new PluginPixmapLoader("logo"), + nullptr, + nullptr +} ; + +// neccessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main(Model *, void *) +{ + return new TapTempo; +} +} + +TapTempo::TapTempo() : + ToolPlugin(&taptempo_plugin_descriptor, nullptr) +{ +} + +QString TapTempo::nodeName() const +{ + return taptempo_plugin_descriptor.name; +} + +TapTempoView::TapTempoView(ToolPlugin * _tool) : + ToolPluginView(_tool), + m_numTaps(0) +{ + setFixedSize(200, 200); + m_bpmButton = new QPushButton; + m_bpmButton->setText("0"); + m_bpmButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QFont font = m_bpmButton->font(); + font.setPointSize(25); + m_bpmButton->setFont(font); + + QWidget* labelArea = new QWidget(this); + m_msLabel = new QLabel; + m_hzLabel = new QLabel; + + LedCheckBox* checkBox = new LedCheckBox(labelArea, tr("Display high precision")); + checkBox->setToolTip(tr("Display high precision")); + connect(checkBox, &LedCheckBox::toggled, [this](bool checked){ m_showDecimal = checked; updateLabels(); }); + + QVBoxLayout * layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignCenter); + layout->addWidget(m_bpmButton, Qt::AlignCenter); + layout->addWidget(labelArea); + QHBoxLayout* labelLayout = new QHBoxLayout(labelArea); + labelLayout->addWidget(m_msLabel, 1); + labelLayout->addWidget(m_hzLabel, 1); + labelLayout->addWidget(checkBox, 0); + + connect(m_bpmButton, &QPushButton::pressed, this, &TapTempoView::onBpmClick); + + hide(); + if(parentWidget()) + { + parentWidget()->hide(); + parentWidget()->layout()->setSizeConstraint(QLayout::SetFixedSize); + + Qt::WindowFlags flags = parentWidget()->windowFlags(); + flags |= Qt::MSWindowsFixedSizeDialogHint; + flags &= ~Qt::WindowMaximizeButtonHint; + parentWidget()->setWindowFlags(flags); + } + + updateLabels(); +} + + +void TapTempoView::onBpmClick() +{ + auto currentTime = std::chrono::steady_clock::now(); + + if (asSeconds(currentTime - m_previousTime) > 2.0) + { + m_numTaps = 1; + m_firstTime = currentTime; + m_previousTime = currentTime; + m_bpm = 0; + updateLabels(); + return; + } + + ++m_numTaps; + + m_bpm = (m_numTaps - 1) / std::max(DBL_MIN, asSeconds(currentTime - m_firstTime)) * 60; + updateLabels(); + + m_previousTime = currentTime; +} + + +void TapTempoView::updateLabels() +{ + // Round the BPM before calculating Hz and ms + double bpm = m_showDecimal ? m_bpm : std::round(m_bpm); + double hz = bpm / 60; + double ms = bpm > 0 ? 1 / hz * 1000 : 0; + + m_bpmButton->setText(QString::number(bpm, 'f', m_showDecimal ? 1 : 0)); + m_msLabel->setText(tr("%1 ms").arg(ms, 0, 'f', 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()) + { + onBpmClick(); + } +} + +void TapTempoView::closeEvent(QCloseEvent* event) +{ + m_numTaps = 0; + m_bpm = 0; + updateLabels(); +} diff --git a/plugins/TapTempo/TapTempo.h b/plugins/TapTempo/TapTempo.h new file mode 100644 index 00000000000..7ae1ab2ceea --- /dev/null +++ b/plugins/TapTempo/TapTempo.h @@ -0,0 +1,90 @@ +/* + * TapTempo.h - plugin to count beats per minute + * + * + * 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. + * + */ + +#ifndef TAPTEMPO_H +#define TAPTEMPO_H + +#include + +#include "ToolPlugin.h" +#include "ToolPluginView.h" + +class QLabel; +class QPushButton; +class LedCheckBox; + +class TapTempoView : public ToolPluginView +{ + Q_OBJECT +public: + TapTempoView(ToolPlugin *); + + void onBpmClick(); + + void keyPressEvent(QKeyEvent *) override; + void closeEvent(QCloseEvent*) override; + +private: + void reset(); + void updateLabels(); + +private: + std::chrono::time_point m_firstTime; + std::chrono::time_point m_previousTime; + int m_numTaps; + QPushButton* m_bpmButton; + QLabel* m_msLabel; + QLabel* m_hzLabel; + double m_bpm = 0; + bool m_showDecimal = false; +}; + +class TapTempo : public ToolPlugin +{ + Q_OBJECT; +public: + TapTempo(); + + virtual PluginView * instantiateView(QWidget *) + { + return new TapTempoView(this); + } + + virtual QString nodeName() const; + + virtual void saveSettings( QDomDocument& doc, QDomElement& element ) + { + Q_UNUSED(doc) + Q_UNUSED(element) + } + + virtual void loadSettings( const QDomElement& element ) + { + Q_UNUSED(element) + } +}; + +#endif diff --git a/plugins/TapTempo/logo.png b/plugins/TapTempo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1453e6bdeb798fe4854161b8a3024b3b6b18d3cf GIT binary patch literal 9472 zcmeHrXH=70({|{HfM7wI5`#1)Bq7w$1VU3n?@a;;5FnH!KxooL=^`T1o1%!Ih=L$e zr3liCbU~y`?})-1^qljYv%dGwv)1?iJIT6}`<^{>&CE5k_uBW4)YH*mVd7>2001mn znn(l6KlJEgq^JCvQt#ac0NBg@jLb*|Xdj>l!QBDpiUpEMU6Oc( z?%g_k6=hEC6FLo`nr&ls*xR5rH+E}TcQjw$dt21HP*|uP@zI~E@@LSY|LEbWVL+tV z1HsaPg}oMq9T_tJ!CuOa9Y#80t4f#QO{mveLc~6d%e1QKWx)i=f`)9d6rNRQ8CUZvf6%AU~D!& zT@L2}BM5M8Nj}{%T9$jB#?iQ6)at&`uU}fvv%mBsitmQL;lkMrDc^C`i#tM7+31_6 zwiTWibK1qpNyWj$&h{#ZTzuM$oqxi-%OX_PcTX_K=2KBl`lO@i7sayi<|3d?#z1Wz z%;S|u^y1l|u}&Lu+?D~Ojp9o?t8VHJ;Q-)I?+J17lO31yr|EZp?*F_qEqS+&@Lp8g zV|PB&vhC!7@N?3sjPhcSfVYYgfn&jJYjgFsDj=Hb9;Y1fTn_u=Ay=4=aWgAC<`v>% z%tK=1VNZ;-SjXyK?dcrf=`oumavPzB@)De#ryf61E+L5Z>3&pUiA}gFmePNizWWtiEaSi;Y$JqsC^$o9?AgWd%!9FkUw|Hz>JP=Hdaj5{Zl_j*IDa=8q3){Pw5IrrWN4khPc* zbzEh))(3@&Ch@W)!Nik&BBpQZF59FKk!#mm(p(4|96{2=4uhS0Df`6FHxeZ!lgy;; zb$KlG0(19w13pHG$JjJ0;%@g{>?jO2ya$tdXkcoWcB(e%<@e7y%=G$E+T#RssuoBdFPx2XR&Y_>!qUUa=o`bFIsz`MzAQZcbZ890afSxOGBqu=!^G@+bB1qWE;1Z#J*a7_~E> z!97uEn+)xn@=0}EjW7}Jdo>_94a-6nbZi~&zkwhgqL13 zj{3HDzCm-x*evjYNq3#s(_ghiru@v^CKetR+?&>eKiaE)kSPu zaZGPx=F^0@yq=Oz4*|zF!y4+&p0>J->domd>~ZS8GzAxdJ`$LgeKO<8lM}*s|6uMy zIjb*?}6~^cBtb lk#nv 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 T-wRMy2Y{BE4)VE*ZRr01<1{R_ literal 0 HcmV?d00001