Ver Fonte

Fix multiple SoundEngine bugs

kiwec há 4 meses atrás
pai
commit
40bea45338

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

@@ -554,7 +554,7 @@ void Beatmap::skipEmptySection() {
     const long nextHitObjectDelta = m_iNextHitObjectTime - (long)m_iCurMusicPosWithOffsets;
 
     if(!osu_end_skip.getBool() && nextHitObjectDelta < 0)
-        m_music->setPositionMS(max(m_music->getLengthMS(), (unsigned long)1) - 1);
+        m_music->setPositionMS(max(m_music->getLengthMS(), (u32)1) - 1);
     else
         m_music->setPositionMS(max(m_iNextHitObjectTime - (long)(offset * offsetMultiplier), (long)0));
 
@@ -2542,6 +2542,7 @@ void Beatmap::update2() {
                     m_bIsPlaying = true;
 
                     engine->getSound()->play(m_music);
+                    m_music->setLoop(false);
                     m_music->setPositionMS(0);
                     m_music->setVolume(getIdealVolume());
                     m_music->setSpeed(osu->getSpeedMultiplier());

+ 13 - 8
src/App/Osu/Changelog.cpp

@@ -29,17 +29,22 @@ Changelog::Changelog() : ScreenBackable() {
     CHANGELOG latest;
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
-    latest.changes.push_back("- Added SoundEngine auto-restart settings");
-    latest.changes.push_back("- Disabled FPoSu noclip by default");
-    latest.changes.push_back("- Fixed auto mod staying on after Ctrl+clicking a map");
-    latest.changes.push_back("- Fixed downloads sometimes failing on Windows");
-    latest.changes.push_back("- Fixed recent score times not being visible in leaderboards");
-    latest.changes.push_back("- Fixed restarting map while watching a replay");
     latest.changes.push_back("- Improved sound engine reliability");
-    latest.changes.push_back("- Re-added win_snd_wasapi_exclusive convar");
-    latest.changes.push_back("- User mods will no longer change when watching a replay or joining a multiplayer room");
     changelogs.push_back(latest);
 
+    CHANGELOG v35_03;
+    v35_03.title = "35.03 (2024-06-10)";
+    v35_03.changes.push_back("- Added SoundEngine auto-restart settings");
+    v35_03.changes.push_back("- Disabled FPoSu noclip by default");
+    v35_03.changes.push_back("- Fixed auto mod staying on after Ctrl+clicking a map");
+    v35_03.changes.push_back("- Fixed downloads sometimes failing on Windows");
+    v35_03.changes.push_back("- Fixed recent score times not being visible in leaderboards");
+    v35_03.changes.push_back("- Fixed restarting map while watching a replay");
+    v35_03.changes.push_back("- Improved sound engine reliability");
+    v35_03.changes.push_back("- Re-added win_snd_wasapi_exclusive convar");
+    v35_03.changes.push_back("- User mods will no longer change when watching a replay or joining a multiplayer room");
+    changelogs.push_back(v35_03);
+
     CHANGELOG v35_02;
     v35_02.title = "35.02 (2024-06-08)";
     v35_02.changes.push_back("- Fixed online leaderboards displaying incorrect values");

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

@@ -57,7 +57,7 @@ Osu *osu = NULL;
 
 // release configuration
 ConVar auto_update("auto_update", true, FCVAR_DEFAULT);
-ConVar osu_version("osu_version", 35.03f, FCVAR_DEFAULT | FCVAR_HIDDEN);
+ConVar osu_version("osu_version", 35.04f, FCVAR_DEFAULT | FCVAR_HIDDEN);
 
 #ifdef _DEBUG
 ConVar osu_debug("osu_debug", true, FCVAR_DEFAULT);

+ 70 - 59
src/Engine/Sound.cpp

@@ -30,7 +30,6 @@ Sound::Sound(std::string filepath, bool stream, bool overlayable, bool loop, boo
     m_bIsOverlayable = overlayable;
     m_fSpeed = 1.0f;
     m_fVolume = 1.0f;
-    m_fLastPlayTime = -1.0f;
 }
 
 std::vector<HCHANNEL> Sound::getActiveChannels() {
@@ -122,12 +121,24 @@ void Sound::initAsync() {
             debugLog("BASS_FX_TempoCreate() returned error %d on file %s\n", BASS_ErrorGetCode(), m_sFilePath.c_str());
             return;
         }
+
+        // Only compute the length once
+        i64 length = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
+        f64 lengthInSeconds = BASS_ChannelBytes2Seconds(m_stream, length);
+        f64 lengthInMilliSeconds = lengthInSeconds * 1000.0;
+        m_length = (u32)lengthInMilliSeconds;
     } else {
         m_sample = BASS_SampleLoad(false, m_sFilePath.c_str(), 0, 0, 1, BASS_SAMPLE_FLOAT);
         if(!m_sample) {
             debugLog("BASS_SampleLoad() returned error %d on file %s\n", BASS_ErrorGetCode(), m_sFilePath.c_str());
             return;
         }
+
+        // Only compute the length once
+        i64 length = BASS_ChannelGetLength(m_sample, BASS_POS_BYTE);
+        f64 lengthInSeconds = BASS_ChannelBytes2Seconds(m_sample, length);
+        f64 lengthInMilliSeconds = lengthInSeconds * 1000.0;
+        m_length = (u32)lengthInMilliSeconds;
     }
 
     m_bAsyncReady = true;
@@ -136,9 +147,11 @@ void Sound::initAsync() {
 void Sound::destroy() {
     if(!m_bReady) return;
 
+    m_bStarted = false;
     m_bReady = false;
     m_bAsyncReady = false;
     m_fLastPlayTime = 0.0;
+    m_bPaused = false;
 
     if(m_bStream) {
         BASS_Mixer_ChannelRemove(m_stream);
@@ -166,24 +179,25 @@ void Sound::setPosition(double percent) {
         return;
     }
 
-    percent = clamp<double>(percent, 0.0, 1.0);
+    percent = clamp<f64>(percent, 0.0, 1.0);
 
-    const double length = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
-    const double lengthInSeconds = BASS_ChannelBytes2Seconds(m_stream, length);
-
-    // NOTE: abused for play interp
-    if(lengthInSeconds * percent < snd_play_interp_duration.getFloat()) {
-        m_fLastPlayTime = engine->getTime() - lengthInSeconds * percent;
-    } else {
-        m_fLastPlayTime = 0.0;
+    f64 length = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
+    if(length < 0) {
+        debugLog("Could not set stream position: error %d\n", BASS_ErrorGetCode());
+        return;
     }
 
-    if(!BASS_ChannelSetPosition(m_stream, (QWORD)(length * percent), BASS_POS_BYTE | BASS_POS_FLUSH)) {
+    f64 lengthInSeconds = BASS_ChannelBytes2Seconds(m_stream, length);
+    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());
         }
     }
+
+    if(m_bStarted) {
+        m_fLastPlayTime = engine->getTime() - (lengthInSeconds * percent);
+    }
 }
 
 void Sound::setPositionMS(unsigned long ms) {
@@ -193,13 +207,11 @@ void Sound::setPositionMS(unsigned long ms) {
         return;
     }
 
-    const QWORD position = BASS_ChannelSeconds2Bytes(m_stream, ms / 1000.0);
-
-    // NOTE: abused for play interp
-    if((double)ms / 1000.0 < snd_play_interp_duration.getFloat())
-        m_fLastPlayTime = engine->getTime() - ((double)ms / 1000.0);
-    else
-        m_fLastPlayTime = 0.0;
+    i64 position = BASS_ChannelSeconds2Bytes(m_stream, ms / 1000.0);
+    if(position < 0) {
+        debugLog("Could not set stream position: error %d\n", BASS_ErrorGetCode());
+        return;
+    }
 
     if(!BASS_ChannelSetPosition(m_stream, position, BASS_POS_BYTE | BASS_POS_FLUSH)) {
         if(Osu::debug->getBool()) {
@@ -207,6 +219,10 @@ void Sound::setPositionMS(unsigned long ms) {
                      BASS_ErrorGetCode(), m_sFilePath.c_str());
         }
     }
+
+    if(m_bStarted) {
+        m_fLastPlayTime = engine->getTime() - ((f64)ms / 1000.0);
+    }
 }
 
 void Sound::setVolume(float volume) {
@@ -276,59 +292,63 @@ void Sound::setLoop(bool loop) {
 }
 
 float Sound::getPosition() {
-    if(!m_bReady) return 0.0f;
+    if(!m_bReady) return 0.f;
     if(!m_bStream) {
         engine->showMessageError("Programmer Error", "Called getPosition on a sample!");
-        return 0.0f;
+        return 0.f;
+    }
+
+    i64 lengthBytes = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
+    if(lengthBytes < 0) {
+        // The stream ended and got freed by BASS_STREAM_AUTOFREE -> invalid handle!
+        return 1.f;
     }
 
-    const QWORD lengthBytes = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
-    const QWORD positionBytes = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    i64 positionBytes = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
     const float position = (float)((double)(positionBytes) / (double)(lengthBytes));
     return position;
 }
 
-unsigned long Sound::getPositionMS() {
+u32 Sound::getPositionMS() {
     if(!m_bReady) return 0;
     if(!m_bStream) {
         engine->showMessageError("Programmer Error", "Called getPositionMS on a sample!");
         return 0;
     }
 
-    const QWORD position = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
-    const double positionInSeconds = BASS_ChannelBytes2Seconds(m_stream, position);
-    const double positionInMilliSeconds = positionInSeconds * 1000.0;
-    const unsigned long positionMS = static_cast<unsigned long>(positionInMilliSeconds);
+    i64 position = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    if(position < 0) {
+        // The stream ended and got freed by BASS_STREAM_AUTOFREE -> invalid handle!
+        return m_length;
+    }
+
+    f64 positionInSeconds = BASS_ChannelBytes2Seconds(m_stream, position);
+    f64 positionInMilliSeconds = positionInSeconds * 1000.0;
+    u32 positionMS = (u32)positionInMilliSeconds;
+    if(!isPlaying()) {
+        return positionMS;
+    }
 
     // special case: a freshly started channel position jitters, lerp with engine time over a set duration to smooth
     // things over
-    const double interpDuration = snd_play_interp_duration.getFloat();
-    const unsigned long interpDurationMS = interpDuration * 1000;
-    if(interpDuration > 0.0 && positionMS < interpDurationMS) {
-        const float speedMultiplier = getSpeed();
-        const double delta = (engine->getTime() - m_fLastPlayTime) * speedMultiplier;
-        if(m_fLastPlayTime > 0.0 && delta < interpDuration && isPlaying()) {
-            const double lerpPercent = clamp<double>(((delta / interpDuration) - snd_play_interp_ratio.getFloat()) /
-                                                         (1.0 - snd_play_interp_ratio.getFloat()),
-                                                     0.0, 1.0);
-            return static_cast<unsigned long>(lerp<double>(delta * 1000.0, (double)positionMS, lerpPercent));
-        }
+    f64 interpDuration = snd_play_interp_duration.getFloat();
+    u32 interpDurationMS = interpDuration * 1000;
+    if(interpDuration <= 0.0 || positionMS >= interpDurationMS) return positionMS;
+
+    f64 speedMultiplier = getSpeed();
+    f64 delta = (engine->getTime() - m_fLastPlayTime) * speedMultiplier;
+    f64 interp_ratio = snd_play_interp_ratio.getFloat();
+    if(delta < interpDuration) {
+        f64 lerpPercent = clamp<f64>(((delta / interpDuration) - interp_ratio) / (1.0 - interp_ratio), 0.0, 1.0);
+        positionMS = (u32)lerp<f64>(delta * 1000.0, (f64)positionMS, lerpPercent);
     }
 
     return positionMS;
 }
 
-unsigned long Sound::getLengthMS() {
+u32 Sound::getLengthMS() {
     if(!m_bReady) return 0;
-    if(!m_bStream) {
-        engine->showMessageError("Programmer Error", "Called getLengthMS on a sample!");
-        return 0;
-    }
-
-    const QWORD length = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
-    const double lengthInSeconds = BASS_ChannelBytes2Seconds(m_stream, length);
-    const double lengthInMilliSeconds = lengthInSeconds * 1000.0;
-    return static_cast<unsigned long>(lengthInMilliSeconds);
+    return m_length;
 }
 
 float Sound::getSpeed() { return m_fSpeed; }
@@ -347,23 +367,14 @@ float Sound::getFrequency() {
 }
 
 bool Sound::isPlaying() {
-    if(!m_bReady) return false;
-
-    auto channels = getActiveChannels();
-    return !channels.empty();
+    return m_bReady && m_bStarted && !m_bPaused && !getActiveChannels().empty();
 }
 
 bool Sound::isFinished() {
-    if(!m_bReady) return false;
-
-    // The sound has never been started!
-    if(m_fLastPlayTime <= 0.0) return false;
-
-    return getActiveChannels().empty() && !m_bPaused;
+    return m_bReady && m_bStarted && !isPlaying();
 }
 
 void Sound::rebuild(std::string newFilePath) {
     m_sFilePath = newFilePath;
-
     reload();
 }

+ 5 - 3
src/Engine/Sound.h

@@ -38,8 +38,8 @@ class Sound : public Resource {
     void setLastPlayTime(double lastPlayTime) { m_fLastPlayTime = lastPlayTime; }
 
     float getPosition();
-    unsigned long getPositionMS();
-    unsigned long getLengthMS();
+    u32 getPositionMS();
+    u32 getLengthMS();
     float getPan() { return m_fPan; }
     float getSpeed();
     float getFrequency();
@@ -63,6 +63,7 @@ class Sound : public Resource {
     SOUNDHANDLE m_stream = 0;
     SOUNDHANDLE m_sample = 0;
 
+    bool m_bStarted = false;
     bool m_bPaused = false;
     bool m_bStream;
     bool m_bIsLooped;
@@ -72,5 +73,6 @@ class Sound : public Resource {
     float m_fPan;
     float m_fSpeed;
     float m_fVolume;
-    double m_fLastPlayTime;
+    f64 m_fLastPlayTime = 0.0;
+    u32 m_length = 0;
 };

+ 2 - 0
src/Engine/SoundEngine.cpp

@@ -731,6 +731,7 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
         }
     }
 
+    snd->m_bStarted = true;
     snd->m_bPaused = false;
     snd->setLastPlayTime(engine->getTime());
     return true;
@@ -813,6 +814,7 @@ void SoundEngine::setOutputDevice(OUTPUT_DEVICE device) {
         if(osu->isInPlayMode()) {
             osu->getSelectedBeatmap()->unloadMusic();
             osu->getSelectedBeatmap()->loadMusic(false, osu->getSelectedBeatmap()->m_bForceStreamPlayback);
+            osu->getSelectedBeatmap()->getMusic()->setLoop(false);
             if(was_playing) {
                 play(osu->getSelectedBeatmap()->getMusic());
             }