5 Commits 58d5748de9 ... ca456e4baa

Auteur SHA1 Bericht Datum
  kiwec ca456e4baa Sort strings in the correct order 4 maanden geleden
  kiwec 56a1543fe6 Fix BASS_ChannelSetPosition desync 4 maanden geleden
  kiwec 5a44f2a8e6 Correctly get music position from BASS 4 maanden geleden
  kiwec 71075eedd3 Fix music position interpolation causing audio desync 4 maanden geleden
  kiwec d62c95c979 Set BASS_STREAM_AUTOFREE on sample channels 4 maanden geleden

+ 17 - 12
src/App/Osu/Beatmap.cpp

@@ -57,8 +57,6 @@ ConVar osu_draw_hitobjects("osu_draw_hitobjects", true, FCVAR_DEFAULT);
 ConVar osu_draw_beatmap_background_image("osu_draw_beatmap_background_image", true, FCVAR_DEFAULT);
 
 ConVar osu_universal_offset("osu_universal_offset", 0.0f, FCVAR_DEFAULT);
-ConVar osu_universal_offset_hardcoded_fallback_dsound("osu_universal_offset_hardcoded_fallback_dsound", -15.0f,
-                                                      FCVAR_DEFAULT);
 ConVar osu_old_beatmap_offset(
     "osu_old_beatmap_offset", 24.0f, FCVAR_DEFAULT,
     "offset in ms which is added to beatmap versions < 5 (default value is hardcoded 24 ms in stable)");
