diff --git a/Source/effects.cpp b/Source/effects.cpp index f3eb56beb..d8f3c34ee 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -1281,6 +1281,7 @@ void PlaySfxLoc(_sfx_id psfx, int x, int y, bool randomizeByCategory) void sound_stop() { music_stop(); + ClearDuplicateSounds(); for (auto &sfx : sgSFX) { if (sfx.pSnd != nullptr) { sfx.pSnd->DSB.Stop(); @@ -1295,6 +1296,7 @@ void sound_update() } stream_update(); + CleanupFinishedDuplicateSounds(); } void effects_cleanup_sfx() diff --git a/Source/sound.cpp b/Source/sound.cpp index cb2a495a8..9d8b1af7e 100644 --- a/Source/sound.cpp +++ b/Source/sound.cpp @@ -3,6 +3,8 @@ * * Implementation of functions setting up the audio pipeline. */ +#include "sound.h" + #include #include @@ -18,6 +20,7 @@ #include "storm/storm.h" #include "utils/log.hpp" #include "utils/stdcompat/optional.hpp" +#include "utils/stdcompat/shared_ptr_array.hpp" #include "utils/stubs.h" namespace devilution { @@ -68,6 +71,16 @@ void CleanupMusic() #endif } +std::vector> duplicateSounds; +SoundSample *DuplicateSound(const SoundSample &sound) { + auto duplicate = std::make_unique(); + if (duplicate->DuplicateFrom(sound) != 0) + return nullptr; + auto *result = duplicate.get(); + duplicateSounds.push_back(std::move(duplicate)); + return result; +} + } // namespace /* data */ @@ -109,6 +122,19 @@ static int CapVolume(int volume) return volume - volume % 100; } +void ClearDuplicateSounds() { + duplicateSounds.clear(); +} + +void CleanupFinishedDuplicateSounds() +{ + duplicateSounds.erase( + std::remove_if(duplicateSounds.begin(), duplicateSounds.end(), [](const auto &sound) { + return !sound->IsPlaying(); + }), + duplicateSounds.end()); +} + void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) { DWORD tc; @@ -122,35 +148,39 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) return; } + SoundSample *sound = &pSnd->DSB; + if (pSnd->DSB.IsPlaying()) { + sound = DuplicateSound(pSnd->DSB); + if (sound == nullptr) + return; + } + lVolume = CapVolume(lVolume + sgOptions.Audio.nSoundVolume); - pSnd->DSB.Play(lVolume, lPan); + sound->Play(lVolume, lPan); pSnd->start_tc = tc; } std::unique_ptr sound_file_load(const char *path, bool stream) { - HANDLE file; int error = 0; - if (!SFileOpenFile(path, &file)) { - ErrDlg("SFileOpenFile failed", path, __FILE__, __LINE__); - } auto snd = std::make_unique(); - snd->sound_path = std::string(path); snd->start_tc = SDL_GetTicks() - 80 - 1; if (stream) { - snd->file_handle = file; - error = snd->DSB.SetChunkStream(file); + error = snd->DSB.SetChunkStream(path); if (error != 0) { - SFileCloseFile(file); ErrSdl(); } } else { + HANDLE file; + if (!SFileOpenFile(path, &file)) { + ErrDlg("SFileOpenFile failed", path, __FILE__, __LINE__); + } DWORD dwBytes = SFileGetFileSize(file, nullptr); - auto wave_file = std::make_unique(dwBytes); + auto wave_file = MakeArraySharedPtr(dwBytes); SFileReadFile(file, wave_file.get(), dwBytes, nullptr, nullptr); - error = snd->DSB.SetChunk(std::move(wave_file), dwBytes); + error = snd->DSB.SetChunk(wave_file, dwBytes); SFileCloseFile(file); } if (error != 0) { @@ -165,8 +195,6 @@ TSnd::~TSnd() { DSB.Stop(); DSB.Release(); - if (file_handle != nullptr) - SFileCloseFile(file_handle); } #endif diff --git a/Source/sound.h b/Source/sound.h index dece3b4af..6d3b53d48 100644 --- a/Source/sound.h +++ b/Source/sound.h @@ -34,9 +34,6 @@ enum _music_id : uint8_t { struct TSnd { #ifndef NOSOUND - std::string sound_path; - /** Used for streamed audio */ - HANDLE file_handle = nullptr; SoundSample DSB; Uint32 start_tc; @@ -50,8 +47,8 @@ struct TSnd { }; extern bool gbSndInited; - -void snd_update(bool bStopAll); +void ClearDuplicateSounds(); +void CleanupFinishedDuplicateSounds(); void snd_stop_snd(TSnd *pSnd); void snd_play_snd(TSnd *pSnd, int lVolume, int lPan); std::unique_ptr sound_file_load(const char *path, bool stream = false); diff --git a/Source/sound_stubs.cpp b/Source/sound_stubs.cpp index a63cb199b..ae1dd99b6 100644 --- a/Source/sound_stubs.cpp +++ b/Source/sound_stubs.cpp @@ -10,9 +10,9 @@ bool gbSoundOn; // Disable clang-format here because our config says: // AllowShortFunctionsOnASingleLine: None // clang-format off -void snd_update(bool bStopAll) { } +void ClearDuplicateSounds() { } +void CleanupFinishedDuplicateSounds() { } void snd_stop_snd(TSnd *pSnd) { } -bool snd_playing(TSnd *pSnd) { return false; } void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) { } std::unique_ptr sound_file_load(const char *path, bool stream) { return nullptr; } void sound_file_cleanup(TSnd *sound_file) { } diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index bf2669488..e539fedb4 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -22,6 +22,7 @@ #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_compat.h" +#include "utils/stdcompat/optional.hpp" namespace devilution { namespace { diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index 12260f6bd..c74a5150b 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -14,17 +14,26 @@ #include "options.h" #include "storm/storm_sdl_rw.h" -#include "utils/stubs.h" +#include "storm/storm.h" #include "utils/log.hpp" +#include "utils/stubs.h" namespace devilution { -///// SoundSample ///// +SoundSample::~SoundSample() +{ + if (file_handle_ != nullptr) + SFileCloseFile(file_handle_); +} void SoundSample::Release() { - stream_ = std::nullopt; + stream_ = nullptr; file_data_ = nullptr; + file_data_size_ = 0; + if (file_handle_ != nullptr) + SFileCloseFile(file_handle_); + file_handle_ = nullptr; }; /** @@ -51,7 +60,6 @@ void SoundSample::Play(int lVolume, int lPan, int channel) : copysign(1.F - std::pow(Base, static_cast(-std::fabs(lPan) / Scale)), static_cast(lPan))); - stream_->rewind(); if (!stream_->play()) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); return; @@ -67,30 +75,39 @@ void SoundSample::Stop() stream_->stop(); }; -int SoundSample::SetChunkStream(HANDLE stormHandle) +int SoundSample::SetChunkStream(std::string filePath) { - stream_.emplace(SFileRw_FromStormHandle(stormHandle), std::make_unique(), + file_path_ = std::move(filePath); + if (!SFileOpenFile(file_path_.c_str(), &file_handle_)) { + LogError(LogCategory::Audio, "SFileOpenFile failed (from SoundSample::SetChunkStream): {}", SErrGetLastError()); + return -1; + } + + stream_ = std::make_unique(SFileRw_FromStormHandle(file_handle_), std::make_unique(), std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/true); if (!stream_->open()) { - stream_ = std::nullopt; + stream_ = nullptr; + SFileCloseFile(file_handle_); + file_handle_ = nullptr; LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream): {}", SDL_GetError()); return -1; } return 0; } -int SoundSample::SetChunk(std::unique_ptr fileData, size_t dwBytes) +int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwBytes) { - file_data_ = std::move(fileData); + file_data_ = fileData; + file_data_size_ = dwBytes; SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), dwBytes); if (buf == nullptr) { return -1; } - stream_.emplace(buf, std::make_unique(), + stream_ = std::make_unique(buf, std::make_unique(), std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/true); if (!stream_->open()) { - stream_ = std::nullopt; + stream_ = nullptr; file_data_ = nullptr; LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunk): {}", SDL_GetError()); return -1; @@ -102,7 +119,7 @@ int SoundSample::SetChunk(std::unique_ptr fileData, size_t dwByt /** * @return Audio duration in ms */ -int SoundSample::GetLength() +int SoundSample::GetLength() const { if (!stream_) return 0; diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index 255ada5d9..e60e4604d 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -1,22 +1,28 @@ #pragma once +#include #include #include #include #include "miniwin/miniwin.h" -#include "utils/stdcompat/optional.hpp" +#include "utils/stdcompat/shared_ptr_array.hpp" namespace devilution { class SoundSample final { public: + SoundSample() = default; + SoundSample(SoundSample &&) noexcept = default; + SoundSample &operator=(SoundSample &&) noexcept = default; + ~SoundSample(); + void Release(); bool IsPlaying(); void Play(int lVolume, int lPan, int channel = -1); void Stop(); - int SetChunkStream(HANDLE stormHandle); + int SetChunkStream(std::string filePath); /** * @brief Sets the sample's WAV, FLAC, or Ogg/Vorbis data. @@ -24,13 +30,32 @@ public: * @param dwBytes Length of buffer * @return 0 on success, -1 otherwise */ - int SetChunk(std::unique_ptr file_data, size_t dwBytes); + int SetChunk(ArraySharedPtr file_data, std::size_t dwBytes); + + [[nodiscard]] bool IsStreaming() const + { + return file_data_ == nullptr; + } + + int DuplicateFrom(const SoundSample &other) + { + if (other.IsStreaming()) + return SetChunkStream(other.file_path_); + return SetChunk(other.file_data_, other.file_data_size_); + } - int GetLength(); + int GetLength() const; private: - std::unique_ptr file_data_; - std::optional stream_; + // Non-streaming audio fields: + ArraySharedPtr file_data_; + std::size_t file_data_size_; + + // Streaming audio fields: + HANDLE file_handle_ = nullptr; + std::string file_path_; + + std::unique_ptr stream_; }; } // namespace devilution diff --git a/Source/utils/stdcompat/shared_ptr_array.hpp b/Source/utils/stdcompat/shared_ptr_array.hpp new file mode 100644 index 000000000..709aecd67 --- /dev/null +++ b/Source/utils/stdcompat/shared_ptr_array.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace devilution { + +// Apple Clang 12 has a buggy implementation that fails to compile `std::shared_ptr(new T[size])`. +#if __cplusplus >= 201611L && (!defined(__clang_major__) || __clang_major__ >= 13) +template +using ArraySharedPtr = std::shared_ptr; + +template +ArraySharedPtr MakeArraySharedPtr(std::size_t size) +{ + return ArraySharedPtr(new T[size]); +} +#else +template +using ArraySharedPtr = std::shared_ptr; + +template +ArraySharedPtr MakeArraySharedPtr(std::size_t size) +{ + return ArraySharedPtr { new T[size], std::default_delete() }; +} +#endif + +} // namespace devilution