Browse Source

Update audio engine

We now always output to a global mixer, removing the distinction between
ASIO/WASAPI and regular BASS output.
Clément Wolf 2 weeks ago
parent
commit
bd1efc3266

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

@@ -1127,15 +1127,11 @@ float Beatmap::getIdealVolume() {
         return volume * modifier;
     }
 
-    auto device_id = BASS_GetDevice();
-    BASS_SetDevice(0);
-
     auto decoder = BASS_StreamCreateFile(false, m_selectedDifficulty2->getFullSoundFilePath().c_str(), 0, 0,
                                          BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT);
     if(!decoder) {
         debugLog("BASS_StreamCreateFile() returned error %d on file %s\n", BASS_ErrorGetCode(),
                  m_selectedDifficulty2->getFullSoundFilePath().c_str());
-        BASS_SetDevice(device_id);
         return volume;
     }
 
@@ -1145,7 +1141,6 @@ float Beatmap::getIdealVolume() {
     if(!BASS_ChannelSetPosition(decoder, position, BASS_POS_BYTE)) {
         debugLog("BASS_ChannelSetPosition() returned error %d\n", BASS_ErrorGetCode());
         BASS_ChannelFree(decoder);
-        BASS_SetDevice(device_id);
         return volume;
     }
 
@@ -1153,7 +1148,6 @@ float Beatmap::getIdealVolume() {
     if(!loudness) {
         debugLog("BASS_Loudness_Start() returned error %d\n", BASS_ErrorGetCode());
         BASS_ChannelFree(decoder);
-        BASS_SetDevice(device_id);
         return volume;
     }
 
@@ -1169,7 +1163,6 @@ float Beatmap::getIdealVolume() {
 
     BASS_Loudness_Stop(loudness);
     BASS_ChannelFree(decoder);
-    BASS_SetDevice(device_id);
 
     last_song = m_selectedDifficulty2->getFullSoundFilePath();
     modifier = (level / -16.f);
@@ -1722,7 +1715,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, false,
+            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_music->setVolume(getIdealVolume());

+ 2 - 8
src/App/Osu/VolumeOverlay.cpp

@@ -261,20 +261,14 @@ bool VolumeOverlay::canChangeVolume() {
 }
 
 void VolumeOverlay::gainFocus() {
-    if(engine->getSound()->isWASAPI()) {
-        // NOTE: wasapi exclusive mode controls the system volume, so don't bother
-        return;
-    }
+    if(engine->getSound()->hasExclusiveOutput()) return;
 
     m_fVolumeInactiveToActiveAnim = 0.0f;
     anim->moveLinear(&m_fVolumeInactiveToActiveAnim, 1.0f, 0.3f, 0.1f, true);
 }
 
 void VolumeOverlay::loseFocus() {
-    if(engine->getSound()->isWASAPI()) {
-        // NOTE: wasapi exclusive mode controls the system volume, so don't bother
-        return;
-    }
+    if(engine->getSound()->hasExclusiveOutput()) return;
 
     m_bVolumeInactiveToActiveScheduled = true;
     anim->deleteExistingAnimation(&m_fVolumeInactiveToActiveAnim);

+ 2 - 2
src/Engine/ResourceManager.cpp

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

+ 1 - 1
src/Engine/ResourceManager.h

@@ -94,7 +94,7 @@ class ResourceManager {
 
     // sounds
     Sound *loadSoundAbs(std::string filepath, std::string resourceName, bool stream = false, bool overlayable = false,
-                        bool threeD = false, bool loop = false, bool prescan = false);
+                        bool loop = false, bool prescan = false);
 
     // shaders
     Shader *loadShader(std::string vertexShaderFilePath, std::string fragmentShaderFilePath, std::string resourceName);

+ 24 - 46
src/Engine/Sound.cpp

@@ -21,12 +21,10 @@ ConVar snd_wav_file_min_size("snd_wav_file_min_size", 51, FCVAR_NONE,
                              "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 threeD, bool loop, bool prescan)
-    : Resource(filepath) {
+Sound::Sound(std::string filepath, bool stream, bool overlayable, bool loop, bool prescan) : Resource(filepath) {
     m_sample = 0;
     m_stream = 0;
     m_bStream = stream;
-    m_bIs3d = threeD;
     m_bIsLooped = loop;
     m_bPrescan = prescan;
     m_bIsOverlayable = overlayable;
@@ -38,35 +36,19 @@ Sound::Sound(std::string filepath, bool stream, bool overlayable, bool threeD, b
 std::vector<HCHANNEL> Sound::getActiveChannels() {
     std::vector<HCHANNEL> channels;
 
-    if(engine->getSound()->isMixing()) {
-        if(m_bStream) {
-            if(BASS_Mixer_ChannelGetMixer(m_stream) != 0) {
-                channels.push_back(m_stream);
-            }
-        } else {
-            for(auto chan : mixer_channels) {
-                if(BASS_Mixer_ChannelGetMixer(chan) != 0) {
-                    channels.push_back(chan);
-                }
-            }
-
-            // Only keep channels that are still playing
-            mixer_channels = channels;
+    if(m_bStream) {
+        if(BASS_Mixer_ChannelGetMixer(m_stream) != 0) {
+            channels.push_back(m_stream);
         }
     } else {
-        if(m_bStream) {
-            if(BASS_ChannelIsActive(m_stream) == BASS_ACTIVE_PLAYING) {
-                channels.push_back(m_stream);
-            }
-        } else {
-            HCHANNEL chans[MAX_OVERLAPPING_SAMPLES] = {0};
-            int nb = BASS_SampleGetChannels(m_sample, chans);
-            for(int i = 0; i < nb; i++) {
-                if(BASS_ChannelIsActive(chans[i]) == BASS_ACTIVE_PLAYING) {
-                    channels.push_back(chans[i]);
-                }
+        for(auto chan : mixer_channels) {
+            if(BASS_Mixer_ChannelGetMixer(chan) != 0) {
+                channels.push_back(chan);
             }
         }
+
+        // Only keep channels that are still playing
+        mixer_channels = channels;
     }
 
     return channels;
@@ -76,15 +58,11 @@ HCHANNEL Sound::getChannel() {
     if(m_bStream) {
         return m_stream;
     } else {
-        if(engine->getSound()->isMixing()) {
-            // If we want to be able to control samples after playing them, we
-            // have to store them here, since bassmix only accepts DECODE streams.
-            auto chan = BASS_SampleGetChannel(m_sample, BASS_SAMCHAN_STREAM | BASS_STREAM_DECODE);
-            mixer_channels.push_back(chan);
-            return chan;
-        } else {
-            return BASS_SampleGetChannel(m_sample, 0);
-        }
+        // If we want to be able to control samples after playing them, we
+        // have to store them here, since bassmix only accepts DECODE streams.
+        auto chan = BASS_SampleGetChannel(m_sample, BASS_SAMCHAN_STREAM | BASS_STREAM_DECODE);
+        mixer_channels.push_back(chan);
+        return chan;
     }
 }
 
@@ -139,17 +117,13 @@ void Sound::initAsync() {
             return;
         }
 
-        auto fx_flags = BASS_FX_FREESOURCE;
-        if(engine->getSound()->isMixing()) fx_flags |= BASS_STREAM_DECODE;
-        m_stream = BASS_FX_TempoCreate(m_stream, fx_flags);
+        m_stream = BASS_FX_TempoCreate(m_stream, BASS_FX_FREESOURCE | BASS_STREAM_DECODE);
         if(!m_stream) {
             debugLog("BASS_FX_TempoCreate() returned error %d on file %s\n", BASS_ErrorGetCode(), m_sFilePath.c_str());
             return;
         }
     } else {
         auto flags = BASS_SAMPLE_FLOAT | BASS_SAMPLE_OVER_POS;
-        if(m_bIs3d) flags |= BASS_SAMPLE_3D | BASS_SAMPLE_MONO;
-
         m_sample =
             BASS_SampleLoad(false, m_sFilePath.c_str(), 0, 0, m_bIsOverlayable ? MAX_OVERLAPPING_SAMPLES : 1, flags);
         if(!m_sample) {
@@ -168,14 +142,18 @@ void Sound::destroy() {
     m_fLastPlayTime = 0.0;
 
     if(m_bStream) {
-        if(engine->getSound()->isMixing()) {
-            BASS_Mixer_ChannelRemove(m_stream);
-        }
-
+        BASS_Mixer_ChannelRemove(m_stream);
         BASS_ChannelStop(m_stream);
         BASS_StreamFree(m_stream);
         m_stream = 0;
     } else {
+        for(auto chan : mixer_channels) {
+            BASS_Mixer_ChannelRemove(chan);
+            BASS_ChannelStop(chan);
+            BASS_ChannelFree(chan);
+        }
+        mixer_channels.clear();
+
         BASS_SampleStop(m_sample);
         BASS_SampleFree(m_sample);
         m_sample = 0;

+ 1 - 3
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 threeD, bool loop, bool prescan);
+    Sound(std::string filepath, bool stream, bool overlayable, bool loop, bool prescan);
     virtual ~Sound() { destroy(); }
 
     std::vector<HCHANNEL> mixer_channels;
@@ -50,7 +50,6 @@ class Sound : public Resource {
     bool isFinished();
 
     inline bool isStream() const { return m_bStream; }
-    inline bool is3d() const { return m_bIs3d; }
     inline bool isLooped() const { return m_bIsLooped; }
     inline bool isOverlayable() const { return m_bIsOverlayable; }
 
@@ -66,7 +65,6 @@ class Sound : public Resource {
 
     bool m_bPaused = false;
     bool m_bStream;
-    bool m_bIs3d;
     bool m_bIsLooped;
     bool m_bPrescan;
     bool m_bIsOverlayable;

+ 55 - 119
src/Engine/SoundEngine.cpp

@@ -65,12 +65,6 @@ ConVar asio_buffer_size("asio_buffer_size", -1, FCVAR_NONE,
 
 ConVar osu_universal_offset_hardcoded("osu_universal_offset_hardcoded", 0.0f, FCVAR_NONE);
 
-DWORD CALLBACK OutputWasapiProc(void *buffer, DWORD length, void *user) {
-    if(engine->getSound()->g_bassOutputMixer == 0) return 0;
-    const int c = BASS_ChannelGetData(engine->getSound()->g_bassOutputMixer, buffer, length);
-    return c < 0 ? 0 : c;
-}
-
 void _RESTART_SOUND_ENGINE_ON_CHANGE(UString oldValue, UString newValue) {
     const int oldValueMS = std::round(oldValue.toFloat() * 1000.0f);
     const int newValueMS = std::round(newValue.toFloat() * 1000.0f);
@@ -326,26 +320,23 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
     debugLog("SoundEngine: initializeOutputDevice( %s ) ...\n", device.name.toUtf8());
 
     if(m_currentOutputDevice.driver == OutputDriver::BASS) {
-        BASS_SetDevice(0);
-        BASS_Free();
         BASS_SetDevice(m_currentOutputDevice.id);
-
         BASS_Free();
-    } else if(m_currentOutputDevice.driver == OutputDriver::BASS_ASIO) {
+        BASS_SetDevice(0);
+    }
+
 #ifdef _WIN32
-        g_bassOutputMixer = 0;
+    if(m_currentOutputDevice.driver == OutputDriver::BASS_ASIO) {
         BASS_ASIO_Free();
-#endif
-        BASS_Free();
     } else if(m_currentOutputDevice.driver == OutputDriver::BASS_WASAPI) {
-#ifdef _WIN32
-        g_bassOutputMixer = 0;
         BASS_WASAPI_Free();
-#endif
-        BASS_Free();
     }
+#endif
 
-    if(device.driver == OutputDriver::NONE) {
+    g_bassOutputMixer = 0;
+    BASS_Free();  // free "No sound" device
+
+    if(device.driver == OutputDriver::NONE || (device.driver == OutputDriver::BASS && device.id == 0)) {
         m_bReady = true;
         m_currentOutputDevice = device;
         snd_output_device.setValue(m_currentOutputDevice.name);
@@ -359,14 +350,18 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
     }
 
     if(device.driver == OutputDriver::BASS) {
-        // NOTE: only used by osu atm (new osu uses 5 instead of 10, but not tested enough for offset problems)
-        BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
+        // Normal output: render playback buffer every 5ms (buffer is 100ms large)
+        BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 5);
         BASS_SetConfig(BASS_CONFIG_UPDATETHREADS, 1);
     } else if(device.driver == OutputDriver::BASS_ASIO || device.driver == OutputDriver::BASS_WASAPI) {
+        // ASIO/WASAPI: let driver decide when to render playback buffer
         BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 0);
         BASS_SetConfig(BASS_CONFIG_UPDATETHREADS, 0);
     }
 
+    // Base offset compensation. Gets modified later when using ASIO driver.
+    osu_universal_offset_hardcoded.setValue(-25.0f);
+
     // allow users to override some defaults (but which may cause beatmap desyncs)
     // we only want to set these if their values have been explicitly modified (to avoid sideeffects in the default
     // case, and for my sanity)
@@ -388,36 +383,40 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
     hwnd = winEnv->getHwnd();
 #endif
 
-    int bass_device_id = device.id;
-    unsigned int runtimeFlags = BASS_DEVICE_STEREO | BASS_DEVICE_FREQ;
+    // We initialize a "No sound" device for measuring loudness and mixing sounds,
+    // regardless of the device we'll use for actual output.
+    unsigned int runtimeFlags = BASS_DEVICE_STEREO | BASS_DEVICE_FREQ | BASS_DEVICE_NOSPEAKER;
+    if(!BASS_Init(0, freq, runtimeFlags | BASS_DEVICE_SOFTWARE, hwnd, NULL)) {
+        m_bReady = false;
+        engine->showMessageError("Sound Error", UString::format("BASS_Init(0) failed (%i)!", BASS_ErrorGetCode()));
+        return false;
+    }
+
     if(device.driver == OutputDriver::BASS) {
-        // Regular BASS: we still want a "No sound" device to check for loudness
-        if(!BASS_Init(0, freq, runtimeFlags | BASS_DEVICE_NOSPEAKER, hwnd, NULL)) {
+        if(!BASS_Init(device.id, freq, runtimeFlags, hwnd, NULL)) {
             m_bReady = false;
-            engine->showMessageError("Sound Error", UString::format("BASS_Init(0) failed (%i)!", BASS_ErrorGetCode()));
+            engine->showMessageError("Sound Error",
+                                     UString::format("BASS_Init(%d) failed (%i)!", device.id, BASS_ErrorGetCode()));
             return false;
         }
-    } else if(device.driver == OutputDriver::BASS_ASIO || device.driver == OutputDriver::BASS_WASAPI) {
-        // ASIO and WASAPI: Initialize BASS on "No sound" device
-        runtimeFlags |= BASS_DEVICE_NOSPEAKER;
-        bass_device_id = 0;
     }
 
-    if(!BASS_Init(bass_device_id, freq, runtimeFlags, hwnd, NULL)) {
+    auto mixer_flags = BASS_SAMPLE_FLOAT | BASS_MIXER_NONSTOP | BASS_MIXER_RESUME;
+    if(device.driver != OutputDriver::BASS) mixer_flags |= BASS_STREAM_DECODE;
+    g_bassOutputMixer = BASS_Mixer_StreamCreate(freq, 2, mixer_flags);
+    if(g_bassOutputMixer == 0) {
         m_bReady = false;
         engine->showMessageError("Sound Error",
-                                 UString::format("BASS_Init(%d) failed (%i)!", bass_device_id, BASS_ErrorGetCode()));
+                                 UString::format("BASS_Mixer_StreamCreate() failed (%i)!", BASS_ErrorGetCode()));
         return false;
     }
 
-    // Starting with bass 2020 2.4.15.2 which has all offset problems
-    // fixed, this is the non-dsound backend compensation.
-    // Gets overwritten later if ASIO or WASAPI driver is used.
-    // Depends on BASS_CONFIG_UPDATEPERIOD/BASS_CONFIG_DEV_BUFFER.
-    osu_universal_offset_hardcoded.setValue(15.0f);
+    // Switch to "No sound" device for all future sound processing
+    // Only g_bassOutputMixer will be output to the actual device!
+    BASS_SetDevice(0);
 
-    if(device.driver == OutputDriver::BASS_ASIO) {
 #ifdef _WIN32
+    if(device.driver == OutputDriver::BASS_ASIO) {
         if(!BASS_ASIO_Init(device.id, 0)) {
             m_bReady = false;
             engine->showMessageError("Sound Error",
@@ -442,15 +441,6 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
             slider->setKeyDelta(info.bufgran == -1 ? info.bufmin : info.bufgran);
         }
 
-        auto mixer_flags = BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE | BASS_MIXER_NONSTOP;
-        g_bassOutputMixer = BASS_Mixer_StreamCreate(sample_rate, 2, mixer_flags);
-        if(g_bassOutputMixer == 0) {
-            m_bReady = false;
-            engine->showMessageError("Sound Error",
-                                     UString::format("BASS_Mixer_StreamCreate() failed (%i)!", BASS_ErrorGetCode()));
-            return false;
-        }
-
         if(!BASS_ASIO_ChannelEnableBASS(false, 0, g_bassOutputMixer, true)) {
             m_bReady = false;
             engine->showMessageError("Sound Error", UString::format("BASS_ASIO_ChannelEnableBASS() failed (code %i)!",
@@ -470,20 +460,18 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
         osu_universal_offset_hardcoded.setValue(-(actual_latency + 25.0f));
         debugLog("ASIO: wanted %f ms, got %f ms latency. Sample rate: %f Hz\n", wanted_latency, actual_latency,
                  sample_rate);
-#endif
     }
 
     if(device.driver == OutputDriver::BASS_WASAPI) {
-#ifdef _WIN32
         const float bufferSize = std::round(win_snd_wasapi_buffer_size.getFloat() * 1000.0f) / 1000.0f;    // in seconds
         const float updatePeriod = std::round(win_snd_wasapi_period_size.getFloat() * 1000.0f) / 1000.0f;  // in seconds
 
         // BASS_WASAPI_RAW ignores sound "enhancements" that some sound cards offer (adds latency)
         // BASS_MIXER_NONSTOP prevents some sound cards from going to sleep when there is no output
+        // BASS_WASAPI_EXCLUSIVE makes neosu have exclusive output to the sound card
         auto flags = BASS_WASAPI_RAW | BASS_MIXER_NONSTOP | BASS_WASAPI_EXCLUSIVE;
 
-        debugLog("WASAPI bufferSize = %f, updatePeriod = %f\n", bufferSize, updatePeriod);
-        if(!BASS_WASAPI_Init(device.id, 0, 0, flags, bufferSize, updatePeriod, OutputWasapiProc, NULL)) {
+        if(!BASS_WASAPI_Init(device.id, 0, 0, flags, bufferSize, updatePeriod, WASAPIPROC_BASS, g_bassOutputMixer)) {
             const int errorCode = BASS_ErrorGetCode();
             if(errorCode == BASS_ERROR_WASAPI_BUFFER) {
                 debugLog("Sound Error: BASS_WASAPI_Init() failed with BASS_ERROR_WASAPI_BUFFER!");
@@ -495,28 +483,14 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
             return false;
         }
 
-        BASS_WASAPI_INFO wasapiInfo;
-        BASS_WASAPI_GetInfo(&wasapiInfo);
-        auto mixer_flags = BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE | BASS_MIXER_NONSTOP;
-        g_bassOutputMixer = BASS_Mixer_StreamCreate(wasapiInfo.freq, wasapiInfo.chans, mixer_flags);
-        if(g_bassOutputMixer == 0) {
-            m_bReady = false;
-            engine->showMessageError("Sound Error",
-                                     UString::format("BASS_Mixer_StreamCreate() failed (%i)!", BASS_ErrorGetCode()));
-            return false;
-        }
-
         if(!BASS_WASAPI_Start()) {
             m_bReady = false;
             engine->showMessageError("Sound Error",
                                      UString::format("BASS_WASAPI_Start() failed (%i)!", BASS_ErrorGetCode()));
             return false;
         }
-
-        // since we use the newer bass/fx dlls for wasapi builds anyway (which have different time handling)
-        osu_universal_offset_hardcoded.setValue(-25.0f);
-#endif
     }
+#endif
 
     m_bReady = true;
     m_currentOutputDevice = device;
@@ -583,24 +557,25 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
 
     BASS_ChannelFlags(channel, snd->isLooped() ? BASS_SAMPLE_LOOP : 0, BASS_SAMPLE_LOOP);
 
-    if(isMixing()) {
-        if(BASS_Mixer_ChannelGetMixer(channel) != 0) return false;
+    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;
+    if(snd->isStream()) {
+        flags |= BASS_STREAM_AUTOFREE;
+    }
 
-        if(!BASS_Mixer_StreamAddChannel(g_bassOutputMixer, channel, flags)) {
-            debugLog("BASS_Mixer_StreamAddChannel() failed (%i)!\n", BASS_ErrorGetCode());
-            return false;
-        }
-    } else {
-        if(BASS_ChannelIsActive(channel) == BASS_ACTIVE_PLAYING) return false;
+    if(!BASS_Mixer_StreamAddChannel(g_bassOutputMixer, channel, flags)) {
+        debugLog("BASS_Mixer_StreamAddChannel() failed (%i)!\n", BASS_ErrorGetCode());
+        return false;
+    }
 
-        if(!BASS_ChannelPlay(channel, false)) {
-            debugLog("SoundEngine::play() couldn't BASS_ChannelPlay(), errorcode %i\n", BASS_ErrorGetCode());
-            return false;
+    // Make sure the mixer is playing! Duh.
+    if(m_currentOutputDevice.driver == OutputDriver::BASS) {
+        if(BASS_ChannelIsActive(g_bassOutputMixer) != BASS_ACTIVE_PLAYING) {
+            if(!BASS_ChannelPlay(g_bassOutputMixer, false)) {
+                debugLog("SoundEngine::play() couldn't BASS_ChannelPlay(), errorcode %i\n", BASS_ErrorGetCode());
+                return false;
+            }
         }
     }
 
@@ -609,32 +584,6 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
     return true;
 }
 
-bool SoundEngine::play3d(Sound *snd, Vector3 pos) {
-    if(!m_bReady || snd == NULL || !snd->isReady() || !snd->is3d()) return false;
-    if(snd_restrict_play_frame.getBool() && engine->getTime() <= snd->getLastPlayTime()) return false;
-
-    HCHANNEL channel = snd->getChannel();
-    if(channel == 0) {
-        debugLog("SoundEngine::play3d() failed to get channel, errorcode %i\n", BASS_ErrorGetCode());
-        return false;
-    }
-
-    BASS_3DVECTOR bassPos = BASS_3DVECTOR(pos.x, pos.y, pos.z);
-    if(!BASS_ChannelSet3DPosition(channel, &bassPos, NULL, NULL)) {
-        debugLog("SoundEngine::play3d() couldn't BASS_ChannelSet3DPosition(), errorcode %i\n", BASS_ErrorGetCode());
-        return false;
-    }
-
-    BASS_Apply3D();
-    if(BASS_ChannelPlay(channel, false)) {
-        snd->setLastPlayTime(engine->getTime());
-        return true;
-    } else {
-        debugLog("SoundEngine::play3d() couldn't BASS_ChannelPlay(), errorcode %i\n", BASS_ErrorGetCode());
-        return false;
-    }
-}
-
 void SoundEngine::pause(Sound *snd) {
     if(!m_bReady || snd == NULL || !snd->isReady()) return;
     if(!snd->isStream()) {
@@ -724,19 +673,6 @@ void SoundEngine::setVolume(float volume) {
     }
 }
 
-void SoundEngine::set3dPosition(Vector3 headPos, Vector3 viewDir, Vector3 viewUp) {
-    if(!m_bReady) return;
-
-    BASS_3DVECTOR bassHeadPos = BASS_3DVECTOR(headPos.x, headPos.y, headPos.z);
-    BASS_3DVECTOR bassViewDir = BASS_3DVECTOR(viewDir.x, viewDir.y, viewDir.z);
-    BASS_3DVECTOR bassViewUp = BASS_3DVECTOR(viewUp.x, viewUp.y, viewUp.z);
-
-    if(!BASS_Set3DPosition(&bassHeadPos, NULL, &bassViewDir, &bassViewUp))
-        debugLog("SoundEngine::set3dPosition() couldn't BASS_Set3DPosition(), errorcode %i\n", BASS_ErrorGetCode());
-    else
-        BASS_Apply3D();  // apply the changes
-}
-
 void SoundEngine::onFreqChanged(UString oldValue, UString newValue) {
     (void)oldValue;
     (void)newValue;

+ 1 - 6
src/Engine/SoundEngine.h

@@ -36,20 +36,15 @@ class SoundEngine {
     void update();
 
     bool play(Sound *snd, float pan = 0.0f, float pitch = 1.0f);
-    bool play3d(Sound *snd, Vector3 pos);
     void pause(Sound *snd);
     void stop(Sound *snd);
 
     bool isASIO() { return m_currentOutputDevice.driver == OutputDriver::BASS_ASIO; }
     bool isWASAPI() { return m_currentOutputDevice.driver == OutputDriver::BASS_WASAPI; }
-    bool isMixing() {
-        return m_currentOutputDevice.driver == OutputDriver::BASS_ASIO ||
-               m_currentOutputDevice.driver == OutputDriver::BASS_WASAPI;
-    }
+    bool hasExclusiveOutput() { return isASIO() || isWASAPI(); }
 
     bool setOutputDevice(OUTPUT_DEVICE device);
     void setVolume(float volume);
-    void set3dPosition(Vector3 headPos, Vector3 viewDir, Vector3 viewUp);
 
     OUTPUT_DEVICE getDefaultDevice();
     OUTPUT_DEVICE getWantedDevice();