@@ -553,10 +551,13 @@ void Beatmap::skipEmptySection() {
 
     const long nextHitObjectDelta = m_iNextHitObjectTime - (long)m_iCurMusicPosWithOffsets;
 
-    if(!osu_end_skip.getBool() && nextHitObjectDelta < 0)
+    if(!osu_end_skip.getBool() && nextHitObjectDelta < 0) {
         m_music->setPositionMS(max(m_music->getLengthMS(), (u32)1) - 1);
-    else
+        m_bWasSeekFrame = true;
+    } else {
         m_music->setPositionMS(max(m_iNextHitObjectTime - (long)(offset * offsetMultiplier), (long)0));
+        m_bWasSeekFrame = true;
+    }
 
     engine->getSound()->play(osu->getSkin()->getMenuHit());
 
@@ -947,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);
@@ -1039,6 +1040,7 @@ void Beatmap::actualRestart() {
 
     // reset position
     m_music->setPositionMS(0);
+    m_bWasSeekFrame = true;
     m_iCurMusicPos = 0;
 
     m_bIsPlaying = true;
@@ -1778,12 +1780,15 @@ void Beatmap::handlePreviewPlay() {
 
             if(start_at_song_beginning) {
                 m_music->setPositionMS(0);
+                m_bWasSeekFrame = true;
             } else if(m_iContinueMusicPos != 0) {
                 m_music->setPositionMS(m_iContinueMusicPos);
+                m_bWasSeekFrame = true;
             } else {
                 m_music->setPositionMS(m_selectedDifficulty2->getPreviewTime() < 0
                                            ? (unsigned long)(m_music->getLengthMS() * 0.40f)
                                            : m_selectedDifficulty2->getPreviewTime());
+                m_bWasSeekFrame = true;
             }
 
             m_music->setVolume(getIdealVolume());
@@ -1795,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;
 
@@ -1809,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());
@@ -1893,9 +1896,9 @@ void Beatmap::playMissSound() {
 }
 
 unsigned long Beatmap::getMusicPositionMSInterpolated() {
-    if(!osu_interpolate_music_pos.getBool() || isLoading())
+    if(!osu_interpolate_music_pos.getBool() || isLoading()) {
         return m_music->getPositionMS();
-    else {
+    } else {
         const double interpolationMultiplier = 1.0;
 
         // TODO: fix snapping at beginning for maps with instant start
@@ -2555,6 +2558,7 @@ void Beatmap::update2() {
                     engine->getSound()->play(m_music);
                     m_music->setLoop(false);
                     m_music->setPositionMS(0);
+                    m_bWasSeekFrame = true;
                     m_music->setVolume(getIdealVolume());
                     m_music->setSpeed(osu->getSpeedMultiplier());
 
@@ -2564,6 +2568,7 @@ void Beatmap::update2() {
                        m_hitobjects[0]->getTime() > (long)osu_quick_retry_time.getInt())
                         m_music->setPositionMS(
                             max((long)0, m_hitobjects[0]->getTime() - (long)osu_quick_retry_time.getInt()));
+                        m_bWasSeekFrame = true;
 
                     m_bIsRestartScheduledQuick = false;
 
@@ -2601,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);

+ 14 - 7
src/App/Osu/Changelog.cpp

@@ -29,16 +29,23 @@ Changelog::Changelog() : ScreenBackable() {
     CHANGELOG latest;
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
-    latest.changes.push_back("- Changed \"Open Skins folder\" button to open the currently selected skin's folder");
-    latest.changes.push_back("- Fixed master volume control not working on exclusive WASAPI");
-    latest.changes.push_back("- Fixed screenshots failing to save");
-    latest.changes.push_back("- Fixed skins with non-ANSI folder names failing to open on Windows");
-    latest.changes.push_back("- Fixed sliderslide and spinnerspin sounds not looping");
+    latest.changes.push_back("- Fixed Artist/Creator/Title sorting to be in A-Z order");
     latest.changes.push_back("- Improved sound engine reliability");
-    latest.changes.push_back("- Re-added strain graphs");
-    latest.changes.push_back("- Removed sliderhead fadeout animation (set osu_slider_sliderhead_fadeout to 1 for old behavior)");
+    latest.changes.push_back("- Removed herobrine");
     changelogs.push_back(latest);
 
+    CHANGELOG v35_04;
+    v35_04.title = "35.04 (2024-06-11)";
+    v35_04.changes.push_back("- Changed \"Open Skins folder\" button to open the currently selected skin's folder");
+    v35_04.changes.push_back("- Fixed master volume control not working on exclusive WASAPI");
+    v35_04.changes.push_back("- Fixed screenshots failing to save");
+    v35_04.changes.push_back("- Fixed skins with non-ANSI folder names failing to open on Windows");
+    v35_04.changes.push_back("- Fixed sliderslide and spinnerspin sounds not looping");
+    v35_04.changes.push_back("- Improved sound engine reliability");
+    v35_04.changes.push_back("- Re-added strain graphs");
+    v35_04.changes.push_back("- Removed sliderhead fadeout animation (set osu_slider_sliderhead_fadeout to 1 for old behavior)");
+    changelogs.push_back(v35_04);
+
     CHANGELOG v35_03;
     v35_03.title = "35.03 (2024-06-10)";
     v35_03.changes.push_back("- Added SoundEngine auto-restart settings");

+ 10 - 13
src/App/Osu/MainMenu.cpp

@@ -139,7 +139,6 @@ ConVar osu_main_menu_banner_ifupdatedfromoldversion_le3303_text(
 ConVar *MainMenu::m_osu_universal_offset_ref = NULL;
 ConVar *MainMenu::m_osu_universal_offset_hardcoded_ref = NULL;
 ConVar *MainMenu::m_osu_old_beatmap_offset_ref = NULL;
-ConVar *MainMenu::m_osu_universal_offset_hardcoded_fallback_dsound_ref = NULL;
 ConVar *MainMenu::m_osu_mod_random_ref = NULL;
 ConVar *MainMenu::m_osu_songbrowser_background_fade_in_duration_ref = NULL;
 
@@ -149,9 +148,6 @@ MainMenu::MainMenu() : OsuScreen() {
         m_osu_universal_offset_hardcoded_ref = convar->getConVarByName("osu_universal_offset_hardcoded");
     if(m_osu_old_beatmap_offset_ref == NULL)
         m_osu_old_beatmap_offset_ref = convar->getConVarByName("osu_old_beatmap_offset");
-    if(m_osu_universal_offset_hardcoded_fallback_dsound_ref == NULL)
-        m_osu_universal_offset_hardcoded_fallback_dsound_ref =
-            convar->getConVarByName("osu_universal_offset_hardcoded_fallback_dsound");
     if(m_osu_mod_random_ref == NULL) m_osu_mod_random_ref = convar->getConVarByName("osu_mod_random");
     if(m_osu_songbrowser_background_fade_in_duration_ref == NULL)
         m_osu_songbrowser_background_fade_in_duration_ref =
@@ -204,22 +200,23 @@ MainMenu::MainMenu() : OsuScreen() {
 
     // check if the user has never clicked the changelog for this update
     m_bDidUserUpdateFromOlderVersion = false;
-    m_bDidUserUpdateFromOlderVersionLe3300 = false;
-    m_bDidUserUpdateFromOlderVersionLe3303 = false;
+    m_bDrawVersionNotificationArrow = false;
     {
-        m_bDrawVersionNotificationArrow = false;
         if(env->fileExists(NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE)) {
             File versionFile(NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE);
             if(versionFile.canRead()) {
                 float version = std::stof(versionFile.readLine());
                 if(version < Osu::version->getFloat() - 0.0001f) m_bDrawVersionNotificationArrow = true;
-
-                if(version < 33.01f - 0.0001f) m_bDidUserUpdateFromOlderVersionLe3300 = true;
-                if(version < 33.04f - 0.0001f) m_bDidUserUpdateFromOlderVersionLe3303 = true;
-            } else
+                if(version < 34.05) {
+                    // SoundEngine choking issues have been fixed, option has been removed from settings menu
+                    // We leave the cvar available as it could still be useful for some players
+                    convar->getConVarByName("restart_sound_engine_before_playing")->setValue(false);
+                    osu->getOptionsMenu()->save();
+                }
+            } else {
                 m_bDrawVersionNotificationArrow = true;
-        } else
-            m_bDrawVersionNotificationArrow = false;
+            }
+        }
     }
     m_bDidUserUpdateFromOlderVersion = m_bDrawVersionNotificationArrow;  // (same logic atm)
 

+ 0 - 3
src/App/Osu/MainMenu.h

@@ -76,7 +76,6 @@ class MainMenu : public OsuScreen, public MouseListener {
     static ConVar *m_osu_universal_offset_ref;
     static ConVar *m_osu_universal_offset_hardcoded_ref;
     static ConVar *m_osu_old_beatmap_offset_ref;
-    static ConVar *m_osu_universal_offset_hardcoded_fallback_dsound_ref;
     static ConVar *m_osu_mod_random_ref;
     static ConVar *m_osu_songbrowser_background_fade_in_duration_ref;
 
@@ -124,8 +123,6 @@ class MainMenu : public OsuScreen, public MouseListener {
 
     bool m_bDrawVersionNotificationArrow;
     bool m_bDidUserUpdateFromOlderVersion;
-    bool m_bDidUserUpdateFromOlderVersionLe3300;
-    bool m_bDidUserUpdateFromOlderVersionLe3303;
 
     // custom
     float m_fMainMenuAnimTime;

+ 0 - 7
src/App/Osu/OptionsMenu.cpp

@@ -761,13 +761,6 @@ OptionsMenu::OptionsMenu() : ScreenBackable() {
         for(auto i = asio_idx; i < asio_end_idx; i++) {
             m_elements[i].render_condition = RenderCondition::ASIO_ENABLED;
         }
-
-        // Jank
-        addCheckbox("Restart SoundEngine before every song",
-            "Useful if music or sounds start lagging/glitching after a while.\n"
-            "You probably also want to set a start delay, to make sure SoundEngine had time to reinitialize fully.",
-            convar->getConVarByName("restart_sound_engine_before_playing"));
-        addSlider("Song start delay:", 0.0f, 5.0f, convar->getConVarByName("snd_ready_delay"))->setKeyDelta(0.5f);
     }
 
     addSubSection("Volume");

+ 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.04f, FCVAR_DEFAULT | FCVAR_HIDDEN);
+ConVar osu_version("osu_version", 35.05f, FCVAR_DEFAULT | FCVAR_HIDDEN);
 
 #ifdef _DEBUG
 ConVar osu_debug("osu_debug", true, FCVAR_DEFAULT);

+ 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);
             }
         }
 

+ 3 - 3
src/App/Osu/SongBrowser/SongBrowser.cpp

@@ -249,7 +249,7 @@ bool SongBrowser::SortByArtist::operator()(Button const *a, Button const *b) con
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getArtist().c_str(), b->getDatabaseBeatmap()->getArtist().c_str());
     if(res == 0) return a->getSortHack() < b->getSortHack();
-    return res > 0;
+    return res < 0;
 }
 
 bool SongBrowser::SortByBPM::operator()(Button const *a, Button const *b) const {
@@ -266,7 +266,7 @@ bool SongBrowser::SortByCreator::operator()(Button const *a, Button const *b) co
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getCreator().c_str(), b->getDatabaseBeatmap()->getCreator().c_str());
     if(res == 0) return a->getSortHack() < b->getSortHack();
-    return res > 0;
+    return res < 0;
 }
 
 bool SongBrowser::SortByDateAdded::operator()(Button const *a, Button const *b) const {
@@ -310,7 +310,7 @@ bool SongBrowser::SortByTitle::operator()(Button const *a, Button const *b) cons
 
     int res = strcasecmp(a->getDatabaseBeatmap()->getTitle().c_str(), b->getDatabaseBeatmap()->getTitle().c_str());
     if(res == 0) return a->getSortHack() < b->getSortHack();
-    return res > 0;
+    return res < 0;
 }
 
 SongBrowser::SongBrowser() : ScreenBackable() {

+ 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);

+ 91 - 46
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;
 
@@ -171,7 +169,9 @@ void Sound::destroy() {
     m_bReady = false;
     m_bAsyncReady = false;
     m_fLastPlayTime = 0.0;
+    m_fChannelCreationTime = 0.0;
     m_bPaused = false;
+    m_paused_position_ms = 0;
 
     if(m_bStream) {
         BASS_Mixer_ChannelRemove(m_stream);
@@ -193,31 +193,7 @@ void Sound::destroy() {
 }
 
 void Sound::setPosition(double percent) {
-    if(!m_bReady) return;
-    if(!m_bStream) {
-        engine->showMessageError("Programmer Error", "Called setPosition 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());
-        return;
-    }
-
-    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);
-    }
+    return setPositionMS(clamp<f64>(percent, 0.0, 1.0) * m_length);
 }
 
 void Sound::setPositionMS(unsigned long ms) {
@@ -227,21 +203,66 @@ void Sound::setPositionMS(unsigned long ms) {
         return;
     }
 
-    i64 position = BASS_ChannelSeconds2Bytes(m_stream, ms / 1000.0);
-    if(position < 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(!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());
+    // 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)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();
+
+        reload();
+
+        setSpeed(speed);
+        setPan(pan);
+        setLoop(loop);
+        m_bPaused = true;
+        m_paused_position_ms = ms;
+
+        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());
+            }
         }
-    }
 
