diff --git a/include/AudioEngine.h b/include/AudioEngine.h index f80c860d819..c01b2ea1a5e 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -289,9 +289,6 @@ class LMMS_EXPORT AudioEngine : public QObject void changeQuality(const struct qualitySettings & qs); - inline bool isMetronomeActive() const { return m_metronomeActive; } - inline void setMetronomeActive(bool value = true) { m_metronomeActive = value; } - //! Block until a change in model can be done (i.e. wait for audio thread) void requestChangeInModel(); void doneChangeInModel(); @@ -352,8 +349,6 @@ class LMMS_EXPORT AudioEngine : public QObject void swapBuffers(); - void handleMetronome(); - void clearInternal(); bool m_renderOnly; @@ -402,8 +397,6 @@ class LMMS_EXPORT AudioEngine : public QObject AudioEngineProfiler m_profiler; - bool m_metronomeActive; - bool m_clearSignal; std::recursive_mutex m_changeMutex; diff --git a/include/Metronome.h b/include/Metronome.h new file mode 100644 index 00000000000..a59ad5b83bc --- /dev/null +++ b/include/Metronome.h @@ -0,0 +1,43 @@ +/* + * Metronome.h + * + * Copyright (c) 2024 saker + * + * 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_METRONOME_H +#define LMMS_METRONOME_H + +#include + +namespace lmms { +class Metronome +{ +public: + bool active() const { return m_active; } + void setActive(bool active) { m_active = active; } + void processTick(int currentTick, int ticksPerBar, int beatsPerBar, size_t bufferOffset); + +private: + bool m_active = false; +}; +} // namespace lmms + +#endif // LMMS_METRONOME_H diff --git a/include/Song.h b/include/Song.h index 2897b2131de..f08edfff602 100644 --- a/include/Song.h +++ b/include/Song.h @@ -33,6 +33,7 @@ #include "AudioEngine.h" #include "Controller.h" +#include "Metronome.h" #include "lmms_constants.h" #include "MeterModel.h" #include "Timeline.h" @@ -375,6 +376,8 @@ class LMMS_EXPORT Song : public TrackContainer const std::string& syncKey() const noexcept { return m_vstSyncController.sharedMemoryKey(); } + Metronome& metronome() { return m_metronome; } + public slots: void playSong(); void record(); @@ -448,6 +451,7 @@ private slots: void restoreKeymapStates(const QDomElement &element); void processAutomations(const TrackList& tracks, TimePos timeStart, fpp_t frames); + void processMetronome(size_t bufferOffset); void setModified(bool value); @@ -513,6 +517,8 @@ private slots: AutomatedValueMap m_oldAutomatedValues; + Metronome m_metronome; + friend class Engine; friend class gui::SongEditor; friend class gui::ControllerRackView; diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 42e7079b016..77453a8bad4 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -87,7 +87,6 @@ AudioEngine::AudioEngine( bool renderOnly ) : m_oldAudioDev( nullptr ), m_audioDevStartFailed( false ), m_profiler(), - m_metronomeActive(false), m_clearSignal(false) { for( int i = 0; i < 2; ++i ) @@ -345,8 +344,6 @@ void AudioEngine::renderStageNoteSetup() Mixer * mixer = Engine::mixer(); mixer->prepareMasterMix(); - handleMetronome(); - // create play-handles for new notes, samples etc. Engine::getSong()->processNextBuffer(); @@ -459,55 +456,6 @@ void AudioEngine::swapBuffers() zeroSampleFrames(m_outputBufferWrite.get(), m_framesPerPeriod); } - - - -void AudioEngine::handleMetronome() -{ - static tick_t lastMetroTicks = -1; - - Song * song = Engine::getSong(); - Song::PlayMode currentPlayMode = song->playMode(); - - bool metronomeSupported = - currentPlayMode == Song::PlayMode::MidiClip - || currentPlayMode == Song::PlayMode::Song - || currentPlayMode == Song::PlayMode::Pattern; - - if (!metronomeSupported || !m_metronomeActive || song->isExporting()) - { - return; - } - - // stop crash with metronome if empty project - if (song->countTracks() == 0) - { - return; - } - - tick_t ticks = song->getPlayPos(currentPlayMode).getTicks(); - tick_t ticksPerBar = TimePos::ticksPerBar(); - int numerator = song->getTimeSigModel().getNumerator(); - - if (ticks == lastMetroTicks) - { - return; - } - - if (ticks % (ticksPerBar / 1) == 0) - { - addPlayHandle(new SamplePlayHandle("misc/metronome02.ogg")); - } - else if (ticks % (ticksPerBar / numerator) == 0) - { - addPlayHandle(new SamplePlayHandle("misc/metronome01.ogg")); - } - - lastMetroTicks = ticks; -} - - - void AudioEngine::clear() { m_clearSignal = true; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3608d28486a..1e2c4f3cfdb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,7 @@ set(LMMS_SRCS core/LinkedModelGroups.cpp core/LocklessAllocator.cpp core/MeterModel.cpp + core/Metronome.cpp core/MicroTimer.cpp core/Microtuner.cpp core/MixHelpers.cpp diff --git a/src/core/Metronome.cpp b/src/core/Metronome.cpp new file mode 100644 index 00000000000..8afc6afa497 --- /dev/null +++ b/src/core/Metronome.cpp @@ -0,0 +1,41 @@ +/* + * Metronome.cpp + * + * Copyright (c) 2024 saker + * + * 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 "Metronome.h" + +#include "Engine.h" +#include "SamplePlayHandle.h" + +namespace lmms { +void Metronome::processTick(int currentTick, int ticksPerBar, int beatsPerBar, size_t bufferOffset) +{ + const auto ticksPerBeat = ticksPerBar / beatsPerBar; + if (currentTick % ticksPerBeat != 0 || !m_active) { return; } + + const auto handle = currentTick % ticksPerBar == 0 ? new SamplePlayHandle("misc/metronome02.ogg") + : new SamplePlayHandle("misc/metronome01.ogg"); + handle->setOffset(bufferOffset); + Engine::audioEngine()->addPlayHandle(handle); +} +} // namespace lmms diff --git a/src/core/Song.cpp b/src/core/Song.cpp index e4f31c622c3..4e6bf6f583d 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -332,6 +332,8 @@ void Song::processNextBuffer() { // First frame of tick: process automation and play tracks processAutomations(trackList, getPlayPos(), framesToPlay); + processMetronome(frameOffsetInPeriod); + for (const auto track : trackList) { track->play(getPlayPos(), framesToPlay, frameOffsetInPeriod, clipNum); @@ -426,6 +428,17 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp } } +void Song::processMetronome(size_t bufferOffset) +{ + const auto currentPlayMode = playMode(); + const auto supported = currentPlayMode == PlayMode::MidiClip + || currentPlayMode == PlayMode::Song + || currentPlayMode == PlayMode::Pattern; + + if (!supported || m_exporting) { return; } + m_metronome.processTick(currentTick(), ticksPerBar(), m_timeSigModel.getNumerator(), bufferOffset); +} + void Song::setModified(bool value) { if( !m_loadingProject && m_modified != value) @@ -1542,6 +1555,4 @@ void Song::setKeymap(unsigned int index, std::shared_ptr newMap) emit keymapListChanged(index); Engine::audioEngine()->doneChangeInModel(); } - - } // namespace lmms diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d534be96f3f..fa0c509569f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -43,6 +43,7 @@ #include "ExportProjectDialog.h" #include "FileBrowser.h" #include "FileDialog.h" +#include "Metronome.h" #include "MixerView.h" #include "GuiApplication.h" #include "ImportFilter.h" @@ -430,7 +431,7 @@ void MainWindow::finalize() this, SLOT(onToggleMetronome()), m_toolBar ); m_metronomeToggle->setCheckable(true); - m_metronomeToggle->setChecked(Engine::audioEngine()->isMetronomeActive()); + m_metronomeToggle->setChecked(Engine::getSong()->metronome().active()); m_toolBarLayout->setColumnMinimumWidth( 0, 5 ); m_toolBarLayout->addWidget( project_new, 0, 1 ); @@ -1173,7 +1174,7 @@ void MainWindow::updateConfig( QAction * _who ) void MainWindow::onToggleMetronome() { - Engine::audioEngine()->setMetronomeActive( m_metronomeToggle->isChecked() ); + Engine::getSong()->metronome().setActive(m_metronomeToggle->isChecked()); }