From 9614a8f4fcdd1fa2d468cb4d7834c20be42becaa Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 21 Feb 2022 22:00:02 +0100 Subject: [PATCH] EngineBuffer: Fix assert when new track is loaded during playback with sync When loading a track that is not yet present in the library (and thus doesn't have any BPM because it hasn't been analyzed yet) while another deck is playing and both decks have sync enabled, a debug assertion is triggered: DEBUG ASSERT: "isValid()" in function double mixxx::Bpm::value() const at src/track/bpm.h:53 Aborted (core dumped) The backtrace looks as follows: #0 0x00007f175c87234c in __pthread_kill_implementation () at /usr/lib/libc.so.6 #1 0x00007f175c8254b8 in raise () at /usr/lib/libc.so.6 #2 0x00007f175c80f534 in abort () at /usr/lib/libc.so.6 #3 0x00007f175cf05ee4 in qt_assert(char const*, char const*, int) () at /usr/lib/libQt5Core.so.5 #4 0x000055deb2e67e1c in mixxx::(anonymous namespace)::handleMessage(QtMsgType, QMessageLogContext const&, QString const&) (type=, context=, input=) at src/util/logging.cpp:355 #5 0x00007f175cf47128 in () at /usr/lib/libQt5Core.so.5 #6 0x00007f175cf3fd8a in () at /usr/lib/libQt5Core.so.5 #7 0x00007f175cf06526 in QMessageLogger::critical(char const*, ...) const () at /usr/lib/libQt5Core.so.5 #8 0x000055deb2e5c720 in mixxx_debug_assert(char const*, char const*, int, char const*) (assertion=assertion@entry=0x55deb39bd0db "isValid()", file=file@entry=0x55deb39bbf30 "src/track/bpm.h", line=line@entry=53, function=function@entry=0x55deb39bbf08 "double mixxx::Bpm::value() const") at gsrc/util/assert.h:9 #9 0x000055deb2ee7e7e in mixxx_debug_assert_return_true(char const*, char const*, int, char const*) (function=0x55deb39bbf08 "double mixxx::Bpm::value() const", line=53, file=0x55deb39bbf30 "src/track/bpm.h", assertion=0x55deb39bd0db "isValid()") at gsrc/util/assert.h:18 #10 mixxx::Bpm::value() const (this=) at src/track/bpm.h:53 #11 mixxx::operator*(mixxx::Bpm, double) (multiple=1, bpm=...) at src/track/bpm.h:160 #12 SyncControl::setLocalBpm(mixxx::Bpm) (this=, localBpm=...) at src/engine/sync/synccontrol.cpp:567 #13 0x000055deb34c7ba3 in EngineBuffer::postProcess(int) (this=0x55deb56eb060, iBufferSize=2048) at src/engine/enginebuffer.cpp:1318 #14 0x000055deb3139023 in EngineMaster::processChannels(int) (this=0x55deb5449440, iBufferSize=) at src/engine/enginemaster.cpp:383 #15 0x000055deb31394f7 in EngineMaster::process(int) (this=0x55deb5449440, iBufferSize=iBufferSize@entry=2048) at src/engine/enginemaster.cpp:410 #16 0x000055deb2f91d0b in SoundManager::onDeviceOutputCallback(long) (this=, iFramesPerBuffer=iFramesPerBuffer@entry=1024) at src/soundio/soundmanager.cpp:596 #17 0x000055deb32dd794 in SoundDevicePortAudio::callbackProcessClkRef(long, float*, float const*, PaStreamCallbackTimeInfo const*, unsigned long) (this=0x55deb553e6b0, framesPerBuffer=1024, out=, in=, timeInfo=, statusFlags=) at src/soundio/sounddeviceportaudio.cpp:965 This happens because `newLocalBpm` is invalid when `localBpm` is invalid. Trying to do sync decks while no tempo information is available does not make sense, so we only synchronize decks if the local BPM is available. --- src/engine/enginebuffer.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index b43cc10e94a..8eb7fb4a4f8 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1310,18 +1310,22 @@ void EngineBuffer::postProcess(const int iBufferSize) { } const mixxx::Bpm localBpm = m_pBpmControl->updateLocalBpm(); double beatDistance = m_pBpmControl->updateBeatDistance(); - // FIXME: Double check if calling setLocalBpm with an invalid value is correct and intended. - mixxx::Bpm newLocalBpm; + const SyncMode mode = m_pSyncControl->getSyncMode(); if (localBpm.isValid()) { - newLocalBpm = localBpm; - } - m_pSyncControl->setLocalBpm(newLocalBpm); - SyncMode mode = m_pSyncControl->getSyncMode(); - m_pSyncControl->reportPlayerSpeed(m_speed_old, m_scratching_old); - if (isLeader(mode)) { - m_pEngineSync->notifyBeatDistanceChanged(m_pSyncControl, beatDistance); - } else if (isFollower(mode)) { - m_pSyncControl->updateTargetBeatDistance(); + m_pSyncControl->setLocalBpm(localBpm); + m_pSyncControl->reportPlayerSpeed(m_speed_old, m_scratching_old); + if (isLeader(mode)) { + m_pEngineSync->notifyBeatDistanceChanged(m_pSyncControl, beatDistance); + } else if (isFollower(mode)) { + m_pSyncControl->updateTargetBeatDistance(); + } + } else if (mode == SyncMode::LeaderSoft) { + // If this channel has been automatically chosen to be the leader but + // no BPM is available, another channel may take over leadership and + // this channel becomes a follower. This may happen if the track is + // analyzed upon load and avoids sudden tempo jumps on the other deck + // while the analysis is still running. + requestSyncMode(SyncMode::Follower); } // Update all the indicators that EngineBuffer publishes to allow