-    if(m_bStarted) {
-        m_fLastPlayTime = engine->getTime() - ((f64)ms / 1000.0);
+        if(was_playing) {
+            osu->music_unpause_scheduled = true;
+        }
     }
 }
 
@@ -317,6 +338,9 @@ float Sound::getPosition() {
         engine->showMessageError("Programmer Error", "Called getPosition on a sample!");
         return 0.f;
     }
+    if(m_bPaused) {
+        return (f64)m_paused_position_ms / (f64)m_length;
+    }
 
     i64 lengthBytes = BASS_ChannelGetLength(m_stream, BASS_POS_BYTE);
     if(lengthBytes < 0) {
@@ -324,7 +348,13 @@ float Sound::getPosition() {
         return 1.f;
     }
 
-    i64 positionBytes = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    i64 positionBytes = 0;
+    if(isPlaying()) {
+        positionBytes = BASS_Mixer_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    } else {
+        positionBytes = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    }
+
     const float position = (float)((double)(positionBytes) / (double)(lengthBytes));
     return position;
 }
@@ -335,14 +365,22 @@ u32 Sound::getPositionMS() {
         engine->showMessageError("Programmer Error", "Called getPositionMS on a sample!");
         return 0;
     }
+    if(m_bPaused) {
+        return m_paused_position_ms;
+    }
 
-    i64 position = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
-    if(position < 0) {
+    i64 positionBytes = 0;
+    if(isPlaying()) {
+        positionBytes = BASS_Mixer_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    } else {
+        positionBytes = BASS_ChannelGetPosition(m_stream, BASS_POS_BYTE);
+    }
+    if(positionBytes < 0) {
         // The stream ended and got freed by BASS_STREAM_AUTOFREE -> invalid handle!
         return m_length;
     }
 
-    f64 positionInSeconds = BASS_ChannelBytes2Seconds(m_stream, position);
+    f64 positionInSeconds = BASS_ChannelBytes2Seconds(m_stream, positionBytes);
     f64 positionInMilliSeconds = positionInSeconds * 1000.0;
     u32 positionMS = (u32)positionInMilliSeconds;
     if(!isPlaying()) {
@@ -352,15 +390,22 @@ u32 Sound::getPositionMS() {
     // special case: a freshly started channel position jitters, lerp with engine time over a set duration to smooth
     // things over
     f64 interpDuration = snd_play_interp_duration.getFloat();
-    u32 interpDurationMS = interpDuration * 1000;
-    if(interpDuration <= 0.0 || positionMS >= interpDurationMS) return positionMS;
+    if(interpDuration <= 0.0) return positionMS;
+
+    f64 channel_age = engine->getTime() - m_fChannelCreationTime;
+    if(channel_age >= interpDuration) return positionMS;
 
     f64 speedMultiplier = getSpeed();
-    f64 delta = (engine->getTime() - m_fLastPlayTime) * speedMultiplier;
+    f64 delta = channel_age * speedMultiplier;
     f64 interp_ratio = snd_play_interp_ratio.getFloat();
     if(delta < interpDuration) {
+        auto pre_interp_pos = positionMS;
+        delta = (engine->getTime() - m_fLastPlayTime) * speedMultiplier;
         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);
+        if(pre_interp_pos != positionMS) {
+            debugLog("Interpolating music position! %d ms -> %d ms\n", pre_interp_pos, positionMS);
+        }
     }
 
     return positionMS;

+ 3 - 5
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;
@@ -35,7 +35,6 @@ class Sound : public Resource {
     void setFrequency(float frequency);
     void setPan(float pan);
     void setLoop(bool loop);
-    void setLastPlayTime(double lastPlayTime) { m_fLastPlayTime = lastPlayTime; }
 
     float getPosition();
     u32 getPositionMS();
@@ -44,8 +43,6 @@ class Sound : public Resource {
     float getSpeed();
     float getFrequency();
 
-    inline double getLastPlayTime() const { return m_fLastPlayTime; }
-
     bool isPlaying();
     bool isFinished();
 
@@ -67,12 +64,13 @@ class Sound : public Resource {
     bool m_bPaused = false;
     bool m_bStream;
     bool m_bIsLooped;
-    bool m_bPrescan;
     bool m_bIsOverlayable;
 
     float m_fPan;
     float m_fSpeed;
     float m_fVolume;
     f64 m_fLastPlayTime = 0.0;
+    f64 m_fChannelCreationTime = 0.0;
+    u32 m_paused_position_ms = 0;
     u32 m_length = 0;
 };

+ 20 - 16
src/Engine/SoundEngine.cpp

@@ -143,7 +143,7 @@ SoundEngine::SoundEngine() {
     // all beatmaps timed to non-iTunesSMPB + 529 sample deletion offsets on old dlls pre 2015
     BASS_SetConfig(BASS_CONFIG_MP3_OLDGAPS, 1);
 
-    // avoids lag/jitter in BASS_ChannelGetPosition() shortly after a BASS_ChannelPlay() after loading/silence
+    // avoids lag/jitter in BASS_Mixer_ChannelGetPosition() shortly after a BASS_ChannelPlay() after loading/silence
     BASS_SetConfig(BASS_CONFIG_DEV_NONSTOP, 1);
 
     // if set to 1, increases sample playback latency by 10 ms
@@ -470,18 +470,16 @@ bool SoundEngine::init_bass_mixer(OUTPUT_DEVICE device) {
     }
 
     if(device.driver == OutputDriver::BASS) {
-        if(!BASS_Init(device.id, freq, bass_flags, NULL, NULL)) {
+        if(!BASS_Init(device.id, freq, bass_flags | BASS_DEVICE_SOFTWARE, NULL, NULL)) {
             ready_since = -1.0;
             debugLog("BASS_Init(%d) errored out.\n", device.id);
             display_bass_error();
             return false;
         }
-
-        osu_universal_offset_hardcoded.setValue(-25.f);
     }
 
     auto mixer_flags = BASS_SAMPLE_FLOAT | BASS_MIXER_NONSTOP | BASS_MIXER_RESUME;
-    if(device.driver != OutputDriver::BASS) mixer_flags |= BASS_STREAM_DECODE;
+    if(device.driver != OutputDriver::BASS) mixer_flags |= BASS_STREAM_DECODE | BASS_MIXER_POSEX;
     g_bassOutputMixer = BASS_Mixer_StreamCreate(freq, 2, mixer_flags);
     if(g_bassOutputMixer == 0) {
         ready_since = -1.0;
@@ -501,6 +499,9 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
 
     shutdown();
 
+    // We compensate for latency via BASS_ATTRIB_MIXER_LATENCY
+    osu_universal_offset_hardcoded.setValue(0.f);
+
     if(device.driver == OutputDriver::NONE || (device.driver == OutputDriver::BASS && device.id == 0)) {
         ready_since = -1.0;
         m_currentOutputDevice = device;
@@ -593,7 +594,7 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
 
         double wanted_latency = 1000.0 * asio_buffer_size.getFloat() / sample_rate;
         double actual_latency = 1000.0 * (double)BASS_ASIO_GetLatency(false) / sample_rate;
-        osu_universal_offset_hardcoded.setValue(-(actual_latency + 25.0f));
+        BASS_ChannelSetAttribute(g_bassOutputMixer, BASS_ATTRIB_MIXER_LATENCY, actual_latency / 1000.0);
         debugLog("ASIO: wanted %f ms, got %f ms latency. Sample rate: %f Hz\n", wanted_latency, actual_latency,
                  sample_rate);
     }
@@ -637,7 +638,7 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
             return false;
         }
 
-        osu_universal_offset_hardcoded.setValue(-(25.0f + win_snd_wasapi_buffer_size.getFloat() * 1000.0f));
+        BASS_ChannelSetAttribute(g_bassOutputMixer, BASS_ATTRIB_MIXER_LATENCY, win_snd_wasapi_buffer_size.getFloat());
     }
 #endif
 
@@ -685,7 +686,7 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
     }
 
     if(snd->isOverlayable() && snd_restrict_play_frame.getBool()) {
-        if(engine->getTime() <= snd->getLastPlayTime()) {
+        if(engine->getTime() <= snd->m_fLastPlayTime) {
             return false;
         }
     }
@@ -712,11 +713,7 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
 
     if(BASS_Mixer_ChannelGetMixer(channel) != 0) return false;
 
-    auto flags = BASS_MIXER_DOWNMIX | BASS_MIXER_NORAMPIN;
-    if(snd->isStream()) {
-        flags |= BASS_STREAM_AUTOFREE;
-    }
-
+    auto flags = BASS_MIXER_DOWNMIX | BASS_MIXER_NORAMPIN | BASS_STREAM_AUTOFREE;
     if(!BASS_Mixer_StreamAddChannel(g_bassOutputMixer, channel, flags)) {
         debugLog("BASS_Mixer_StreamAddChannel() failed (%i)!\n", BASS_ErrorGetCode());
         return false;
@@ -733,8 +730,14 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
     }
 
     snd->m_bStarted = true;
-    snd->m_bPaused = false;
-    snd->setLastPlayTime(engine->getTime());
+    snd->m_fChannelCreationTime = engine->getTime();
+    if(snd->m_bPaused) {
+        snd->m_bPaused = false;
+        snd->m_fLastPlayTime = snd->m_fChannelCreationTime - (((f64)snd->m_paused_position_ms) / 1000.0);
+    } else {
+        snd->m_fLastPlayTime = snd->m_fChannelCreationTime;
+    }
+
     return true;
 }
 
@@ -760,6 +763,7 @@ void SoundEngine::pause(Sound *snd) {
     snd->setPan(pan);
     snd->setLoop(loop);
     snd->m_bPaused = true;
+    snd->m_paused_position_ms = pos;
 }
 
 void SoundEngine::stop(Sound *snd) {
@@ -809,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 {