diff --git a/include/MainWindow.h b/include/MainWindow.h index 30d52ec3a65..4442a7ac252 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -248,7 +248,6 @@ private slots: void onExportProject(); void onExportProjectTracks(); void onImportProject(); - void onSongStopped(); void onSongModified(); void onProjectFileNameChanged(); diff --git a/include/NStateButton.h b/include/NStateButton.h index a6b4c4d9ad1..c948fa843d5 100644 --- a/include/NStateButton.h +++ b/include/NStateButton.h @@ -55,25 +55,20 @@ class NStateButton : public ToolButton public slots: - void changeState( int _n ); - + void changeState(int state); signals: - void changedState( int _n ); - + void changedState(int state); protected: - void mousePressEvent( QMouseEvent * _me ) override; - + void mousePressEvent(QMouseEvent* me) override; private: - QVector > m_states; + QVector> m_states; QString m_generalToolTip; int m_curState; - -} ; - +}; } // namespace lmms::gui diff --git a/include/Song.h b/include/Song.h index 02714d8ac6b..2897b2131de 100644 --- a/include/Song.h +++ b/include/Song.h @@ -25,16 +25,18 @@ #ifndef LMMS_SONG_H #define LMMS_SONG_H +#include #include #include #include -#include "TrackContainer.h" #include "AudioEngine.h" #include "Controller.h" #include "lmms_constants.h" #include "MeterModel.h" +#include "Timeline.h" +#include "TrackContainer.h" #include "VstSyncController.h" namespace lmms @@ -105,7 +107,6 @@ class LMMS_EXPORT Song : public TrackContainer public: PlayPos( const int abs = 0 ) : TimePos( abs ), - m_timeLine( nullptr ), m_currentFrame( 0.0f ) { } @@ -125,13 +126,11 @@ class LMMS_EXPORT Song : public TrackContainer { return m_jumped; } - gui::TimeLineWidget * m_timeLine; private: float m_currentFrame; bool m_jumped; - - } ; + }; void processNextBuffer(); @@ -274,6 +273,11 @@ class LMMS_EXPORT Song : public TrackContainer return getPlayPos(m_playMode); } + auto getTimeline(PlayMode mode) -> Timeline& { return m_timelines[static_cast(mode)]; } + auto getTimeline(PlayMode mode) const -> const Timeline& { return m_timelines[static_cast(mode)]; } + auto getTimeline() -> Timeline& { return getTimeline(m_playMode); } + auto getTimeline() const -> const Timeline& { return getTimeline(m_playMode); } + void updateLength(); bar_t length() const { @@ -402,7 +406,7 @@ private slots: void masterVolumeChanged(); - void savePos(); + void savePlayStartPosition(); void updateFramesPerTick(); @@ -481,6 +485,8 @@ private slots: QHash m_errors; + std::array m_timelines; + PlayMode m_playMode; PlayPos m_playPos[PlayModeCount]; bar_t m_length; diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 2be73b77c49..c87458e6c22 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -34,6 +34,12 @@ class QPixmap; class QToolBar; +namespace lmms { + +class Timeline; + +} // namespace lmms + namespace lmms::gui { @@ -42,7 +48,7 @@ class TextFloat; class SongEditor; -class TimeLineWidget : public QWidget, public JournallingObject +class TimeLineWidget : public QWidget { Q_OBJECT public: @@ -60,24 +66,10 @@ class TimeLineWidget : public QWidget, public JournallingObject { Enabled, Disabled - } ; - - enum class LoopPointState - { - Disabled, - Enabled - } ; - - enum class BehaviourAtStopState - { - BackToZero, - BackToStart, - KeepStopPosition - } ; - + }; - TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos & pos, - const TimePos & begin, Song::PlayMode mode, QWidget * parent); + TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos& pos, Timeline& timeline, + const TimePos& begin, Song::PlayMode mode, QWidget* parent); ~TimeLineWidget() override; inline QColor const & getBarLineColor() const { return m_barLineColor; } @@ -117,42 +109,6 @@ class TimeLineWidget : public QWidget, public JournallingObject return m_autoScroll; } - BehaviourAtStopState behaviourAtStop() const - { - return m_behaviourAtStop; - } - - void setBehaviourAtStop (int state) - { - emit loadBehaviourAtStop (state); - } - - bool loopPointsEnabled() const - { - return m_loopPoints == LoopPointState::Enabled; - } - - inline const TimePos & loopBegin() const - { - return ( m_loopPos[0] < m_loopPos[1] ) ? - m_loopPos[0] : m_loopPos[1]; - } - - inline const TimePos & loopEnd() const - { - return ( m_loopPos[0] > m_loopPos[1] ) ? - m_loopPos[0] : m_loopPos[1]; - } - - inline void savePos( const TimePos & pos ) - { - m_savedPos = pos; - } - inline const TimePos & savedPos() const - { - return m_savedPos; - } - inline void setPixelsPerBar( float ppb ) { m_ppb = ppb; @@ -163,14 +119,6 @@ class TimeLineWidget : public QWidget, public JournallingObject void addToolButtons(QToolBar* _tool_bar ); - - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; - inline QString nodeName() const override - { - return "timeline"; - } - inline int markerX( const TimePos & _t ) const { return m_xOffset + static_cast( ( _t - m_begin ) * @@ -178,25 +126,17 @@ class TimeLineWidget : public QWidget, public JournallingObject } signals: - + void positionChanged(const lmms::TimePos& postion); void regionSelectedFromPixels( int, int ); void selectionFinished(); - public slots: - void updatePosition( const lmms::TimePos & ); - void updatePosition() - { - updatePosition( TimePos() ); - } + void updatePosition(); void setSnapSize( const float snapSize ) { m_snapSize = snapSize; } void toggleAutoScroll( int _n ); - void toggleLoopPoints( int _n ); - void toggleBehaviourAtStop( int _n ); - protected: void paintEvent( QPaintEvent * _pe ) override; @@ -222,8 +162,6 @@ public slots: QColor m_barNumberColor; AutoScrollState m_autoScroll; - LoopPointState m_loopPoints; - BehaviourAtStopState m_behaviourAtStop; bool m_changedPosition; @@ -232,12 +170,9 @@ public slots: float m_ppb; float m_snapSize; Song::PlayPos & m_pos; + Timeline* m_timeline; const TimePos & m_begin; const Song::PlayMode m_mode; - TimePos m_loopPos[2]; - - TimePos m_savedPos; - TextFloat * m_hint; int m_initalXSelect; @@ -253,17 +188,7 @@ public slots: } m_action; int m_moveXOff; - - -signals: - void positionChanged( const lmms::TimePos & _t ); - void loopPointStateLoaded( int _n ); - void positionMarkerMoved(); - void loadBehaviourAtStop( int _n ); - -} ; - - +}; } // namespace lmms::gui diff --git a/include/Timeline.h b/include/Timeline.h new file mode 100644 index 00000000000..dc2d293c4f5 --- /dev/null +++ b/include/Timeline.h @@ -0,0 +1,82 @@ +/* + * Timeline.h + * + * Copyright (c) 2023 Dominic Clark + * + * 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_TIMELINE_H +#define LMMS_TIMELINE_H + +#include + +#include "JournallingObject.h" +#include "TimePos.h" + +namespace lmms { + +class Timeline : public QObject, public JournallingObject +{ + Q_OBJECT + +public: + enum class StopBehaviour + { + BackToZero, + BackToStart, + KeepPosition + }; + + auto loopBegin() const -> TimePos { return m_loopBegin; } + auto loopEnd() const -> TimePos { return m_loopEnd; } + auto loopEnabled() const -> bool { return m_loopEnabled; } + + void setLoopBegin(TimePos begin); + void setLoopEnd(TimePos end); + void setLoopPoints(TimePos begin, TimePos end); + void setLoopEnabled(bool enabled); + + auto playStartPosition() const -> TimePos { return m_playStartPosition; } + auto stopBehaviour() const -> StopBehaviour { return m_stopBehaviour; } + + void setPlayStartPosition(TimePos position) { m_playStartPosition = position; } + void setStopBehaviour(StopBehaviour behaviour); + + auto nodeName() const -> QString override { return "timeline"; } + +signals: + void loopEnabledChanged(bool enabled); + void stopBehaviourChanged(lmms::Timeline::StopBehaviour behaviour); + +protected: + void saveSettings(QDomDocument& doc, QDomElement& element) override; + void loadSettings(const QDomElement& element) override; + +private: + TimePos m_loopBegin = TimePos{0}; + TimePos m_loopEnd = TimePos{DefaultTicksPerBar}; + bool m_loopEnabled = false; + + StopBehaviour m_stopBehaviour = StopBehaviour::BackToStart; + TimePos m_playStartPosition = TimePos{-1}; +}; + +} // namespace lmms + +#endif // LMMS_TIMELINE_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1155f5e0d22..c2dc0bf784f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -74,6 +74,7 @@ set(LMMS_SRCS core/SerializingObject.cpp core/Song.cpp core/TempoSyncKnobModel.cpp + core/Timeline.cpp core/TimePos.cpp core/ToolPlugin.cpp core/Track.cpp diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 2febaee2e7f..ba8420a4186 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -52,16 +52,10 @@ SampleClip::SampleClip( Track * _track ) : connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), this, SLOT(updateLength())); - //care about positionmarker - gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine; - if( timeLine ) - { - connect( timeLine, SIGNAL(positionMarkerMoved()), this, SLOT(playbackPositionChanged())); - } //playbutton clicked or space key / on Export Song set isPlaying to false connect( Engine::getSong(), SIGNAL(playbackStateChanged()), this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); - //care about loops + //care about loops and jumps connect( Engine::getSong(), SIGNAL(updateSampleTracks()), this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); //care about mute Clips diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 3a735331c64..ddc55707ca2 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -184,14 +184,9 @@ void Song::setTimeSignature() -void Song::savePos() +void Song::savePlayStartPosition() { - gui::TimeLineWidget* tl = getPlayPos().m_timeLine; - - if( tl != nullptr ) - { - tl->savePos( getPlayPos() ); - } + getTimeline().setPlayStartPosition(getPlayPos()); } @@ -258,16 +253,17 @@ void Song::processNextBuffer() return false; }; - const auto timeline = getPlayPos().m_timeLine; - const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled(); + const auto& timeline = getTimeline(); + const auto loopEnabled = !m_exporting && timeline.loopEnabled(); // Ensure playback begins within the loop if it is enabled - if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); } + if (loopEnabled) { enforceLoop(timeline.loopBegin(), timeline.loopEnd()); } - // Inform VST plugins if the user moved the play head + // Inform VST plugins and sample tracks if the user moved the play head if (getPlayPos().jumped()) { m_vstSyncController.setPlaybackJumped(true); + emit updateSampleTracks(); getPlayPos().setJumped(false); } @@ -301,13 +297,13 @@ void Song::processNextBuffer() } // Handle loop points, and inform VST plugins of the loop status - if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline->loopBegin())) + if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline.loopBegin())) { m_vstSyncController.startCycle( - timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks()); + timeline.loopBegin().getTicks(), timeline.loopEnd().getTicks()); // Loop if necessary, and decrement the remaining loops if we did - if (enforceLoop(timeline->loopBegin(), timeline->loopEnd()) + if (enforceLoop(timeline.loopBegin(), timeline.loopEnd()) && m_loopRenderRemaining > 1) { m_loopRenderRemaining--; @@ -492,7 +488,7 @@ void Song::playSong() m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -531,7 +527,7 @@ void Song::playPattern() m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -556,7 +552,7 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop ) m_paused = false; } - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -644,39 +640,31 @@ void Song::stop() // To avoid race conditions with the processing threads Engine::audioEngine()->requestChangeInModel(); - TimeLineWidget * tl = getPlayPos().m_timeLine; + auto& timeline = getTimeline(); m_paused = false; m_recording = true; + m_playing = false; - if( tl ) + switch (timeline.stopBehaviour()) { - switch( tl->behaviourAtStop() ) - { - case TimeLineWidget::BehaviourAtStopState::BackToZero: - getPlayPos().setTicks(0); - m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; - break; + case Timeline::StopBehaviour::BackToZero: + getPlayPos().setTicks(0); + m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; + break; - case TimeLineWidget::BehaviourAtStopState::BackToStart: - if( tl->savedPos() >= 0 ) - { - getPlayPos().setTicks(tl->savedPos().getTicks()); - setToTime(tl->savedPos()); + case Timeline::StopBehaviour::BackToStart: + if (timeline.playStartPosition() >= 0) + { + getPlayPos().setTicks(timeline.playStartPosition().getTicks()); + setToTime(timeline.playStartPosition()); - tl->savePos( -1 ); - } - break; + timeline.setPlayStartPosition(-1); + } + break; - case TimeLineWidget::BehaviourAtStopState::KeepStopPosition: - break; - } - } - else - { - getPlayPos().setTicks( 0 ); - m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; + case Timeline::StopBehaviour::KeepPosition: + break; } - m_playing = false; m_elapsedMilliSeconds[static_cast(PlayMode::None)] = m_elapsedMilliSeconds[static_cast(m_playMode)]; getPlayPos(PlayMode::None).setTicks(getPlayPos().getTicks()); @@ -719,37 +707,35 @@ void Song::startExport() m_exporting = true; updateLength(); + const auto& timeline = getTimeline(PlayMode::Song); + if (m_renderBetweenMarkers) { - m_exportSongBegin = m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin(); - m_exportSongEnd = m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd(); + m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin(); + m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd(); - getPlayPos(PlayMode::Song).setTicks( getPlayPos(PlayMode::Song).m_timeLine->loopBegin().getTicks() ); + getPlayPos(PlayMode::Song).setTicks(timeline.loopBegin().getTicks()); } else { m_exportSongEnd = TimePos(m_length, 0); // Handle potentially ridiculous loop points gracefully. - if (m_loopRenderCount > 1 && getPlayPos(PlayMode::Song).m_timeLine->loopEnd() > m_exportSongEnd) + if (m_loopRenderCount > 1 && timeline.loopEnd() > m_exportSongEnd) { - m_exportSongEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd(); + m_exportSongEnd = timeline.loopEnd(); } if (!m_exportLoop) m_exportSongEnd += TimePos(1,0); m_exportSongBegin = TimePos(0,0); - // FIXME: remove this check once we load timeline in headless mode - if (getPlayPos(PlayMode::Song).m_timeLine) - { - m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd && - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ? - getPlayPos(PlayMode::Song).m_timeLine->loopBegin() : TimePos(0,0); - m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd && - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ? - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() : TimePos(0,0); - } + m_exportLoopBegin = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd + ? timeline.loopBegin() + : TimePos{0}; + m_exportLoopEnd = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd + ? timeline.loopEnd() + : TimePos{0}; getPlayPos(PlayMode::Song).setTicks( 0 ); } @@ -1080,11 +1066,7 @@ void Song::loadProject( const QString & fileName ) m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" ); m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" ); - if( getPlayPos(PlayMode::Song).m_timeLine ) - { - // reset loop-point-state - getPlayPos(PlayMode::Song).m_timeLine->toggleLoopPoints( 0 ); - } + getTimeline(PlayMode::Song).setLoopEnabled(false); if( !dataFile.content().firstChildElement( "track" ).isNull() ) { @@ -1167,9 +1149,9 @@ void Song::loadProject( const QString & fileName ) { getGUI()->getProjectNotes()->SerializingObject::restoreState( node.toElement() ); } - else if( node.nodeName() == getPlayPos(PlayMode::Song).m_timeLine->nodeName() ) + else if (node.nodeName() == getTimeline(PlayMode::Song).nodeName()) { - getPlayPos(PlayMode::Song).m_timeLine->restoreState( node.toElement() ); + getTimeline(PlayMode::Song).restoreState(node.toElement()); } } } @@ -1253,7 +1235,7 @@ bool Song::saveProjectFile(const QString & filename, bool withResources) getGUI()->pianoRoll()->saveState( dataFile, dataFile.content() ); getGUI()->automationEditor()->m_editor->saveState( dataFile, dataFile.content() ); getGUI()->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() ); - getPlayPos(PlayMode::Song).m_timeLine->saveState( dataFile, dataFile.content() ); + getTimeline(PlayMode::Song).saveState(dataFile, dataFile.content()); } saveControllerStates( dataFile, dataFile.content() ); diff --git a/src/core/Timeline.cpp b/src/core/Timeline.cpp new file mode 100644 index 00000000000..f6f30c21c67 --- /dev/null +++ b/src/core/Timeline.cpp @@ -0,0 +1,83 @@ +/* + * Timeline.cpp + * + * Copyright (c) 2023 Dominic Clark + * + * 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 "Timeline.h" + +#include +#include + +#include +#include + +namespace lmms { + +void Timeline::setLoopBegin(TimePos begin) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd}); +} + +void Timeline::setLoopEnd(TimePos end) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end); +} + +void Timeline::setLoopPoints(TimePos begin, TimePos end) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end); +} + +void Timeline::setLoopEnabled(bool enabled) +{ + if (enabled != m_loopEnabled) { + m_loopEnabled = enabled; + emit loopEnabledChanged(m_loopEnabled); + } +} + +void Timeline::setStopBehaviour(StopBehaviour behaviour) +{ + if (behaviour != m_stopBehaviour) { + m_stopBehaviour = behaviour; + emit stopBehaviourChanged(m_stopBehaviour); + } +} + +void Timeline::saveSettings(QDomDocument& doc, QDomElement& element) +{ + element.setAttribute("lp0pos", static_cast(loopBegin())); + element.setAttribute("lp1pos", static_cast(loopEnd())); + element.setAttribute("lpstate", static_cast(loopEnabled())); + element.setAttribute("stopbehaviour", static_cast(stopBehaviour())); +} + +void Timeline::loadSettings(const QDomElement& element) +{ + setLoopPoints( + static_cast(element.attribute("lp0pos").toInt()), + static_cast(element.attribute("lp1pos").toInt()) + ); + setLoopEnabled(static_cast(element.attribute("lpstate").toInt())); + setStopBehaviour(static_cast(element.attribute("stopbehaviour", "1").toInt())); +} + +} // namespace lmms diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 62ed84c7aac..0413e3bd656 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -226,8 +226,6 @@ MainWindow::MainWindow() : connect( Engine::getSong(), SIGNAL(playbackStateChanged()), this, SLOT(updatePlayPauseIcons())); - connect(Engine::getSong(), SIGNAL(stopped()), SLOT(onSongStopped())); - connect(Engine::getSong(), SIGNAL(modified()), SLOT(onSongModified())); connect(Engine::getSong(), SIGNAL(projectFileNameChanged()), SLOT(onProjectFileNameChanged())); @@ -1606,42 +1604,6 @@ void MainWindow::onImportProject() } } -void MainWindow::onSongStopped() -{ - Song * song = Engine::getSong(); - Song::PlayPos const & playPos = song->getPlayPos(); - - TimeLineWidget * tl = playPos.m_timeLine; - - if( tl ) - { - SongEditorWindow* songEditor = getGUI()->songEditor(); - switch( tl->behaviourAtStop() ) - { - case TimeLineWidget::BehaviourAtStopState::BackToZero: - if( songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) ) - { - songEditor->m_editor->updatePosition(0); - } - break; - - case TimeLineWidget::BehaviourAtStopState::BackToStart: - if( tl->savedPos() >= 0 ) - { - if(songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) ) - { - songEditor->m_editor->updatePosition( TimePos(tl->savedPos().getTicks() ) ); - } - tl->savePos( -1 ); - } - break; - - case TimeLineWidget::BehaviourAtStopState::KeepStopPosition: - break; - } - } -} - void MainWindow::onSongModified() { // Only update the window title if the code is executed from the GUI main thread. diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index c8ef19b79bd..e7153bfa3e7 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -132,13 +132,12 @@ AutomationEditor::AutomationEditor() : m_quantizeModel.setValue( m_quantizeModel.findText( "1/8" ) ); // add time-line - m_timeLine = new TimeLineWidget( VALUES_WIDTH, 0, m_ppb, - Engine::getSong()->getPlayPos( - Song::PlayMode::AutomationClip ), - m_currentPosition, - Song::PlayMode::AutomationClip, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) ); + m_timeLine = new TimeLineWidget(VALUES_WIDTH, 0, m_ppb, + Engine::getSong()->getPlayPos(Song::PlayMode::AutomationClip), + Engine::getSong()->getTimeline(Song::PlayMode::AutomationClip), + m_currentPosition, Song::PlayMode::AutomationClip, this + ); + connect(this, &AutomationEditor::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); @@ -1602,11 +1601,7 @@ void AutomationEditor::resizeEvent(QResizeEvent * re) } centerTopBottomScroll(); - if( Engine::getSong() ) - { - Engine::getSong()->getPlayPos( Song::PlayMode::AutomationClip - ).m_timeLine->setFixedWidth( width() ); - } + m_timeLine->setFixedWidth(width()); updateTopBottomLevels(); update(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 67fb940aa52..8f2d964ff91 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -265,12 +265,11 @@ PianoRoll::PianoRoll() : // add time-line m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb, - Engine::getSong()->getPlayPos( - Song::PlayMode::MidiClip ), - m_currentPosition, - Song::PlayMode::MidiClip, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) ); + Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip), + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip), + m_currentPosition, Song::PlayMode::MidiClip, this + ); + connect(this, &PianoRoll::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); @@ -282,10 +281,7 @@ PianoRoll::PianoRoll() : this, SLOT( updatePositionStepRecording( const lmms::TimePos& ) ) ); // update timeline when in record-accompany mode - connect( Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine, - SIGNAL( positionChanged( const lmms::TimePos& ) ), - this, - SLOT( updatePositionAccompany( const lmms::TimePos& ) ) ); + connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PianoRoll::updatePositionAccompany); // TODO /* connect( engine::getSong()->getPlayPos( Song::PlayMode::Pattern ).m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), @@ -3734,8 +3730,7 @@ void PianoRoll::resizeEvent(QResizeEvent* re) { updatePositionLineHeight(); updateScrollbars(); - Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip) - .m_timeLine->setFixedWidth(width()); + m_timeLine->setFixedWidth(width()); update(); } @@ -5167,7 +5162,8 @@ void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de ) de.appendChild(markedSemiTonesRoot); } - de.setAttribute("stopbehaviour", static_cast(m_editor->m_timeLine->behaviourAtStop())); + de.setAttribute("stopbehaviour", static_cast( + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).stopBehaviour())); MainWindow::saveWidgetState( this, de ); } @@ -5182,7 +5178,8 @@ void PianoRollWindow::loadSettings( const QDomElement & de ) MainWindow::restoreWidgetState( this, de ); - m_editor->m_timeLine->setBehaviourAtStop(de.attribute("stopbehaviour").toInt()); + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).setStopBehaviour( + static_cast(de.attribute("stopbehaviour").toInt())); // update margins here because we're later in the startup process // We can't earlier because everything is still starting with the diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 5180687596e..69a41a7648b 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -97,14 +97,12 @@ SongEditor::SongEditor( Song * song ) : m_zoomingModel->setParent(this); m_snappingModel->setParent(this); - m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32, - pixelsPerBar(), - m_song->getPlayPos(Song::PlayMode::Song), - m_currentPosition, - Song::PlayMode::Song, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine, - SLOT( updatePosition( const lmms::TimePos& ) ) ); + m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(), + m_song->getPlayPos(Song::PlayMode::Song), + m_song->getTimeline(Song::PlayMode::Song), + m_currentPosition, Song::PlayMode::Song, this + ); + connect(this, &TrackContainerView::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); connect( m_timeLine, SIGNAL(regionSelectedFromPixels(int,int)), @@ -560,7 +558,7 @@ void SongEditor::wheelEvent( QWheelEvent * we ) m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar); // update timeline - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(pixelsPerBar()); + m_timeLine->setPixelsPerBar(pixelsPerBar()); // and make sure, all Clip's are resized and relocated realignTracks(); } @@ -808,8 +806,7 @@ void SongEditor::updatePosition( const TimePos & t ) m_scrollBack = false; } - const int x = m_song->getPlayPos(Song::PlayMode::Song).m_timeLine-> - markerX( t ) + 8; + const int x = m_timeLine->markerX(t) + 8; if( x >= trackOpWidth + widgetWidth -1 ) { m_positionLine->show(); @@ -872,7 +869,7 @@ void SongEditor::zoomingChanged() int ppb = calculatePixelsPerBar(); setPixelsPerBar(ppb); - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(ppb); + m_timeLine->setPixelsPerBar(ppb); realignTracks(); updateRubberband(); m_timeLine->setSnapSize(getSnapSize()); diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 049a8623ffb..f77361a913d 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -45,9 +45,8 @@ namespace constexpr int MIN_BAR_LABEL_DISTANCE = 35; } -TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, - Song::PlayPos & pos, const TimePos & begin, Song::PlayMode mode, - QWidget * parent ) : +TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Song::PlayPos& pos, Timeline& timeline, + const TimePos& begin, Song::PlayMode mode, QWidget* parent) : QWidget( parent ), m_inactiveLoopColor( 52, 63, 53, 64 ), m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), @@ -59,35 +58,28 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, m_barLineColor( 192, 192, 192 ), m_barNumberColor( m_barLineColor.darker( 120 ) ), m_autoScroll( AutoScrollState::Enabled ), - m_loopPoints( LoopPointState::Disabled ), - m_behaviourAtStop( BehaviourAtStopState::BackToZero ), m_changedPosition( true ), m_xOffset( xoff ), m_posMarkerX( 0 ), m_ppb( ppb ), m_snapSize( 1.0 ), m_pos( pos ), + m_timeline{&timeline}, m_begin( begin ), m_mode( mode ), - m_savedPos( -1 ), m_hint( nullptr ), m_action( Action::NoAction ), m_moveXOff( 0 ) { - m_loopPos[0] = 0; - m_loopPos[1] = DefaultTicksPerBar; - setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); m_xOffset -= m_posMarkerPixmap.width() / 2; setMouseTracking(true); - m_pos.m_timeLine = this; auto updateTimer = new QTimer(this); - connect( updateTimer, SIGNAL(timeout()), - this, SLOT(updatePosition())); + connect(updateTimer, &QTimer::timeout, this, &TimeLineWidget::updatePosition); updateTimer->start( 1000 / 60 ); // 60 fps connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), this, SLOT(update())); @@ -98,10 +90,6 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, TimeLineWidget::~TimeLineWidget() { - if( getGUI()->songEditor() ) - { - m_pos.m_timeLine = nullptr; - } delete m_hint; } @@ -129,10 +117,10 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) loopPoints->setGeneralToolTip( tr( "Loop points" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_off" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_on" ) ); - connect( loopPoints, SIGNAL(changedState(int)), this, - SLOT(toggleLoopPoints(int))); - connect( this, SIGNAL(loopPointStateLoaded(int)), loopPoints, - SLOT(changeState(int))); + connect(loopPoints, &NStateButton::changedState, m_timeline, &Timeline::setLoopEnabled); + connect(m_timeline, &Timeline::loopEnabledChanged, loopPoints, &NStateButton::changeState); + connect(m_timeline, &Timeline::loopEnabledChanged, this, static_cast(&QWidget::update)); + loopPoints->changeState(static_cast(m_timeline->loopEnabled())); auto behaviourAtStop = new NStateButton(_tool_bar); behaviourAtStop->addState( embed::getIconPixmap( "back_to_zero" ), @@ -144,50 +132,24 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) "started" ) ); behaviourAtStop->addState( embed::getIconPixmap( "keep_stop_position" ), tr( "After stopping keep position" ) ); - connect( behaviourAtStop, SIGNAL(changedState(int)), this, - SLOT(toggleBehaviourAtStop(int))); - connect( this, SIGNAL(loadBehaviourAtStop(int)), behaviourAtStop, - SLOT(changeState(int))); - behaviourAtStop->changeState( static_cast(BehaviourAtStopState::BackToStart) ); + connect(behaviourAtStop, &NStateButton::changedState, m_timeline, + [timeline = m_timeline](int value) { + timeline->setStopBehaviour(static_cast(value)); + } + ); + connect(m_timeline, &Timeline::stopBehaviourChanged, behaviourAtStop, + [button = behaviourAtStop](Timeline::StopBehaviour value) { + button->changeState(static_cast(value)); + } + ); + behaviourAtStop->changeState(static_cast(m_timeline->stopBehaviour())); _tool_bar->addWidget( autoScroll ); _tool_bar->addWidget( loopPoints ); _tool_bar->addWidget( behaviourAtStop ); } - - - -void TimeLineWidget::saveSettings( QDomDocument & _doc, QDomElement & _this ) -{ - _this.setAttribute( "lp0pos", (int) loopBegin() ); - _this.setAttribute( "lp1pos", (int) loopEnd() ); - _this.setAttribute( "lpstate", static_cast(m_loopPoints) ); - _this.setAttribute( "stopbehaviour", static_cast(m_behaviourAtStop) ); -} - - - - -void TimeLineWidget::loadSettings( const QDomElement & _this ) -{ - m_loopPos[0] = _this.attribute( "lp0pos" ).toInt(); - m_loopPos[1] = _this.attribute( "lp1pos" ).toInt(); - m_loopPoints = static_cast( - _this.attribute( "lpstate" ).toInt() ); - update(); - emit loopPointStateLoaded( static_cast(m_loopPoints) ); - - if( _this.hasAttribute( "stopbehaviour" ) ) - { - emit loadBehaviourAtStop( _this.attribute( "stopbehaviour" ).toInt() ); - } -} - - - - -void TimeLineWidget::updatePosition( const TimePos & ) +void TimeLineWidget::updatePosition() { const int new_x = markerX( m_pos ); @@ -200,34 +162,11 @@ void TimeLineWidget::updatePosition( const TimePos & ) } } - - - void TimeLineWidget::toggleAutoScroll( int _n ) { m_autoScroll = static_cast( _n ); } - - - -void TimeLineWidget::toggleLoopPoints( int _n ) -{ - m_loopPoints = static_cast( _n ); - update(); -} - - - - -void TimeLineWidget::toggleBehaviourAtStop( int _n ) -{ - m_behaviourAtStop = static_cast( _n ); -} - - - - void TimeLineWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); @@ -242,11 +181,11 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) // Draw the loop rectangle int const & loopRectMargin = getLoopRectangleVerticalPadding(); int const loopRectHeight = this->height() - 2 * loopRectMargin; - int const loopStart = markerX( loopBegin() ) + 8; - int const loopEndR = markerX( loopEnd() ) + 9; + int const loopStart = markerX(m_timeline->loopBegin()) + 8; + int const loopEndR = markerX(m_timeline->loopEnd()) + 9; int const loopRectWidth = loopEndR - loopStart; - bool const loopPointsActive = loopPointsEnabled(); + bool const loopPointsActive = m_timeline->loopEnabled(); // Draw the main rectangle (inner fill only) QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 ); @@ -336,12 +275,12 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event ) else if( event->button() == Qt::RightButton ) { m_moveXOff = m_posMarkerPixmap.width() / 2; - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); - const TimePos loopMid = ( m_loopPos[0] + m_loopPos[1] ) / 2; - m_action = t < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; - std::sort(std::begin(m_loopPos), std::end(m_loopPos)); - m_loopPos[( m_action == Action::MoveLoopBegin ) ? 0 : 1] = t; + const auto cursorXOffset = std::max(event->x() - m_xOffset - m_moveXOff, 0); + const TimePos timeAtCursor = m_begin + static_cast(cursorXOffset * TimePos::ticksPerBar() / m_ppb); + const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd()) / 2; + + m_action = timeAtCursor < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; } if( m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd ) @@ -360,49 +299,53 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event ) void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); + + const auto cursorXOffset = std::max(event->x() - m_xOffset - m_moveXOff, 0); + TimePos timeAtCursor = m_begin + static_cast(cursorXOffset * TimePos::ticksPerBar() / m_ppb); switch( m_action ) { case Action::MovePositionMarker: - m_pos.setTicks(t.getTicks()); - Engine::getSong()->setToTime(t, m_mode); + m_pos.setTicks(timeAtCursor.getTicks()); + Engine::getSong()->setToTime(timeAtCursor, m_mode); if (!( Engine::getSong()->isPlaying())) { //Song::PlayMode::None is used when nothing is being played. - Engine::getSong()->setToTime(t, Song::PlayMode::None); + Engine::getSong()->setToTime(timeAtCursor, Song::PlayMode::None); } m_pos.setCurrentFrame( 0 ); m_pos.setJumped( true ); updatePosition(); - positionMarkerMoved(); break; case Action::MoveLoopBegin: case Action::MoveLoopEnd: { - const int i = m_action == Action::MoveLoopBegin ? 0 : 1; + const auto otherPoint = m_action == Action::MoveLoopBegin + ? m_timeline->loopEnd() + : m_timeline->loopBegin(); const bool control = event->modifiers() & Qt::ControlModifier; if (control) { // no ctrl-press-hint when having ctrl pressed delete m_hint; m_hint = nullptr; - m_loopPos[i] = t; } else { - m_loopPos[i] = t.quantize(m_snapSize); + timeAtCursor = timeAtCursor.quantize(m_snapSize); } // Catch begin == end - if (m_loopPos[0] == m_loopPos[1]) + if (timeAtCursor == otherPoint) { const int offset = control ? 1 : m_snapSize * TimePos::ticksPerBar(); - // Note, swap 1 and 0 below and the behavior "skips" the other - // marking instead of pushing it. - if (m_action == Action::MoveLoopBegin) { m_loopPos[0] -= offset; } - else { m_loopPos[1] += offset; } + if (m_action == Action::MoveLoopBegin) { timeAtCursor -= offset; } + else { timeAtCursor += offset; } } + // Update m_action so we still move the correct point even if it is + // dragged past the other. + m_action = timeAtCursor < otherPoint ? Action::MoveLoopBegin : Action::MoveLoopEnd; + m_timeline->setLoopPoints(timeAtCursor, otherPoint); update(); break; } diff --git a/src/gui/widgets/NStateButton.cpp b/src/gui/widgets/NStateButton.cpp index 4fbcc0d6563..4dc0252fc26 100644 --- a/src/gui/widgets/NStateButton.cpp +++ b/src/gui/widgets/NStateButton.cpp @@ -67,34 +67,27 @@ void NStateButton::addState( const QPixmap & _pm, const QString & _tooltip ) -void NStateButton::changeState( int _n ) +void NStateButton::changeState(int state) { - if( _n >= 0 && _n < (int) m_states.size() ) + if (state >= 0 && state < m_states.size() && state != m_curState) { - m_curState = _n; + m_curState = state; - const QString & _tooltip = - ( m_states[m_curState].second != "" ) ? - m_states[m_curState].second : - m_generalToolTip; - setToolTip(_tooltip); + const auto& [icon, tooltip] = m_states[m_curState]; + setToolTip(tooltip.isEmpty() ? m_generalToolTip : tooltip); + setIcon(icon); - setIcon( m_states[m_curState].first ); - - emit changedState( m_curState ); + emit changedState(m_curState); } } - - -void NStateButton::mousePressEvent( QMouseEvent * _me ) +void NStateButton::mousePressEvent(QMouseEvent* me) { - if( _me->button() == Qt::LeftButton && m_states.size() ) + if (me->button() == Qt::LeftButton && !m_states.empty()) { - changeState( ( ++m_curState ) % m_states.size() ); + changeState((m_curState + 1) % m_states.size()); } - ToolButton::mousePressEvent( _me ); + ToolButton::mousePressEvent(me); } - -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui