From af2cd7dce717f411ba04dd2996682f0ebe78f5c5 Mon Sep 17 00:00:00 2001 From: Gondos Date: Sat, 4 Oct 2025 01:36:15 +0200 Subject: [PATCH 1/3] SDL3_mixer prototype --- Extern/CMakeLists.txt | 1 + Extern/SDL3/CMakeLists.txt | 52 ++++++++++ Sound/XRSound/CMakeLists.txt | 47 +-------- Sound/XRSound/LICENSE | 33 ------- Sound/XRSound/src/CMakeLists.txt | 9 +- Sound/XRSound/src/ISound.cpp | 113 ++++++++++++++++++++++ Sound/XRSound/src/ISound.h | 42 ++++++++ Sound/XRSound/src/ModuleXRSoundEngine.cpp | 3 +- Sound/XRSound/src/VesselXRSoundEngine.cpp | 5 +- Sound/XRSound/src/XRSoundEngine.cpp | 18 ++-- Sound/XRSound/src/XRSoundEngine.h | 6 +- Sound/XRSound/src/XRSoundEngine30.cpp | 1 + 12 files changed, 234 insertions(+), 96 deletions(-) create mode 100644 Extern/SDL3/CMakeLists.txt create mode 100644 Sound/XRSound/src/ISound.cpp create mode 100644 Sound/XRSound/src/ISound.h diff --git a/Extern/CMakeLists.txt b/Extern/CMakeLists.txt index a8690b14e..f3bc01c56 100644 --- a/Extern/CMakeLists.txt +++ b/Extern/CMakeLists.txt @@ -6,6 +6,7 @@ endif(NOT DEFINED ORBITER_BINARY_ROOT_DIR) add_subdirectory(Lua) add_subdirectory(zlib) +add_subdirectory(SDL3) ## LFS add_library(lfs SHARED luafilesystem/src/lfs.c) diff --git a/Extern/SDL3/CMakeLists.txt b/Extern/SDL3/CMakeLists.txt new file mode 100644 index 000000000..da6007c39 --- /dev/null +++ b/Extern/SDL3/CMakeLists.txt @@ -0,0 +1,52 @@ +project(SDL3) + +Include(FetchContent) + +set(SDL_SHARED ON) +set(SDL_STATIC OFF) +#set(SDL_INSTALL ON) + + +FetchContent_Declare( + SDL3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git +# GIT_TAG release-3.2.24 + GIT_TAG 8824eace93de228032639f11df262df91a6257a4 + OVERRIDE_FIND_PACKAGE +) +FetchContent_MakeAvailable(SDL3) + +set_target_properties(SDL3-shared PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${ORBITER_BINARY_ROOT_DIR} + LIBRARY_OUTPUT_DIRECTORY ${ORBITER_BINARY_ROOT_DIR} + RUNTIME_OUTPUT_DIRECTORY ${ORBITER_BINARY_ROOT_DIR} +) + +set_target_properties(SDL3_Headers PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${ORBITER_INSTALL_SDK_DIR}/include" + LIBRARY_OUTPUT_DIRECTORY "${ORBITER_INSTALL_SDK_DIR}/include" + RUNTIME_OUTPUT_DIRECTORY "${ORBITER_INSTALL_SDK_DIR}/include" +) + +file(GLOB SDL3_HEADERS "${SDL3_SOURCE_DIR}/include/SDL3/*.h" "${SDL3_BINARY_DIR}/include-revision/SDL3/*.h") +file(COPY ${SDL3_HEADERS} DESTINATION "${ORBITER_BINARY_SDK_DIR}/include/SDL3") + +install(TARGETS SDL3_Headers RUNTIME DESTINATION "${ORBITER_INSTALL_SDK_DIR}/include") +install(TARGETS SDL3-shared RUNTIME DESTINATION ${ORBITER_INSTALL_ROOT_DIR}) + +############### +## SDL_mixer ## +############### + +set(BUILD_SHARED_LIBS ON) + +FetchContent_Declare( + SDL3_mixer + GIT_REPOSITORY https://github.com/libsdl-org/SDL_mixer.git + GIT_TAG 5cdf029bae982df1d6c210f915fc151a616d982f +) +FetchContent_MakeAvailable(SDL3_mixer) + +install(TARGETS SDL3_mixer-shared RUNTIME DESTINATION ${ORBITER_INSTALL_ROOT_DIR}) +file(GLOB SDL3_MIXER_HEADERS "${SDL3_mixer_SOURCE_DIR}/include/SDL3_mixer/*.h") +file(COPY ${SDL3_MIXER_HEADERS} DESTINATION "${ORBITER_BINARY_SDK_DIR}/include/SDL3_Mixer") diff --git a/Sound/XRSound/CMakeLists.txt b/Sound/XRSound/CMakeLists.txt index 5d510e516..f4c4612d2 100644 --- a/Sound/XRSound/CMakeLists.txt +++ b/Sound/XRSound/CMakeLists.txt @@ -1,49 +1,7 @@ -## irrKlang -if(NOT IRRKLANG_DIR) - set(IRRKLANG_DIR ${EXTERN_DIR}/irrKlang/${ARCH}) - - if(NOT EXISTS ${IRRKLANG_DIR}) - if(BUILD64) - set(IRRKLANG_URL https://www.ambiera.at/downloads/irrKlang-64bit-1.6.0.zip) - else() - set(IRRKLANG_URL https://www.ambiera.at/downloads/irrKlang-32bit-1.6.0.zip) - endif() - - include(FetchContent) - FetchContent_Declare(irrKlang - URL ${IRRKLANG_URL} - SOURCE_DIR ${IRRKLANG_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP ON - ) - - message("Downloading irrKlang from ${IRRKLANG_URL} ...") - FetchContent_MakeAvailable(irrKlang) - - else() - message("Found existing irrKlang at ${IRRKLANG_DIR}") - endif() - -elseif(NOT EXISTS ${IRRKLANG_DIR}) - message(FATAL_ERROR "IRRKLANG_DIR does not exist") -endif() - -if(BUILD64) - set(IRRKLANG_BIN_DIR ${IRRKLANG_DIR}/bin/winx64-visualStudio) - set(IRRKLANG_LIB_DIR ${IRRKLANG_DIR}/lib/Winx64-visualStudio) -else() - # because of a bug in the 32bit version of irrKlang (it has hidden "__MACOSX" dir), - # CMake does not strip the top-level directory - string(APPEND IRRKLANG_DIR /irrKlang-1.6.0) - - set(IRRKLANG_BIN_DIR ${IRRKLANG_DIR}/bin/win32-visualStudio) - set(IRRKLANG_LIB_DIR ${IRRKLANG_DIR}/lib/Win32-visualStudio) -endif() -set(IRRKLANG_LIB ${IRRKLANG_LIB_DIR}/irrKlang.lib) - ## XRSound set(ASSET_DIR ${CMAKE_CURRENT_SOURCE_DIR}/assets) set(SOLUTION_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) -set(IRRKLANG_DLL_DIR ${IRRKLANG_DIR}) + # Deploy assets file(GLOB config_files ${ASSET_DIR}/XRSound/*.cfg) @@ -55,9 +13,6 @@ add_custom_target(XRSound_assets ALL COMMAND ${CMAKE_COMMAND} -E copy ${ASSET_DIR}/XRSound/ReadMe.txt ${ORBITER_BINARY_ROOT_DIR}/XRSound/ COMMAND ${CMAKE_COMMAND} -E copy "${ASSET_DIR}/Doc/XRSound User Manual.pdf" ${ORBITER_BINARY_ROOT_DIR}/Doc/ COMMAND ${CMAKE_COMMAND} -E copy ${SOLUTION_DIR}/XRSound.h ${ORBITER_BINARY_SDK_DIR}/XRSound/ - COMMAND ${CMAKE_COMMAND} -E copy ${IRRKLANG_BIN_DIR}/ikpMP3.dll ${ORBITER_BINARY_ROOT_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${IRRKLANG_BIN_DIR}/ikpFlac.dll ${ORBITER_BINARY_ROOT_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${IRRKLANG_BIN_DIR}/irrKlang.dll ${ORBITER_BINARY_ROOT_DIR} COMMAND ${CMAKE_COMMAND} -E copy_directory ${ASSET_DIR}/XRSound/Default ${ORBITER_BINARY_ROOT_DIR}/XRSound/Default ) diff --git a/Sound/XRSound/LICENSE b/Sound/XRSound/LICENSE index 2fc172fd5..12846461b 100644 --- a/Sound/XRSound/LICENSE +++ b/Sound/XRSound/LICENSE @@ -19,36 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -============================================================================= - -DEPENDENCIES: - -1) XRSound uses the irrKlang Sound Engine. -See https://www.ambiera.com/irrklang/license.html for details -about the ikkLang license. Its license as of 31-July-2021 is quoted below: - -The irrKlang License -irrKlang's source codes, documentation and binaries contained within the -distributed archive are copyright © Nikolaus Gebhardt / Ambiera 2001-2020. - -The contents of the irrKlang distribution archive may not be redistributed, -reproduced, modified, transmitted, broadcast, published or adapted in any way, -shape or form, without the prior written consent of the owner, Nikolaus -Gebhardt. - -The irrKlang.dll, irrKlang.so and libirrklang.dylib files may be -redistributed without the author's prior permission in non-commercial -products, and must remain unmodified except for compressing the file. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff --git a/Sound/XRSound/src/CMakeLists.txt b/Sound/XRSound/src/CMakeLists.txt index 497c2ecac..4b6faa930 100644 --- a/Sound/XRSound/src/CMakeLists.txt +++ b/Sound/XRSound/src/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(XRSound_dll SHARED Resource.rc Utils/ConfigFileParser.cpp Utils/FileList.cpp + ISound.cpp ) set_target_properties(XRSound_dll PROPERTIES @@ -22,18 +23,22 @@ set_target_properties(XRSound_dll PROPERTIES target_include_directories(XRSound_dll PUBLIC ${ORBITER_SOURCE_SDK_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/Utils - ${IRRKLANG_DIR}/include +# ${IRRKLANG_DIR}/include ) target_link_libraries(XRSound_dll ${ORBITER_LIB} ${ORBITER_SDK_LIB} - ${IRRKLANG_LIB} +# ${IRRKLANG_LIB} + SDL3::SDL3-shared + SDL3_mixer::SDL3_mixer-shared ) add_dependencies(XRSound_dll ${OrbiterTgt} Orbitersdk + SDL3::SDL3-shared + SDL3_mixer::SDL3_mixer-shared ) install(TARGETS XRSound_dll diff --git a/Sound/XRSound/src/ISound.cpp b/Sound/XRSound/src/ISound.cpp new file mode 100644 index 000000000..5e1063735 --- /dev/null +++ b/Sound/XRSound/src/ISound.cpp @@ -0,0 +1,113 @@ +#include "ISound.h" + +ISoundEngine::ISoundEngine() +{ + if(!MIX_Init()) { + exit(1); + } + m_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); + + if(!m_mixer) { + exit(2); + } +} + +ISoundEngine::~ISoundEngine() +{ + MIX_DestroyMixer(m_mixer); + MIX_Quit(); +} + +ISound *ISoundEngine::play2D(const char * soundFileName, bool playLooped, bool startPaused, bool track) +{ + MIX_Audio *audio; + + auto v = m_buffers.find(soundFileName); + + if(v == m_buffers.end()) { + printf("ISoundEngine::play2D: late loading of %s\n", soundFileName); + audio = MIX_LoadAudio(m_mixer, soundFileName, false); + m_buffers.insert({soundFileName, audio}); + } else { + printf("ISoundEngine::play2D: using cache of %s\n", soundFileName); + audio = v->second; + } + + if(audio == nullptr) { + fprintf(stderr,"failed to create buffer for %s\n", soundFileName); + exit(EXIT_FAILURE); + } + + MIX_Track *audio_track = MIX_CreateTrack(m_mixer); + if(!MIX_SetTrackAudio(audio_track, audio)) + exit(EXIT_FAILURE); + + ISound *snd = new ISound(audio_track, playLooped, startPaused); + + if(startPaused || track) + return snd; + else + return nullptr; +} + +ISound::ISound(MIX_Track *track, bool looped, bool paused) +{ + m_started = !paused; + m_paused = paused; + m_pan = 0; + m_track = track; + m_options = SDL_CreateProperties(); + SDL_SetNumberProperty(m_options, MIX_PROP_PLAY_LOOPS_NUMBER, looped ? -1 : 0); + if(!paused) + MIX_PlayTrack(m_track, m_options); +} + +ISound::~ISound() +{ + if(MIX_TrackPlaying(m_track)) { + MIX_StopTrack(m_track, 0); + } + SDL_DestroyProperties(m_options); + MIX_DestroyTrack(m_track); +} + +void ISound::setIsLooped(bool looped) +{ + +} + +void ISound::setIsPaused(bool paused) +{ + if(paused) + MIX_PauseTrack(m_track); + else { + if(m_started) + MIX_ResumeTrack(m_track); + else { + m_started = true; + MIX_PlayTrack(m_track, m_options); + } + } + + m_paused = paused; +} + +void ISound::setPan(float pan) +{ + m_pan = pan; + const float right = (pan + 1.0f) / 2.0f; + const MIX_StereoGains gains = { 1.0f - right, right }; + MIX_SetTrackStereo(m_track, &gains); +} + +bool ISound::setPlayPosition(unsigned int pos) +{ + Sint64 frames = MIX_TrackMSToFrames(m_track, pos); + return MIX_SetTrackPlaybackPosition(m_track, frames); +} + +unsigned int ISound::getPlayPosition() +{ + Sint64 frames = MIX_GetTrackPlaybackPosition(m_track); + return MIX_TrackFramesToMS(m_track, frames); +} diff --git a/Sound/XRSound/src/ISound.h b/Sound/XRSound/src/ISound.h new file mode 100644 index 000000000..b4249bf87 --- /dev/null +++ b/Sound/XRSound/src/ISound.h @@ -0,0 +1,42 @@ +#pragma once +#include +//#include +//#include +#include +#include +#include + +class ISound { +public: + ISound(MIX_Track *audio, bool looped, bool paused); + ~ISound(); + bool isFinished() { return m_started && !m_paused && !MIX_TrackPlaying(m_track); } + void setVolume(float vol) { MIX_SetTrackGain(m_track, vol); } + void setIsLooped(bool looped); + void setIsPaused(bool paused); + void setPan(float pan); + float getPan() { return m_pan; } + bool setPlaybackSpeed(float speed) { return MIX_SetTrackFrequencyRatio(m_track, speed); } + float getPlaybackSpeed() { return MIX_GetTrackFrequencyRatio(m_track); } + bool setPlayPosition(unsigned int pos); + unsigned int getPlayPosition(); + void stop() { MIX_StopTrack(m_track, 0);} +private: + MIX_Track *m_track; + SDL_PropertiesID m_options; + float m_pan; + bool m_paused; + bool m_started; +}; + +class ISoundEngine { +public: + ISoundEngine(); + ~ISoundEngine(); + ISound *play2D(const char *soundFileName, bool playLooped=false, bool startPaused=false, bool track=false); + const char *getDriverName() { return SDL_GetCurrentAudioDriver(); } +// ALuint preload(const char *soundFileName); +private: + MIX_Mixer *m_mixer; + std::unordered_map m_buffers; +}; diff --git a/Sound/XRSound/src/ModuleXRSoundEngine.cpp b/Sound/XRSound/src/ModuleXRSoundEngine.cpp index ee8651c09..e857d01ee 100644 --- a/Sound/XRSound/src/ModuleXRSoundEngine.cpp +++ b/Sound/XRSound/src/ModuleXRSoundEngine.cpp @@ -8,6 +8,7 @@ #include "ModuleXRSoundEngine.h" #include "XRSoundConfigFileParser.h" #include "XRSoundDLL.h" // for XRSoundDLL::GetAbsoluteSimTime() +#include "ISound.h" // Static method to create a new instance of an XRSoundEngine for a module. This is the ONLY place where new // XRSoundEngine instances for modules are constructed. @@ -106,7 +107,7 @@ void ModuleXRSoundEngine::UpdateSoundState(WavContext &context) else { // sound has finished, so release its resources - pISound->drop(); + delete pISound; context.pISound = nullptr; } } diff --git a/Sound/XRSound/src/VesselXRSoundEngine.cpp b/Sound/XRSound/src/VesselXRSoundEngine.cpp index cba955644..9b12d4171 100644 --- a/Sound/XRSound/src/VesselXRSoundEngine.cpp +++ b/Sound/XRSound/src/VesselXRSoundEngine.cpp @@ -10,6 +10,7 @@ #include "DefaultSoundGroupPreSteps.h" #include "AnimationState.h" #include "XRSoundDLL.h" // for XRSoundDLL::GetAbsoluteSimTime() +#include "ISound.h" // static pressure threshold at which OAT and Mach values are valid; matches the XR constant value // (APPROX) AS SEEN ON SURFACE MFD, BUT TOO RISKY TO USE IN PRODUCTION: const double OAT_VALID_STATICP_THRESHOLD = 0.014; // in pascals @@ -288,7 +289,7 @@ void VesselXRSoundEngine::UpdateSoundState(WavContext &context) { release_sound: // sound has finished, so release its resources - pISound->drop(); + delete pISound; context.pISound = nullptr; } } @@ -752,7 +753,7 @@ double VesselXRSoundEngine::GetPlasmaLevel() // This code is lifted from SetHullTempsPostStep::AddHeat in XR1PostSteps.cpp. It does not need to be precise; it is just to get an approximate // level of plasma for the vessel in at atmosphere. const double HULL_HEATING_FACTOR = 3.1034e-10; - const double workingHullHeatingFactor = HULL_HEATING_FACTOR * 0.642; // tweaked very carefully… + const double workingHullHeatingFactor = HULL_HEATING_FACTOR * 0.642; // tweaked very carefully… const double atmPressure = pVessel->GetAtmPressure(); const double airspeed = pVessel->GetAirspeed(); // check *airspeed* here, not ground speed diff --git a/Sound/XRSound/src/XRSoundEngine.cpp b/Sound/XRSound/src/XRSoundEngine.cpp index 27011662d..7244508ef 100644 --- a/Sound/XRSound/src/XRSoundEngine.cpp +++ b/Sound/XRSound/src/XRSoundEngine.cpp @@ -11,6 +11,7 @@ #include "DefaultSoundGroupPreSteps.h" #include "AnimationState.h" #include "XRSoundDLL.h" // for XRSoundDLL::GetAbsoluteSimTime() +#include "ISound.h" // static data and methods @@ -31,15 +32,12 @@ bool XRSoundEngine::InitializeIrrKlangEngine() // Note: we do NOT want to use multi-threading here: that opens up possible timing gaps / race conditions between the time // we query a given sound's state in our thread and when the OTHER thread updates that state. // TODO: if and when we want to support 3D sounds, will need to add ESEO_USE_3D_BUFFERS flag below as well - s_pKlangEngine = createIrrKlangDevice( - ESOD_AUTO_DETECT, - ESEO_LOAD_PLUGINS | ESEO_PRINT_DEBUG_INFO_TO_DEBUGGER - ); + s_pKlangEngine = new ISoundEngine(); char logMsg[256]; // can't use CString easily here b/c Orbiter's oapiWriteLog takes a char * instead of const char * for some bizarre reason. if (s_pKlangEngine) - sprintf_s(logMsg, "%s initialized using sound driver %s; irrKlang version = %s. XRSound UpdateInterval = %.03lf (%.1lf updates per second)", - GetVersionStr(), XRSoundEngine::GetSoundDriverName(), IRR_KLANG_VERSION, + sprintf_s(logMsg, "%s initialized using sound driver %s. XRSound UpdateInterval = %.03lf (%.1lf updates per second)", + GetVersionStr(), XRSoundEngine::GetSoundDriverName(), s_globalConfig.UpdateInterval, (1.0 / s_globalConfig.UpdateInterval)); else sprintf_s(logMsg, "%s ERROR: could not initialize default sound device.", GetVersionStr()); @@ -80,7 +78,7 @@ void XRSoundEngine::DestroyIrrKlangEngine() } // free and reset the engine - s_pKlangEngine->drop(); + delete s_pKlangEngine; s_pKlangEngine = nullptr; s_bIrrKlangEngineNeedsInitialization = true; // need to reinitialize the engine on next LoadWav call } @@ -238,7 +236,7 @@ bool XRSoundEngine::PlayWav(const int soundID, const bool bLoop, const float vol if (pISound->isFinished()) { // this means the previous sound in this slot has finished, but its pISound interface was not freed yet - pISound->drop(); + delete pISound; pISound = nullptr; // we'll obtain a new pISound below } // else sound is already playing, so we will just update its volume and loop settings later @@ -341,7 +339,7 @@ bool XRSoundEngine::StopWavImpl(WavContext *pContext, XRSoundEngine *pEngine) VERBOSE_LOG(pEngine, "XRSoundEngine::StopWavImpl: stopping sound %s", static_cast(pContext->ToStr())); pISound->stop(); } - pISound->drop(); // free irrKlang resources for this sound + delete pISound; // free irrKlang resources for this sound pContext->ResetPlaybackFields(); // reset all playback fields to their initial state, indicating the context is not in use } return bStopped; @@ -548,7 +546,7 @@ void XRSoundEngine::UpdateIrrKlangEngine() if (!XRSoundEngine::IsKlangEngineInitialized()) return; // edge case: there are no sound-enabled vessels in Orbiter yet, so nothing to do - s_pKlangEngine->update(); +// s_pKlangEngine->update(); } // Reset any static data for a simulation restart (e.g., one-shot timers, etc.) diff --git a/Sound/XRSound/src/XRSoundEngine.h b/Sound/XRSound/src/XRSoundEngine.h index 0843b24b9..d1461c9cd 100644 --- a/Sound/XRSound/src/XRSoundEngine.h +++ b/Sound/XRSound/src/XRSoundEngine.h @@ -25,9 +25,11 @@ class ModuleXRSoundEngine; #define oapiGetSimTime ERROR! "Do not invoke oapiGetSimTime: see comment block in XRSoundDLL::clbkPreStep for details" #ifdef XRSOUND_DLL_BUILD -#include -using namespace irrklang; +//#include +//using namespace irrklang; #include "XRSoundConfigFileParser.h" +class ISound; +class ISoundEngine; #else // we're compiling XRSoundLib, so all we need is a forward reference (and we don't want to include the full class definition on the .lib side!) class ISoundEngine; diff --git a/Sound/XRSound/src/XRSoundEngine30.cpp b/Sound/XRSound/src/XRSoundEngine30.cpp index a2b91d3e8..6b47e251c 100644 --- a/Sound/XRSound/src/XRSoundEngine30.cpp +++ b/Sound/XRSound/src/XRSoundEngine30.cpp @@ -6,6 +6,7 @@ // ============================================================== #include "XRSoundEngine.h" +#include "ISound.h" // Functionality added in XRSound version 3.0 From 23ef4270ec0881daa95fe288c39fce7aa4aeb458 Mon Sep 17 00:00:00 2001 From: Gondos Date: Sat, 4 Oct 2025 23:15:49 +0200 Subject: [PATCH 2/3] Add error handling and lower master gain to prevent clipping --- Sound/XRSound/src/ISound.cpp | 96 +++++++++++++++++++++-------- Sound/XRSound/src/ISound.h | 20 ++++-- Sound/XRSound/src/XRSoundEngine.cpp | 2 +- 3 files changed, 84 insertions(+), 34 deletions(-) diff --git a/Sound/XRSound/src/ISound.cpp b/Sound/XRSound/src/ISound.cpp index 5e1063735..2c581aebf 100644 --- a/Sound/XRSound/src/ISound.cpp +++ b/Sound/XRSound/src/ISound.cpp @@ -1,53 +1,76 @@ +// ============================================================== +// SDL3_mixer implementation of the ISound API +// +// Copyright (c) 2025 Gondos +// Licensed under the MIT License +// ============================================================== + #include "ISound.h" +#include "OrbiterAPI.h" ISoundEngine::ISoundEngine() { if(!MIX_Init()) { - exit(1); + oapiWriteLogError("SDL3_mixer: Could not initialize mixer: %s", SDL_GetError()); + m_mixer = nullptr; + return; } m_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL); if(!m_mixer) { - exit(2); + oapiWriteLogError("SDL3_mixer: Could not initialize mixer device: %s", SDL_GetError()); } + + // The sounds fell saturated so we lower the master gain + MIX_SetMasterGain(m_mixer, 0.7f); } ISoundEngine::~ISoundEngine() { + for(auto &audio: m_buffers) { + MIX_DestroyAudio(audio.second); + } MIX_DestroyMixer(m_mixer); MIX_Quit(); } -ISound *ISoundEngine::play2D(const char * soundFileName, bool playLooped, bool startPaused, bool track) +// Create an audio track, attach audio data to it and return a corresponding ISound object +ISound *ISoundEngine::play2D(const char * soundFileName, bool playLooped, bool startPaused) { + // The core handles null pointers so we bail out early if the initialization failed + if(!m_mixer) return nullptr; + MIX_Audio *audio; + // Check if the sound file is alread in the cache auto v = m_buffers.find(soundFileName); - if(v == m_buffers.end()) { - printf("ISoundEngine::play2D: late loading of %s\n", soundFileName); + //printf("ISoundEngine::play2D: late loading of %s\n", soundFileName); audio = MIX_LoadAudio(m_mixer, soundFileName, false); - m_buffers.insert({soundFileName, audio}); + if(audio) { + m_buffers.insert({soundFileName, audio}); + } else { + oapiWriteLogError("SDL3_mixer: Failed to load sound file %s: %s", soundFileName, SDL_GetError()); + return nullptr; + } } else { - printf("ISoundEngine::play2D: using cache of %s\n", soundFileName); + //printf("ISoundEngine::play2D: using cache of %s\n", soundFileName); audio = v->second; } - if(audio == nullptr) { - fprintf(stderr,"failed to create buffer for %s\n", soundFileName); - exit(EXIT_FAILURE); - } - MIX_Track *audio_track = MIX_CreateTrack(m_mixer); - if(!MIX_SetTrackAudio(audio_track, audio)) - exit(EXIT_FAILURE); + if(!audio_track) { + oapiWriteLogError("SDL3_mixer: Failed to create track: %s", SDL_GetError()); + return nullptr; + } - ISound *snd = new ISound(audio_track, playLooped, startPaused); + if(!MIX_SetTrackAudio(audio_track, audio)) { + MIX_DestroyTrack(audio_track); + oapiWriteLogError("SDL3_mixer: Failed to set track audio: %s", SDL_GetError()); + return nullptr; + } - if(startPaused || track) - return snd; - else - return nullptr; + return new ISound(audio_track, playLooped, startPaused); } ISound::ISound(MIX_Track *track, bool looped, bool paused) @@ -58,12 +81,17 @@ ISound::ISound(MIX_Track *track, bool looped, bool paused) m_track = track; m_options = SDL_CreateProperties(); SDL_SetNumberProperty(m_options, MIX_PROP_PLAY_LOOPS_NUMBER, looped ? -1 : 0); - if(!paused) - MIX_PlayTrack(m_track, m_options); + if(!paused) { + if(!MIX_PlayTrack(m_track, m_options)) { + oapiWriteLogError("SDL3_mixer: Failed to play track: %s", SDL_GetError()); + } + } } ISound::~ISound() { + // The doc says that destroying a track that is playing in another thread + // will block so we stop it there. Is it enough? if(MIX_TrackPlaying(m_track)) { MIX_StopTrack(m_track, 0); } @@ -73,19 +101,33 @@ ISound::~ISound() void ISound::setIsLooped(bool looped) { - + // With SDL3_mixer, changing the loops number will only take effects if the track is stopped and restarted + // We don't want that so we only update the m_options member and hope that the user has not started the playback yet + SDL_SetNumberProperty(m_options, MIX_PROP_PLAY_LOOPS_NUMBER, looped ? -1 : 0); + if(m_started) { + // Drop a one time warning if that's not the case + static std::once_flag once; + std::call_once(once, []{ + oapiWriteLogError("SDL3_mixer: Changing looping status of a playing sound is unsupported"); + }); + } } void ISound::setIsPaused(bool paused) { - if(paused) + if(paused) { MIX_PauseTrack(m_track); - else { - if(m_started) + } else { + // We cannot start a track in a paused state + // so we use m_started to check if the track + // needs to be started or resumed + if(m_started) { MIX_ResumeTrack(m_track); - else { + } else { m_started = true; - MIX_PlayTrack(m_track, m_options); + if(!MIX_PlayTrack(m_track, m_options)) { + oapiWriteLogError("SDL3_mixer: Failed to play track: %s", SDL_GetError()); + } } } diff --git a/Sound/XRSound/src/ISound.h b/Sound/XRSound/src/ISound.h index b4249bf87..dbf861214 100644 --- a/Sound/XRSound/src/ISound.h +++ b/Sound/XRSound/src/ISound.h @@ -1,11 +1,18 @@ +// ============================================================== +// SDL3_mixer implementation of the ISound API +// +// Copyright (c) 2025 Gondos +// Licensed under the MIT License +// ============================================================== + #pragma once -#include -//#include -//#include #include #include #include +// An ISound object is just an SDL3_Mixer track with an already attached audio stream +// It can be created paused or playing +// Once the sound is played, it will be destroyed by the XRSound core class ISound { public: ISound(MIX_Track *audio, bool looped, bool paused); @@ -20,7 +27,7 @@ class ISound { float getPlaybackSpeed() { return MIX_GetTrackFrequencyRatio(m_track); } bool setPlayPosition(unsigned int pos); unsigned int getPlayPosition(); - void stop() { MIX_StopTrack(m_track, 0);} + void stop() { MIX_StopTrack(m_track, 0); } private: MIX_Track *m_track; SDL_PropertiesID m_options; @@ -29,13 +36,14 @@ class ISound { bool m_started; }; +// Basic SDL3_mixer sound engine +// It must be instanciated only once class ISoundEngine { public: ISoundEngine(); ~ISoundEngine(); - ISound *play2D(const char *soundFileName, bool playLooped=false, bool startPaused=false, bool track=false); + ISound *play2D(const char *soundFileName, bool playLooped = false, bool startPaused = false); const char *getDriverName() { return SDL_GetCurrentAudioDriver(); } -// ALuint preload(const char *soundFileName); private: MIX_Mixer *m_mixer; std::unordered_map m_buffers; diff --git a/Sound/XRSound/src/XRSoundEngine.cpp b/Sound/XRSound/src/XRSoundEngine.cpp index 7244508ef..42f03add3 100644 --- a/Sound/XRSound/src/XRSoundEngine.cpp +++ b/Sound/XRSound/src/XRSoundEngine.cpp @@ -246,7 +246,7 @@ bool XRSoundEngine::PlayWav(const int soundID, const bool bLoop, const float vol { // sound is not playing, so let's start immediately and track it via its pISound interface // NOTE: we start this paused so that we can set the proper volume level before starting it via UpdateSoundState - pISound = pContext->pISound = s_pKlangEngine->play2D(pContext->csSoundFilename, bLoop, true, true); + pISound = pContext->pISound = s_pKlangEngine->play2D(pContext->csSoundFilename, bLoop, true); if (pISound == nullptr) // this means the sound could not be played; e.g., corrupt file, etc. { VERBOSE_LOG(this, "XRSoundEngine::PlayWav ERROR: could not play sound %s", static_cast(pContext->ToStr())); From bfa54266db543d0a60bdfe95bb629a0be933c2ba Mon Sep 17 00:00:00 2001 From: Gondos Date: Sat, 4 Oct 2025 23:38:50 +0200 Subject: [PATCH 3/3] Remove references to irrklang --- .gitignore | 2 - CMakeLists.txt | 4 - Doc/Orbiter User Manual/CREDITS.tex | 5 +- Sound/XRSound/CMakeLists.txt | 3 - Sound/XRSound/assets/XRSound/XRSound.cfg | 16 +--- Sound/XRSound/src/CMakeLists.txt | 2 - .../XRSound/src/DefaultSoundGroupPreSteps.cpp | 2 +- Sound/XRSound/src/ModuleXRSoundEngine.cpp | 14 ++-- Sound/XRSound/src/VesselXRSoundEngine.cpp | 24 +++--- Sound/XRSound/src/VesselXRSoundEngine.h | 2 +- Sound/XRSound/src/XRSoundDLL.cpp | 21 +---- Sound/XRSound/src/XRSoundDLL.h | 1 - Sound/XRSound/src/XRSoundEngine.cpp | 81 ++++++++----------- Sound/XRSound/src/XRSoundEngine.h | 21 ++--- Sound/XRSound/src/XRSoundEngine30.cpp | 16 ++-- 15 files changed, 82 insertions(+), 132 deletions(-) diff --git a/.gitignore b/.gitignore index e94cb6340..2ed8cf877 100644 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,6 @@ Sound/XRSound/**/Release-with-OrbiterRelease/ .kdev/ [Bb]uild/ -Extern/irrKlang - Doc/**/*.log Doc/**/*.pdf Doc/**/*.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f1c284ac..2f2367335 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,10 +211,6 @@ option(ORBITER_SANITIZER OFF ) -if(ORBITER_BUILD_XRSOUND) - set(IRRKLANG_DIR "" CACHE PATH "Path to the irrKlang library") -endif() - if(ORBITER_BUILD_D3D9CLIENT) find_package(DXSDK REQUIRED) set(DXSDK_DIR diff --git a/Doc/Orbiter User Manual/CREDITS.tex b/Doc/Orbiter User Manual/CREDITS.tex index eb1424183..08cc3a957 100644 --- a/Doc/Orbiter User Manual/CREDITS.tex +++ b/Doc/Orbiter User Manual/CREDITS.tex @@ -22,9 +22,10 @@ \subsection{Source contributions and components} Steve Arch: TransX development \url{http://orbiter.quorg.org} \subsection{Libraries, code, data, algorithms} -\textbf{IrrKlang}\\ +\textbf{SDL3 and SDL3_mixer}\\ Audio and sound library\\ -\url{https://www.ambiera.com/irrklang/}\\ +\url{https://github.com/libsdl-org/SDL/}\\ +\url{https://github.com/libsdl-org/SDL_mixer/}\\ \\ \textbf{Zlib}\\ Compression/decompression library\\ diff --git a/Sound/XRSound/CMakeLists.txt b/Sound/XRSound/CMakeLists.txt index f4c4612d2..06cf431c9 100644 --- a/Sound/XRSound/CMakeLists.txt +++ b/Sound/XRSound/CMakeLists.txt @@ -28,8 +28,5 @@ install(DIRECTORY ${ORBITER_BINARY_ROOT_DIR}/XRSound/Default install(FILES "${ORBITER_BINARY_ROOT_DIR}/Doc/XRSound User Manual.pdf" DESTINATION ${ORBITER_INSTALL_DOC_DIR}/ ) -install(FILES ${ORBITER_BINARY_ROOT_DIR}/ikpMP3.dll ${ORBITER_BINARY_ROOT_DIR}/ikpFlac.dll ${ORBITER_BINARY_ROOT_DIR}/irrKlang.dll - DESTINATION ${ORBITER_INSTALL_ROOT_DIR}/ -) add_subdirectory(src) diff --git a/Sound/XRSound/assets/XRSound/XRSound.cfg b/Sound/XRSound/assets/XRSound/XRSound.cfg index 0640e1b7b..b7b3f8eda 100644 --- a/Sound/XRSound/assets/XRSound/XRSound.cfg +++ b/Sound/XRSound/assets/XRSound/XRSound.cfg @@ -21,23 +21,15 @@ #-------------------------------------------------------------------------- # Space-separated list of sound file extensions supported by XRSound. # -# XRSound uses irrKlang Pro, which supports these sound file formats by default: +# XRSound uses SDL3_mixer, which supports these sound file formats by default: # # RIFF WAVE (*.wav) # Ogg Vorbis (*.ogg) -# MPEG-1 Audio Layer 3 (*.mp3) [via ikpMP3.dll plugin, included] -# Free Lossless Audio Codec (*.flac) [via ikpFlac.dll plugin, included] -# Amiga Modules (*.mod) -# Impulse Tracker (*.it) -# Scream Tracker 3 (*.s3d) -# Fast Tracker 2 (*.xm) +# MPEG-1 Audio Layer 3 (*.mp3) # -# Additional formats may be supported via plugins; visit -# https://www.ambiera.com/irrklang/ for more information. -# -# Default = .wav .ogg .mp3 .flac .mod .it .s3d .xm +# Default = .wav .ogg .mp3 #-------------------------------------------------------------------------- -SupportedSoundFileTypes = .wav .ogg .mp3 .flac .mod .it .s3d .xm +SupportedSoundFileTypes = .wav .ogg .mp3 #-------------------------------------------------------------------------- # Sets the master volume for all sounds. diff --git a/Sound/XRSound/src/CMakeLists.txt b/Sound/XRSound/src/CMakeLists.txt index 4b6faa930..dce9023b7 100644 --- a/Sound/XRSound/src/CMakeLists.txt +++ b/Sound/XRSound/src/CMakeLists.txt @@ -23,13 +23,11 @@ set_target_properties(XRSound_dll PROPERTIES target_include_directories(XRSound_dll PUBLIC ${ORBITER_SOURCE_SDK_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/Utils -# ${IRRKLANG_DIR}/include ) target_link_libraries(XRSound_dll ${ORBITER_LIB} ${ORBITER_SDK_LIB} -# ${IRRKLANG_LIB} SDL3::SDL3-shared SDL3_mixer::SDL3_mixer-shared ) diff --git a/Sound/XRSound/src/DefaultSoundGroupPreSteps.cpp b/Sound/XRSound/src/DefaultSoundGroupPreSteps.cpp index b2be3ea31..9514474f2 100644 --- a/Sound/XRSound/src/DefaultSoundGroupPreSteps.cpp +++ b/Sound/XRSound/src/DefaultSoundGroupPreSteps.cpp @@ -606,7 +606,7 @@ void MusicDefaultSoundGroupPreStep::clbkPreStep(const double simt, const double if (!HasFocus()) return; - // To prevent intermittent music stuttering on Orbiter startup when Orbiter hangs for a full second, which starves the irrKLang engine, + // To prevent intermittent music stuttering on Orbiter startup, // don't play any music until two seconds after simulation start. if (simt < 2.0) { diff --git a/Sound/XRSound/src/ModuleXRSoundEngine.cpp b/Sound/XRSound/src/ModuleXRSoundEngine.cpp index e857d01ee..359c8b6ff 100644 --- a/Sound/XRSound/src/ModuleXRSoundEngine.cpp +++ b/Sound/XRSound/src/ModuleXRSoundEngine.cpp @@ -13,7 +13,7 @@ // Static method to create a new instance of an XRSoundEngine for a module. This is the ONLY place where new // XRSoundEngine instances for modules are constructed. // -// This also handles static one-time initialization of our singleton irrKlang engine. +// This also handles static one-time initialization of our singleton sound engine. ModuleXRSoundEngine *ModuleXRSoundEngine::CreateInstance(const char *pUniqueModuleName) { _ASSERTE(pUniqueModuleName); @@ -22,12 +22,12 @@ ModuleXRSoundEngine *ModuleXRSoundEngine::CreateInstance(const char *pUniqueModu if (!pUniqueModuleName || !*pUniqueModuleName) return nullptr; - // Must handle initializing the irrKlang engine here since clbkSimulationStart is too late: it needs to be done + // Must handle initializing the sound engine here since clbkSimulationStart is too late: it needs to be done // before the first call to LoadWav. - if (s_bIrrKlangEngineNeedsInitialization) + if (s_bSoundEngineNeedsInitialization) { - s_bIrrKlangEngineNeedsInitialization = false; - InitializeIrrKlangEngine(); + s_bSoundEngineNeedsInitialization = false; + InitializeSoundEngine(); } return new ModuleXRSoundEngine(pUniqueModuleName); @@ -60,7 +60,7 @@ void ModuleXRSoundEngine::FreeResources() static_cast(m_csModuleName)); s_globalConfig.WriteLog(msg); - // stop all of this module's sounds and free all irrKlang resources for them + // stop all of this module's sounds and free all sound resources for them StopAllWav(); } @@ -99,7 +99,7 @@ void ModuleXRSoundEngine::UpdateSoundState(WavContext &context) { if (!pISound->isFinished()) { - // update the irrKlang state for this sound + // update the state for this sound pISound->setVolume(context.volume); // Note: context.volume has already been adjusted for MasterVolume setting in config pISound->setIsLooped(context.bLoop); pISound->setIsPaused(context.bPaused); diff --git a/Sound/XRSound/src/VesselXRSoundEngine.cpp b/Sound/XRSound/src/VesselXRSoundEngine.cpp index 9b12d4171..fe31f7499 100644 --- a/Sound/XRSound/src/VesselXRSoundEngine.cpp +++ b/Sound/XRSound/src/VesselXRSoundEngine.cpp @@ -25,7 +25,7 @@ void VesselXRSoundEngine::FreeResources() static_cast(GetVesselName())); s_globalConfig.WriteLog(msg); - // stop all of this vessel's sounds and free all irrKlang resources for them + // stop all of this vessel's sounds and free all resources for them StopAllWav(); // free all our DefaultSoundPreStep objects @@ -35,19 +35,19 @@ void VesselXRSoundEngine::FreeResources() // Static method to create a new instance of an XRSoundEngine for a vessel. This is the ONLY place where new // XRSoundEngine instances for vessels are constructed. // -// This also handles static one-time initialization of our singleton irrKlang engine. +// This also handles static one-time initialization of our singleton sound engine. VesselXRSoundEngine *VesselXRSoundEngine::CreateInstance(const OBJHANDLE hVessel) { _ASSERTE(oapiIsVessel(hVessel)); if (!oapiIsVessel(hVessel)) return nullptr; - // Must handle initializing the irrKlang engine here since clbkSimulationStart is too late: it needs to be done + // Must handle initializing the sound engine here since clbkSimulationStart is too late: it needs to be done // before the first call to LoadWav. - if (s_bIrrKlangEngineNeedsInitialization) + if (s_bSoundEngineNeedsInitialization) { - s_bIrrKlangEngineNeedsInitialization = false; - InitializeIrrKlangEngine(); + s_bSoundEngineNeedsInitialization = false; + InitializeSoundEngine(); } VesselXRSoundEngine *pEngine = new VesselXRSoundEngine(hVessel); @@ -90,7 +90,7 @@ VesselXRSoundEngine::~VesselXRSoundEngine() // Returns true on success, false if XRSound.dll not present this default sound is disabled via config file (i.e., this default sound is not loaded). bool VesselXRSoundEngine::SetDefaultSoundEnabled(const XRSound::DefaultSoundID soundID, const bool bEnabled) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bSuccess = false; @@ -124,7 +124,7 @@ bool VesselXRSoundEngine::SetDefaultSoundEnabled(const XRSound::DefaultSoundID s // option: which default sound ID to check bool VesselXRSoundEngine::GetDefaultSoundEnabled(const XRSound::DefaultSoundID soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bEnabled = false; @@ -147,7 +147,7 @@ bool VesselXRSoundEngine::GetDefaultSoundEnabled(const XRSound::DefaultSoundID s // Returns true on success, false if XRSound.dll not present or defaultSoundID is not a valid group sound ID. bool VesselXRSoundEngine::SetDefaultSoundGroupFolder(const XRSound::DefaultSoundID defaultSoundID, const char *pSubfolderPath) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bSuccess = false; @@ -174,7 +174,7 @@ bool VesselXRSoundEngine::SetDefaultSoundGroupFolder(const XRSound::DefaultSound // groupdefaultSoundID: which default XRSound group to update (only DefaultSoundIDs that end in "Group" are valid for this call) const char *VesselXRSoundEngine::GetDefaultSoundGroupFolder(const XRSound::DefaultSoundID defaultSoundID) const { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return nullptr; const char *pSoundFolder = nullptr; @@ -195,7 +195,7 @@ const char *VesselXRSoundEngine::GetDefaultSoundGroupFolder(const XRSound::Defau // mjd simulation time afte the currently processed step into modified Julian Date format[days] void VesselXRSoundEngine::clbkPreStep(const double simt, const double simdt, const double mjd) { - if (IsKlangEngineInitialized()) + if (IsSoundEngineInitialized()) { // Update our map of all animation (door) IDs -> animation states. This data is used by our DefaultSoundPreStep // handlers to determine when a given door is changing states. In addition, this method logs animation states to @@ -280,7 +280,7 @@ void VesselXRSoundEngine::UpdateSoundState(WavContext &context) goto release_sound; } - // update the irrKlang state for this sound + // update the state for this sound pISound->setVolume(volume); pISound->setIsLooped(context.bLoop); pISound->setIsPaused(context.bPaused); diff --git a/Sound/XRSound/src/VesselXRSoundEngine.h b/Sound/XRSound/src/VesselXRSoundEngine.h index 9ff018db4..a6d574f74 100644 --- a/Sound/XRSound/src/VesselXRSoundEngine.h +++ b/Sound/XRSound/src/VesselXRSoundEngine.h @@ -122,7 +122,7 @@ class VesselXRSoundEngine : public XRSoundEngine } double GetCameraDistance(); - // invoked ~20 times per second to update this sound's position relative to Orbiter's camera as well as update sounds via irrKlang engine + // invoked ~20 times per second to update this sound's position relative to Orbiter's camera as well as update sounds via sound engine void clbkPreStep(const double simt, const double simdt, const double mjd); protected: diff --git a/Sound/XRSound/src/XRSoundDLL.cpp b/Sound/XRSound/src/XRSoundDLL.cpp index 313c25b02..6ba637efc 100644 --- a/Sound/XRSound/src/XRSoundDLL.cpp +++ b/Sound/XRSound/src/XRSoundDLL.cpp @@ -152,9 +152,6 @@ DLLCLBK void InitModule(HINSTANCE hDLL) // parse this immediately so we can read it from the start XRSoundDLL::ParseGlobalConfigFile(); - - // NOTE: the initial implementation was to call XRSoundEngine::InitializeIrrKlangEngine from here, but - // it hung indefinitely when calling createIrrKlangDevice, so I had to move that logic into XRSoundDLL::GetXRSoundEngineInstance. } // parse (or re-parse) our global XRSound.cfg file (this parse is vessel-independent) @@ -186,7 +183,7 @@ XRSoundDLL *XRSoundDLL::s_pInstance; // Constructor XRSoundDLL::XRSoundDLL(HINSTANCE hDLL) : - Module(hDLL), m_hDLL(hDLL), m_nextSoundEnginesRefreshSimt(0), m_absoluteSimTime(0), m_nextIrrKlangUpdateRealtime(0) + Module(hDLL), m_hDLL(hDLL), m_nextSoundEnginesRefreshSimt(0), m_absoluteSimTime(0) { } @@ -198,7 +195,7 @@ XRSoundDLL::~XRSoundDLL() // sanity checks to make sure things were already cleaned up as expected _ASSERTE(m_allVesselsMap.size() == 0); _ASSERTE(m_allModulesMap.size() == 0); - _ASSERTE(!XRSoundEngine::IsKlangEngineInitialized()); + _ASSERTE(!XRSoundEngine::IsSoundEngineInitialized()); #endif } @@ -209,7 +206,7 @@ void XRSoundDLL::clbkSimulationStart(RenderMode mode) // reparse our global (i.e., vessel-independent) config file in case the user dropped to the launchpad and restarted XRSoundDLL::ParseGlobalConfigFile(); - // NOTE: this is too late to invoke XRSoundEngine::InitializeIrrKlangEngine, since vessels need the engine to be available + // NOTE: this is too late to invoke XRSoundEngine::InitializeSoundEngine, since vessels need the engine to be available // before clbkSetClassCaps. // Instead, the first call to XRSoundDLL::GetXRSoundEngineInstance handles initializing the engine, since that // is the first call that must be made before any other XRSoundEngine calls can be made. @@ -239,7 +236,7 @@ void XRSoundDLL::clbkSimulationEnd() XRSoundEngine::DestroyInstance(it->second); m_allVesselsMap.clear(); - XRSoundEngine::DestroyIrrKlangEngine(); + XRSoundEngine::DestroySoundEngine(); } // Returns a list of all Orbiter vessel handles that exist during this frame @@ -398,16 +395,6 @@ void XRSoundDLL::clbkPreStep(double simtDoNotUse, double simdt, double mjd) } m_nextSoundEnginesRefreshSimt = simt + GetGlobalConfig().UpdateInterval; } - - // NOTE: we need to give irrKlang ~20 timeslices a second in *realtime*, not *sim time*, which can be slowed down to 1/10 realtime. - // give irrKlang a timeslice to update the state of the sound output - const double systemUptime = GetSystemUptime(); - if (systemUptime >= m_nextIrrKlangUpdateRealtime) - { - // DEV DEBUGGING ONLY: sprintf(oapiDebugString(), "Updating irrKlang engine at systemUptime %lf", systemUptime); - XRSoundEngine::UpdateIrrKlangEngine(); - m_nextIrrKlangUpdateRealtime = systemUptime + 0.05; // 20 updates per second in realtime - } } // Returns the number of seconds since the system booted (realtime); typically has 10-16 millisecond accuracy (16 ms = 1/60th second), diff --git a/Sound/XRSound/src/XRSoundDLL.h b/Sound/XRSound/src/XRSoundDLL.h index bfd66b4a4..a6ec98314 100644 --- a/Sound/XRSound/src/XRSoundDLL.h +++ b/Sound/XRSound/src/XRSoundDLL.h @@ -72,6 +72,5 @@ class XRSoundDLL : public oapi::Module protected: HINSTANCE m_hDLL; // our DLL's handle double m_nextSoundEnginesRefreshSimt; - double m_nextIrrKlangUpdateRealtime; double m_absoluteSimTime; // replaces simt and oapiGetSimTime(), both of which are unreliable! See note in XRSoundDLL::clbkPreStep. }; diff --git a/Sound/XRSound/src/XRSoundEngine.cpp b/Sound/XRSound/src/XRSoundEngine.cpp index 42f03add3..dd8a4a2fb 100644 --- a/Sound/XRSound/src/XRSoundEngine.cpp +++ b/Sound/XRSound/src/XRSoundEngine.cpp @@ -15,27 +15,24 @@ // static data and methods -ISoundEngine *XRSoundEngine::s_pKlangEngine = nullptr; +ISoundEngine *XRSoundEngine::s_SoundEngine = nullptr; XRSoundConfigFileParser XRSoundEngine::s_globalConfig; -bool XRSoundEngine::s_bIrrKlangEngineNeedsInitialization = true; -WavContext *XRSoundEngine::s_pMusicFolderWavContext = nullptr; // this global, vessel-independent context will exist until the irrKlang engine is terminated +bool XRSoundEngine::s_bSoundEngineNeedsInitialization = true; +WavContext *XRSoundEngine::s_pMusicFolderWavContext = nullptr; // this global, vessel-independent context will exist until the sound engine is terminated CString XRSoundEngine::s_csVersion; -// Perform one-time initialization of our irrKlang singleton. +// Perform one-time initialization of our ISoundEngine singleton. // Returns: true on success, false on error (which means no sounds will play). -bool XRSoundEngine::InitializeIrrKlangEngine() +bool XRSoundEngine::InitializeSoundEngine() { - _ASSERTE(!XRSoundEngine::IsKlangEngineInitialized()); + _ASSERTE(!XRSoundEngine::IsSoundEngineInitialized()); - if (!XRSoundEngine::IsKlangEngineInitialized()) + if (!XRSoundEngine::IsSoundEngineInitialized()) { - // Note: we do NOT want to use multi-threading here: that opens up possible timing gaps / race conditions between the time - // we query a given sound's state in our thread and when the OTHER thread updates that state. - // TODO: if and when we want to support 3D sounds, will need to add ESEO_USE_3D_BUFFERS flag below as well - s_pKlangEngine = new ISoundEngine(); + s_SoundEngine = new ISoundEngine(); char logMsg[256]; // can't use CString easily here b/c Orbiter's oapiWriteLog takes a char * instead of const char * for some bizarre reason. - if (s_pKlangEngine) + if (s_SoundEngine) sprintf_s(logMsg, "%s initialized using sound driver %s. XRSound UpdateInterval = %.03lf (%.1lf updates per second)", GetVersionStr(), XRSoundEngine::GetSoundDriverName(), s_globalConfig.UpdateInterval, (1.0 / s_globalConfig.UpdateInterval)); @@ -46,20 +43,20 @@ bool XRSoundEngine::InitializeIrrKlangEngine() s_globalConfig.WriteLog("----------------------------------------------------------------------------"); s_globalConfig.WriteLog(logMsg); - if (!XRSoundEngine::IsKlangEngineInitialized()) + if (!XRSoundEngine::IsSoundEngineInitialized()) return false; } else { - s_globalConfig.WriteLog("WARNING: XRSoundEngine::InitializeIrrKlangEngine() called, but engine was already initialized."); + s_globalConfig.WriteLog("WARNING: XRSoundEngine::InitializeSoundEngine() called, but engine was already initialized."); } - // irrKlang (was) initialized successfully! + // Sound engine (was) initialized successfully! return true; } -// Perform one-time static cleanup of our irrKlang singleton. -void XRSoundEngine::DestroyIrrKlangEngine() +// Perform one-time static cleanup of our ISoundEngine singleton. +void XRSoundEngine::DestroySoundEngine() { char logMsg[256]; // can't use CString easily here b/c Orbiter's oapiWriteLog takes a char * instead of const char * for some bizarre reason. sprintf_s(logMsg, "%s terminating.", GetVersionStr()); @@ -68,7 +65,7 @@ void XRSoundEngine::DestroyIrrKlangEngine() s_globalConfig.WriteLog(logMsg); // It is not an error to call this if the engine was never initialized, so we don't check for that here. - if (XRSoundEngine::IsKlangEngineInitialized()) + if (XRSoundEngine::IsSoundEngineInitialized()) { // need to stop and free our static MusicFolder sound if any vessel started it if (s_pMusicFolderWavContext) @@ -78,15 +75,15 @@ void XRSoundEngine::DestroyIrrKlangEngine() } // free and reset the engine - delete s_pKlangEngine; - s_pKlangEngine = nullptr; - s_bIrrKlangEngineNeedsInitialization = true; // need to reinitialize the engine on next LoadWav call + delete s_SoundEngine; + s_SoundEngine = nullptr; + s_bSoundEngineNeedsInitialization = true; // need to reinitialize the engine on next LoadWav call } } -bool XRSoundEngine::IsKlangEngineInitialized() +bool XRSoundEngine::IsSoundEngineInitialized() { - return (s_pKlangEngine != nullptr); + return (s_SoundEngine != nullptr); } // Returns the name of the sound driver, like 'ALSA' for the alsa device. @@ -96,8 +93,8 @@ bool XRSoundEngine::IsKlangEngineInitialized() const char *XRSoundEngine::GetSoundDriverName() { const char *pDriverName = nullptr; - if (IsKlangEngineInitialized()) - pDriverName = s_pKlangEngine->getDriverName(); + if (IsSoundEngineInitialized()) + pDriverName = s_SoundEngine->getDriverName(); return pDriverName; } @@ -144,7 +141,7 @@ float XRSoundEngine::GetVersion() const // Returns true on success, false if file not found or pSoundFileName is nullptr or empty bool XRSoundEngine::LoadWav(const int soundID, const char *pSoundFilename, const XRSound::PlaybackType playbackType) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; if (!pSoundFilename || !*pSoundFilename) @@ -202,7 +199,7 @@ bool XRSoundEngine::LoadWav(const int soundID, const char *pSoundFilename, const // Returns true on success (or vessel does not have focus), false if invalid sound ID bool XRSoundEngine::PlayWav(const int soundID, const bool bLoop, const float volume) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; // Adjust the volume per the MasterVolume: all sounds to be played come through here, so we only need to adjust it once in this place. @@ -246,7 +243,7 @@ bool XRSoundEngine::PlayWav(const int soundID, const bool bLoop, const float vol { // sound is not playing, so let's start immediately and track it via its pISound interface // NOTE: we start this paused so that we can set the proper volume level before starting it via UpdateSoundState - pISound = pContext->pISound = s_pKlangEngine->play2D(pContext->csSoundFilename, bLoop, true); + pISound = pContext->pISound = s_SoundEngine->play2D(pContext->csSoundFilename, bLoop, true); if (pISound == nullptr) // this means the sound could not be played; e.g., corrupt file, etc. { VERBOSE_LOG(this, "XRSoundEngine::PlayWav ERROR: could not play sound %s", static_cast(pContext->ToStr())); @@ -275,7 +272,7 @@ bool XRSoundEngine::PlayWav(const int soundID, const bool bLoop, const float vol // in this method chain. void XRSoundEngine::StopAllWav() { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return; for (auto it = m_allWavsMap.begin(); it != m_allWavsMap.end(); it++) @@ -285,7 +282,7 @@ void XRSoundEngine::StopAllWav() // Pause or resume all wav sounds currently playing for this engine. void XRSoundEngine::SetAllWavPaused(const bool bPaused) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return; for (auto it = m_allWavsMap.begin(); it != m_allWavsMap.end(); it++) @@ -302,13 +299,13 @@ void XRSoundEngine::SetAllWavPaused(const bool bPaused) } } -// Stop playing the wav file with the specified ID and free its irrKlang resources (ISound interface). +// Stop playing the wav file with the specified ID and free its audio resources (ISound interface). // soundID: unique sound ID originally passed to LoadWav // // Returns true on success, false if invalid sound ID bool XRSoundEngine::StopWav(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool retVal = false; @@ -339,7 +336,7 @@ bool XRSoundEngine::StopWavImpl(WavContext *pContext, XRSoundEngine *pEngine) VERBOSE_LOG(pEngine, "XRSoundEngine::StopWavImpl: stopping sound %s", static_cast(pContext->ToStr())); pISound->stop(); } - delete pISound; // free irrKlang resources for this sound + delete pISound; // free resources for this sound pContext->ResetPlaybackFields(); // reset all playback fields to their initial state, indicating the context is not in use } return bStopped; @@ -348,7 +345,7 @@ bool XRSoundEngine::StopWavImpl(WavContext *pContext, XRSoundEngine *pEngine) // Returns false if the specified sound is not playing bool XRSoundEngine::IsWavPlaying(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bIsPlaying = false; @@ -367,7 +364,7 @@ bool XRSoundEngine::IsWavPlaying(const int soundID) // at any frame. const char *XRSoundEngine::GetWavFilename(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return nullptr; const char *pFilename = nullptr; @@ -384,7 +381,7 @@ const char *XRSoundEngine::GetWavFilename(const int soundID) // Returns true on success, false if invalid sound ID or XRSound.dll not present. bool XRSoundEngine::SetPaused(const int soundID, const bool bPause) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bSuccess = false; @@ -412,7 +409,7 @@ bool XRSoundEngine::SetPaused(const int soundID, const bool bPause) // Returns true if sound is paused, or false if not paused, invalid sound ID, or XRSound.dll not present. bool XRSoundEngine::IsPaused(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool bIsPaused = false; @@ -539,16 +536,6 @@ vector XRSoundEngine::GetValidSoundFileExtensions() return m_pConfig->SupportedSoundFileTypesAsVector(); } -// Give irrKlang a timeslice to update the state of the sound output. -// Must be invoked several times per second, but a minimum of 3. -void XRSoundEngine::UpdateIrrKlangEngine() -{ - if (!XRSoundEngine::IsKlangEngineInitialized()) - return; // edge case: there are no sound-enabled vessels in Orbiter yet, so nothing to do - -// s_pKlangEngine->update(); -} - // Reset any static data for a simulation restart (e.g., one-shot timers, etc.) void XRSoundEngine::ResetStaticSimulationData() { diff --git a/Sound/XRSound/src/XRSoundEngine.h b/Sound/XRSound/src/XRSoundEngine.h index d1461c9cd..cccfce342 100644 --- a/Sound/XRSound/src/XRSoundEngine.h +++ b/Sound/XRSound/src/XRSoundEngine.h @@ -24,16 +24,12 @@ class ModuleXRSoundEngine; // Note: ideally I would like to have this invoke '#error foo' to generate an elegant message, but there's no way to declare that #define oapiGetSimTime ERROR! "Do not invoke oapiGetSimTime: see comment block in XRSoundDLL::clbkPreStep for details" -#ifdef XRSOUND_DLL_BUILD -//#include -//using namespace irrklang; -#include "XRSoundConfigFileParser.h" class ISound; class ISoundEngine; +#ifdef XRSOUND_DLL_BUILD +#include "XRSoundConfigFileParser.h" #else // we're compiling XRSoundLib, so all we need is a forward reference (and we don't want to include the full class definition on the .lib side!) -class ISoundEngine; -class ISound; class XRSoundConfigFileParser; #endif @@ -190,9 +186,8 @@ class XRSoundEngine : public XRSoundEngine30 // Methods only invoked by XRSoundDLL (our Orbiter module class). Because these are non-virtual, it is impossible for XRSoundLib to invoke them // because it cannot link with them when the Orbiter vessel using XRSoundLib tries to link. - static void DestroyIrrKlangEngine(); // performs one-time destruction of our irrKlang engine - static bool IsKlangEngineInitialized(); - static void UpdateIrrKlangEngine(); // invoked several times per second so the engine can update its state + static void DestroySoundEngine(); // performs one-time destruction of our sound engine + static bool IsSoundEngineInitialized(); static const char *GetSoundDriverName(); static const char *GetVersionStr(); static void ResetStaticSimulationData(); @@ -207,7 +202,7 @@ class XRSoundEngine : public XRSoundEngine30 void SetAllWavPaused(const bool bPaused); XRSoundConfigFileParser &GetConfig() { _ASSERTE(m_pConfig); return *m_pConfig; } - vector GetValidSoundFileExtensions(); // e.g., ".flac", ".wav", ".mp3", etc. + vector GetValidSoundFileExtensions(); // e.g., ".ogg", ".wav", ".mp3", etc. const char *GetWavFilename(const int soundID); // returns Orbiter's camera's global coordinates @@ -224,7 +219,7 @@ class XRSoundEngine : public XRSoundEngine30 static XRSoundConfigFileParser s_globalConfig; // this is parsed at DLL initialization time so we can write to the log file and read configuration data early-on from our static methods protected: - static bool InitializeIrrKlangEngine(); // performs one-time static initialization of our irrKlang singleton + static bool InitializeSoundEngine(); // performs one-time static initialization of our sound singleton //===================================================================================== // Protected abstract methods -- these are not exposed via the XRSoundEngine interface. @@ -237,8 +232,8 @@ class XRSoundEngine : public XRSoundEngine30 static bool StopWavImpl(WavContext *pContext, XRSoundEngine *pEngine); // data - static ISoundEngine *s_pKlangEngine; // initialized by InitializeIrrKlangEngine - static bool s_bIrrKlangEngineNeedsInitialization; // used to handle one-time startup items + static ISoundEngine *s_SoundEngine; // initialized by InitializeSoundEngine + static bool s_bSoundEngineNeedsInitialization; // used to handle one-time startup items XRSoundConfigFileParser *m_pConfig; // this is per-engine instance instead of static so that we can per-vessel or per-module configuration overrides if we want to diff --git a/Sound/XRSound/src/XRSoundEngine30.cpp b/Sound/XRSound/src/XRSoundEngine30.cpp index 6b47e251c..50b4dbc72 100644 --- a/Sound/XRSound/src/XRSoundEngine30.cpp +++ b/Sound/XRSound/src/XRSoundEngine30.cpp @@ -12,7 +12,7 @@ bool XRSoundEngine::SetPan(const int soundID, const float pan) { - if (!IsKlangEngineInitialized() || (pan < -1.0) || (pan > 1.0)) + if (!IsSoundEngineInitialized() || (pan < -1.0) || (pan > 1.0)) return false; bool retVal = false; @@ -22,7 +22,7 @@ bool XRSoundEngine::SetPan(const int soundID, const float pan) ISound *pISound = pContext->pISound; if (pISound) // was sound ever started via PlayWav? { - // irrKlang has pan direction inverted with Orbiter's X coordinate system, so flip it + // SDL3_mixer has pan direction inverted with Orbiter's X coordinate system, so flip it pISound->setPan(-pan); retVal = true; } @@ -32,7 +32,7 @@ bool XRSoundEngine::SetPan(const int soundID, const float pan) float XRSoundEngine::GetPan(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return -100; float retVal = -100; @@ -42,7 +42,7 @@ float XRSoundEngine::GetPan(const int soundID) ISound *pISound = pContext->pISound; if (pISound) // was sound ever started via PlayWav? { - // irrKlang has pan direction inverted with Orbiter's X coordinate system, so flip it + // SDL3_mixer has pan direction inverted with Orbiter's X coordinate system, so flip it retVal = -(pISound->getPan()); } } @@ -51,7 +51,7 @@ float XRSoundEngine::GetPan(const int soundID) bool XRSoundEngine::SetPlaybackSpeed(const int soundID, const float speed) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool retVal = false; @@ -67,7 +67,7 @@ bool XRSoundEngine::SetPlaybackSpeed(const int soundID, const float speed) float XRSoundEngine::GetPlaybackSpeed(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return 0; float retVal = 0; @@ -84,7 +84,7 @@ float XRSoundEngine::GetPlaybackSpeed(const int soundID) bool XRSoundEngine::SetPlayPosition(const int soundID, const unsigned int positionMillis) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return false; bool retVal = false; @@ -100,7 +100,7 @@ bool XRSoundEngine::SetPlayPosition(const int soundID, const unsigned int positi int XRSoundEngine::GetPlayPosition(const int soundID) { - if (!IsKlangEngineInitialized()) + if (!IsSoundEngineInitialized()) return -1; int retVal = -1;