Browse Source

Fix BASS_ChannelSetPosition desync

Prescan no longer works for some reason...
kiwec 2 months ago
parent
commit
56a1543fe6

+ 4 - 6
src/App/Osu/Beatmap.cpp

@@ -948,7 +948,7 @@ bool Beatmap::start() {
         // Restarting sound engine already reloads the music
     } else {
         unloadMusic();  // need to reload in case of speed/pitch changes (just to be sure)
-        loadMusic(false, m_bForceStreamPlayback);
+        loadMusic(false);
     }
 
     m_music->setLoop(false);
@@ -1800,7 +1800,7 @@ void Beatmap::handlePreviewPlay() {
     m_music->setLoop(osu_beatmap_preview_music_loop.getBool());
 }
 
-void Beatmap::loadMusic(bool stream, bool prescan) {
+void Beatmap::loadMusic(bool stream) {
     stream = stream || m_bForceStreamPlayback;
     m_iResourceLoadUpdateDelayHack = 0;
 
@@ -1814,9 +1814,7 @@ void Beatmap::loadMusic(bool stream, bool prescan) {
         if(!stream) engine->getResourceManager()->requestNextLoadAsync();
 
         m_music = engine->getResourceManager()->loadSoundAbs(
-            m_selectedDifficulty2->getFullSoundFilePath(), "OSU_BEATMAP_MUSIC", stream, false, false,
-            m_bForceStreamPlayback &&
-                prescan);  // m_bForceStreamPlayback = prescan necessary! otherwise big mp3s will go out of sync
+            m_selectedDifficulty2->getFullSoundFilePath(), "OSU_BEATMAP_MUSIC", stream, false, false);
         m_music->setVolume(getIdealVolume());
         m_fMusicFrequencyBackup = m_music->getFrequency();
         m_music->setSpeed(osu->getSpeedMultiplier());
@@ -2608,7 +2606,7 @@ void Beatmap::update2() {
         {
             m_bForceStreamPlayback = true;
             unloadMusic();
-            loadMusic(true, m_bForceStreamPlayback);
+            loadMusic(true);
 
             // we are waiting for an asynchronous start of the beatmap in the next update()
             m_bIsWaiting = true;

+ 1 - 1
src/App/Osu/Beatmap.h

@@ -128,7 +128,7 @@ class Beatmap {
     }
 
     // music/sound
-    void loadMusic(bool stream = true, bool prescan = false);
+    void loadMusic(bool stream = true);
     void unloadMusic();
     float getIdealVolume();
     void setSpeed(float speed);

+ 1 - 2
src/App/Osu/Skin.cpp

@@ -1437,8 +1437,7 @@ void Skin::checkLoadSound(Sound **addressOfPointer, std::string skinElementName,
                 if(osu_skin_async.getBool()) {
                     engine->getResourceManager()->requestNextLoadAsync();
                 }
-                return engine->getResourceManager()->loadSoundAbs(path, resource_name, !isSample, isOverlayable, loop,
-                                                                  false);
+                return engine->getResourceManager()->loadSoundAbs(path, resource_name, !isSample, isOverlayable, loop);
             }
         }
 

+ 2 - 3
src/Engine/ResourceManager.cpp

@@ -350,8 +350,7 @@ McFont *ResourceManager::loadFont(std::string filepath, std::string resourceName
     return fnt;
 }
 
-Sound *ResourceManager::loadSoundAbs(std::string filepath, std::string resourceName, bool stream, bool overlayable,
-                                     bool loop, bool prescan) {
+Sound *ResourceManager::loadSoundAbs(std::string filepath, std::string resourceName, bool stream, bool overlayable, bool loop) {
     // check if it already exists
     if(resourceName.length() > 0) {
         Resource *temp = checkIfExistsAndHandle(resourceName);
@@ -359,7 +358,7 @@ Sound *ResourceManager::loadSoundAbs(std::string filepath, std::string resourceN
     }
 
     // create instance and load it
-    Sound *snd = new Sound(filepath, stream, overlayable, loop, prescan);
+    Sound *snd = new Sound(filepath, stream, overlayable, loop);
     snd->setName(resourceName);
 
     loadResource(snd, true);

+ 1 - 2
src/Engine/ResourceManager.h

@@ -89,8 +89,7 @@ class ResourceManager {
                      bool antialiasing = true, int fontDPI = 96);
 
     // sounds
-    Sound *loadSoundAbs(std::string filepath, std::string resourceName, bool stream = false, bool overlayable = false,
-                        bool loop = false, bool prescan = false);
+    Sound *loadSoundAbs(std::string filepath, std::string resourceName, bool stream = false, bool overlayable = false, bool loop = false);
 
     // shaders
     Shader *loadShader(std::string vertexShaderFilePath, std::string fragmentShaderFilePath, std::string resourceName);

+ 51 - 45
src/Engine/Sound.cpp

@@ -21,12 +21,11 @@ ConVar snd_wav_file_min_size("snd_wav_file_min_size", 51, FCVAR_DEFAULT,
                              "minimum file size in bytes for WAV files to be considered valid (everything below will "
                              "fail to load), this is a workaround for BASS crashes");
 
-Sound::Sound(std::string filepath, bool stream, bool overlayable, bool loop, bool prescan) : Resource(filepath) {
+Sound::Sound(std::string filepath, bool stream, bool overlayable, bool loop) : Resource(filepath) {
     m_sample = 0;
     m_stream = 0;
     m_bStream = stream;
     m_bIsLooped = loop;
-    m_bPrescan = prescan;
     m_bIsOverlayable = overlayable;
     m_fSpeed = 1.0f;
     m_fVolume = 1.0f;
@@ -115,8 +114,7 @@ void Sound::initAsync() {
 #endif
 
     if(m_bStream) {
-        auto flags = BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT;
-        if(m_bPrescan) flags |= BASS_STREAM_PRESCAN;
+        auto flags = BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT | BASS_STREAM_PRESCAN;
         if(convar->getConVarByName("snd_async_buffer")->getInt() > 0) flags |= BASS_ASYNCFILE;
         if(env->getOS() == Environment::OS::WINDOWS) flags |= BASS_UNICODE;
 
@@ -195,67 +193,75 @@ void Sound::destroy() {
 }
 
 void Sound::setPosition(double percent) {
-    if(!m_bReady) return;
+    return setPositionMS(clamp<f64>(percent, 0.0, 1.0) * m_length);
+}
+
+void Sound::setPositionMS(unsigned long ms) {
+    if(!m_bReady || ms > getLengthMS()) return;
     if(!m_bStream) {
-        engine->showMessageError("Programmer Error", "Called setPosition on a sample!");
+        engine->showMessageError("Programmer Error", "Called setPositionMS on a sample!");
         return;
     }
 
-    percent = clamp<f64>(percent, 0.0, 1.0);
-
-    f64 length = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
-    if(length < 0) {
-        debugLog("Could not set stream position: error %d\n", BASS_ErrorGetCode());
+    i64 target_pos = BASS_ChannelSeconds2Bytes(m_stream, ms / 1000.0);
+    if(target_pos < 0) {
+        debugLog("setPositionMS: error %d while calling BASS_ChannelSeconds2Bytes\n", BASS_ErrorGetCode());
         return;
     }
 
-    if(isPlaying()) {
-        if(!BASS_Mixer_ChannelSetPosition(m_stream, (i64)(length * percent), BASS_POS_BYTE)) {
-            if(Osu::debug->getBool()) {
-                debugLog("Sound::setPosition( %f ) BASS_ChannelSetPosition() error %i on file %s\n", percent,
-                         BASS_ErrorGetCode(), m_sFilePath.c_str());
+    // Naively setting position breaks with the current BASS version (& addons).
+    //
+    // BASS_STREAM_PRESCAN no longer seems to work, so our only recourse is to use the BASS_POS_DECODETO
+    // flag which renders the whole audio stream up until the requested seek point.
+    //
+    // The downside of BASS_POS_DECODETO is that it can only seek forward... furthermore, we can't
+    // just seek to 0 before seeking forward again, since BASS_Mixer_ChannelGetPosition breaks
+    // in that case. So, our only recourse is to just reload the whole fucking stream just for seeking.
+
+    bool was_playing = isPlaying();
+    auto pos = getPositionMS();
+    if(pos <= ms) {
+        // Lucky path, we can just seek forward and be done
+        if(isPlaying()) {
+            if(!BASS_Mixer_ChannelSetPosition(m_stream, target_pos, BASS_POS_BYTE | BASS_POS_DECODETO | BASS_POS_MIXER_RESET)) {
+                if(Osu::debug->getBool()) {
+                    debugLog("Sound::setPositionMS( %lu ) BASS_ChannelSetPosition() error %i on file %s\n", ms,
+                             BASS_ErrorGetCode(), m_sFilePath.c_str());
+                }
             }
-        }
 
-        m_fLastPlayTime = m_fChannelCreationTime - ((f64)m_length * percent / 1000.0);
-    } else {
-        if(!BASS_ChannelSetPosition(m_stream, (i64)(length * percent), BASS_POS_BYTE | BASS_POS_FLUSH)) {
-            if(Osu::debug->getBool()) {
-                debugLog("Sound::setPosition( %f ) BASS_ChannelSetPosition() error %i on file %s\n", percent,
-                         BASS_ErrorGetCode(), m_sFilePath.c_str());
+            m_fLastPlayTime = m_fChannelCreationTime - ((f64)ms / 1000.0);
+        } else {
+            if(!BASS_ChannelSetPosition(m_stream, target_pos, BASS_POS_BYTE | BASS_POS_DECODETO | BASS_POS_FLUSH)) {
+                if(Osu::debug->getBool()) {
+                    debugLog("Sound::setPositionMS( %lu ) BASS_ChannelSetPosition() error %i on file %s\n", ms,
+                             BASS_ErrorGetCode(), m_sFilePath.c_str());
+                }
             }
         }
-    }
-}
+    } else {
+        // Unlucky path, we have to reload the stream
+        auto pan = getPan();
+        auto loop = isLooped();
+        auto speed = getSpeed();
 
-void Sound::setPositionMS(unsigned long ms) {
-    if(!m_bReady || ms > getLengthMS()) return;
-    if(!m_bStream) {
-        engine->showMessageError("Programmer Error", "Called setPositionMS on a sample!");
-        return;
-    }
+        reload();
 
-    i64 position = BASS_ChannelSeconds2Bytes(m_stream, ms / 1000.0);
-    if(position < 0) {
-        debugLog("Could not set stream position: error %d\n", BASS_ErrorGetCode());
-        return;
-    }
+        setSpeed(speed);
+        setPan(pan);
+        setLoop(loop);
+        m_bPaused = true;
+        m_paused_position_ms = ms;
 
-    if(isPlaying()) {
-        if(!BASS_Mixer_ChannelSetPosition(m_stream, position, BASS_POS_BYTE)) {
+        if(!BASS_ChannelSetPosition(m_stream, target_pos, BASS_POS_BYTE | BASS_POS_DECODETO | BASS_POS_FLUSH)) {
             if(Osu::debug->getBool()) {
                 debugLog("Sound::setPositionMS( %lu ) BASS_ChannelSetPosition() error %i on file %s\n", ms,
                          BASS_ErrorGetCode(), m_sFilePath.c_str());
             }
         }
 
-        m_fLastPlayTime = m_fChannelCreationTime - ((f64)ms / 1000.0);
-    } else {
-        if(!BASS_ChannelSetPosition(m_stream, position, BASS_POS_BYTE | BASS_POS_FLUSH)) {
-            if(Osu::debug->getBool()) {
-                debugLog("Sound::setPositionMS( %lu ) BASS_ChannelSetPosition() error %i on file %s\n", ms,
-                         BASS_ErrorGetCode(), m_sFilePath.c_str());
-            }
+        if(was_playing) {
+            osu->music_unpause_scheduled = true;
         }
     }
 }

+ 1 - 2
src/Engine/Sound.h

@@ -21,7 +21,7 @@ class Sound : public Resource {
     typedef unsigned long SOUNDHANDLE;
 
    public:
-    Sound(std::string filepath, bool stream, bool overlayable, bool loop, bool prescan);
+    Sound(std::string filepath, bool stream, bool overlayable, bool loop);
     virtual ~Sound() { destroy(); }
 
     std::vector<HCHANNEL> mixer_channels;
@@ -64,7 +64,6 @@ class Sound : public Resource {
     bool m_bPaused = false;
     bool m_bStream;
     bool m_bIsLooped;
-    bool m_bPrescan;
     bool m_bIsOverlayable;
 
     float m_fPan;

+ 1 - 1
src/Engine/SoundEngine.cpp

@@ -813,7 +813,7 @@ void SoundEngine::setOutputDevice(OUTPUT_DEVICE device) {
     if(osu->getSelectedBeatmap()->getMusic() != NULL) {
         if(osu->isInPlayMode()) {
             osu->getSelectedBeatmap()->unloadMusic();
-            osu->getSelectedBeatmap()->loadMusic(false, osu->getSelectedBeatmap()->m_bForceStreamPlayback);
+            osu->getSelectedBeatmap()->loadMusic(false);
             osu->getSelectedBeatmap()->getMusic()->setLoop(false);
             osu->getSelectedBeatmap()->getMusic()->setPositionMS(prevMusicPositionMS);
         } else {