From 7f695c2835286d731b411cb3e65e25ca6179b1ff Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 14 Apr 2022 01:39:03 +0100 Subject: [PATCH] Use SoundSample for music Unifies audio handling between sound effects and music. --- Source/effects.cpp | 8 +- Source/sound.cpp | 188 ++++++++++++++--------------------- Source/sound.h | 10 -- Source/sound_defs.hpp | 11 ++ Source/utils/soundsample.cpp | 53 +++++----- Source/utils/soundsample.h | 56 +++++++++-- 6 files changed, 160 insertions(+), 166 deletions(-) create mode 100644 Source/sound_defs.hpp diff --git a/Source/effects.cpp b/Source/effects.cpp index 13758d622..978d218c0 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -9,6 +9,7 @@ #include "init.h" #include "player.h" #include "sound.h" +#include "sound_defs.hpp" #include "utils/stdcompat/algorithm.hpp" namespace devilution { @@ -1083,7 +1084,8 @@ void StreamPlay(TSFX *pSFX, int lVolume, int lPan) lVolume = VOLUME_MAX; if (pSFX->pSnd == nullptr) pSFX->pSnd = sound_file_load(pSFX->pszName, AllowStreaming); - pSFX->pSnd->DSB.Play(lVolume, sound_get_or_set_sound_volume(1), lPan); + if (pSFX->pSnd->DSB.IsLoaded()) + pSFX->pSnd->DSB.PlayWithVolumeAndPan(lVolume, sound_get_or_set_sound_volume(1), lPan); sgpStreamSFX = pSFX; } } @@ -1122,7 +1124,7 @@ void PlaySfxPriv(TSFX *pSFX, bool loc, Point position) if (pSFX->pSnd == nullptr) pSFX->pSnd = sound_file_load(pSFX->pszName); - if (pSFX->pSnd != nullptr) + if (pSFX->pSnd != nullptr && pSFX->pSnd->DSB.IsLoaded()) snd_play_snd(pSFX->pSnd.get(), lVolume, lPan); } @@ -1287,7 +1289,7 @@ void sound_stop() return; ClearDuplicateSounds(); for (auto &sfx : sgSFX) { - if (sfx.pSnd != nullptr) { + if (sfx.pSnd != nullptr && sfx.pSnd->DSB.IsLoaded()) { sfx.pSnd->DSB.Stop(); } } diff --git a/Source/sound.cpp b/Source/sound.cpp index efb552b59..06994a5e5 100644 --- a/Source/sound.cpp +++ b/Source/sound.cpp @@ -10,12 +10,7 @@ #include #include -#include -#include -#include -#include #include -#include #include "engine/assets.hpp" #include "init.h" @@ -40,18 +35,7 @@ bool gbSoundOn = true; namespace { -std::optional music; - -#ifdef DISABLE_STREAMING_MUSIC -std::unique_ptr musicBuffer; -#endif - -std::unique_ptr CreateDecoder(bool isMp3) -{ - if (isMp3) - return std::make_unique(); - return std::make_unique(); -} +SoundSample music; std::string GetMp3Path(const char *path) { @@ -61,27 +45,50 @@ std::string GetMp3Path(const char *path) return mp3Path; } -void LoadMusic(SDL_RWops *handle, bool isMp3) +bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample &result) { -#ifdef DISABLE_STREAMING_MUSIC - size_t bytestoread = SDL_RWsize(handle); - musicBuffer.reset(new char[bytestoread]); - SDL_RWread(handle, musicBuffer.get(), bytestoread, 1); - SDL_RWclose(handle); - - handle = SDL_RWFromConstMem(musicBuffer.get(), bytestoread); +#ifndef STREAM_ALL_AUDIO + if (stream) { #endif - music.emplace(handle, CreateDecoder(isMp3), - std::make_unique(*sgOptions.Audio.resamplingQuality), /*closeRw=*/true); -} - -void CleanupMusic() -{ - music = std::nullopt; - sgnMusicTrack = NUM_MUSIC; -#ifdef DISABLE_STREAMING_MUSIC - musicBuffer = nullptr; + if (result.SetChunkStream(GetMp3Path(path), /*isMp3=*/true, /*logErrors=*/false) != 0) { + SDL_ClearError(); + if (result.SetChunkStream(path, /*isMp3=*/false, /*logErrors=*/true) != 0) { + if (errorDialog) + ErrSdl(); + return false; + } + } +#ifndef STREAM_ALL_AUDIO + } else { + bool isMp3 = true; + SDL_RWops *file = OpenAsset(GetMp3Path(path).c_str()); + if (file == nullptr) { + SDL_ClearError(); + isMp3 = false; + file = OpenAsset(path); + if (file == nullptr) { + if (errorDialog) + ErrDlg("OpenAsset failed", path, __FILE__, __LINE__); + return false; + } + } + size_t dwBytes = SDL_RWsize(file); + auto waveFile = MakeArraySharedPtr(dwBytes); + if (SDL_RWread(file, waveFile.get(), dwBytes, 1) == 0) { + if (errorDialog) + ErrDlg("Failed to read file", fmt::format("{}: {}", path, SDL_GetError()), __FILE__, __LINE__); + return false; + } + int error = result.SetChunk(waveFile, dwBytes, isMp3); + SDL_RWclose(file); + if (error != 0) { + if (errorDialog) + ErrSdl(); + return false; + } + } #endif + return true; } std::list> duplicateSounds; @@ -161,7 +168,7 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) return; } - sound->Play(lVolume, *sgOptions.Audio.soundVolume, lPan); + sound->PlayWithVolumeAndPan(lVolume, *sgOptions.Audio.soundVolume, lPan); pSnd->start_tc = tc; } @@ -169,47 +176,16 @@ std::unique_ptr sound_file_load(const char *path, bool stream) { auto snd = std::make_unique(); snd->start_tc = SDL_GetTicks() - 80 - 1; - -#ifndef STREAM_ALL_AUDIO - if (stream) { +#ifndef NOSOUND + LoadAudioFile(path, stream, /*errorDialog=*/true, snd->DSB); #endif - if (snd->DSB.SetChunkStream(GetMp3Path(path), /*isMp3=*/true, /*logErrors=*/false) != 0) { - SDL_ClearError(); - if (snd->DSB.SetChunkStream(path, /*isMp3=*/false, /*logErrors=*/true) != 0) { - ErrSdl(); - } - } -#ifndef STREAM_ALL_AUDIO - } else { - bool isMp3 = true; - SDL_RWops *file = OpenAsset(GetMp3Path(path).c_str()); - if (file == nullptr) { - SDL_ClearError(); - isMp3 = false; - file = OpenAsset(path); - if (file == nullptr) { - ErrDlg("OpenAsset failed", path, __FILE__, __LINE__); - } - } - size_t dwBytes = SDL_RWsize(file); - auto waveFile = MakeArraySharedPtr(dwBytes); - if (SDL_RWread(file, waveFile.get(), dwBytes, 1) == 0) { - ErrDlg("Failed to read file", fmt::format("{}: {}", path, SDL_GetError()), __FILE__, __LINE__); - } - int error = snd->DSB.SetChunk(waveFile, dwBytes, isMp3); - SDL_RWclose(file); - if (error != 0) { - ErrSdl(); - } - } -#endif - return snd; } TSnd::~TSnd() { - DSB.Stop(); + if (DSB.IsLoaded()) + DSB.Stop(); DSB.Release(); } @@ -248,8 +224,8 @@ void snd_deinit() void music_stop() { - if (music) - CleanupMusic(); + music.Release(); + sgnMusicTrack = NUM_MUSIC; } void music_start(uint8_t nTrack) @@ -258,45 +234,33 @@ void music_start(uint8_t nTrack) assert(nTrack < NUM_MUSIC); music_stop(); - if (gbMusicOn) { - if (spawn_mpq) - trackPath = SpawnMusicTracks[nTrack]; - else - trackPath = MusicTracks[nTrack]; + if (!gbMusicOn) + return; + if (spawn_mpq) + trackPath = SpawnMusicTracks[nTrack]; + else + trackPath = MusicTracks[nTrack]; #ifdef DISABLE_STREAMING_MUSIC - const bool threadsafe = false; + const bool stream = false; #else - const bool threadsafe = true; + const bool stream = true; #endif - bool isMp3 = true; - SDL_RWops *handle = OpenAsset(GetMp3Path(trackPath).c_str()); - if (handle == nullptr) { - SDL_ClearError(); - handle = OpenAsset(trackPath, threadsafe); - isMp3 = false; - } - - if (handle != nullptr) { - LoadMusic(handle, isMp3); - if (!music->open()) { - LogError(LogCategory::Audio, "Aulib::Stream::open (from music_start): {}", SDL_GetError()); - CleanupMusic(); - return; - } - - music->setVolume(VolumeLogToLinear(*sgOptions.Audio.musicVolume, VOLUME_MIN, VOLUME_MAX)); - if (!diablo_is_focused()) - music_mute(); - if (!music->play(/*iterations=*/0)) { - LogError(LogCategory::Audio, "Aulib::Stream::play (from music_start): {}", SDL_GetError()); - CleanupMusic(); - return; - } + if (!LoadAudioFile(trackPath, stream, /*errorDialog=*/false, music)) { + music_stop(); + return; + } - sgnMusicTrack = (_music_id)nTrack; - } + music.SetVolume(*sgOptions.Audio.musicVolume, VOLUME_MIN, VOLUME_MAX); + if (!diablo_is_focused()) + music_mute(); + if (!music.Play(/*numIterations=*/0)) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from music_start): {}", SDL_GetError()); + music_stop(); + return; } + + sgnMusicTrack = (_music_id)nTrack; } void sound_disable_music(bool disable) @@ -315,8 +279,8 @@ int sound_get_or_set_music_volume(int volume) sgOptions.Audio.musicVolume.SetValue(volume); - if (music) - music->setVolume(VolumeLogToLinear(*sgOptions.Audio.musicVolume, VOLUME_MIN, VOLUME_MAX)); + if (music.IsLoaded()) + music.SetVolume(*sgOptions.Audio.musicVolume, VOLUME_MIN, VOLUME_MAX); return *sgOptions.Audio.musicVolume; } @@ -333,14 +297,14 @@ int sound_get_or_set_sound_volume(int volume) void music_mute() { - if (music) - music->mute(); + if (music.IsLoaded()) + music.Mute(); } void music_unmute() { - if (music) - music->unmute(); + if (music.IsLoaded()) + music.Unmute(); } } // namespace devilution diff --git a/Source/sound.h b/Source/sound.h index 901818e90..22bdf3841 100644 --- a/Source/sound.h +++ b/Source/sound.h @@ -17,16 +17,6 @@ namespace devilution { -#define VOLUME_MIN -1600 -#define VOLUME_MAX 0 -#define VOLUME_STEPS 64 - -#define ATTENUATION_MIN -6400 -#define ATTENUATION_MAX 0 - -#define PAN_MIN -6400 -#define PAN_MAX 6400 - enum _music_id : uint8_t { TMUSIC_TOWN, TMUSIC_L1, diff --git a/Source/sound_defs.hpp b/Source/sound_defs.hpp new file mode 100644 index 000000000..9046046c8 --- /dev/null +++ b/Source/sound_defs.hpp @@ -0,0 +1,11 @@ +#pragma once + +#define VOLUME_MIN -1600 +#define VOLUME_MAX 0 +#define VOLUME_STEPS 64 + +#define ATTENUATION_MIN -6400 +#define ATTENUATION_MAX 0 + +#define PAN_MIN -6400 +#define PAN_MAX 6400 diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index 6b9c721e6..2fbde64c2 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -70,14 +70,21 @@ std::unique_ptr CreateStream(SDL_RWops *handle, bool isMp3) std::make_unique(*sgOptions.Audio.resamplingQuality), /*closeRw=*/true); } -} // namespace - +/** + * @brief Converts log volume passed in into linear volume. + * @param logVolume Logarithmic volume in the range [logMin..logMax] + * @param logMin Volume range minimum (usually ATTENUATION_MIN for game sounds and VOLUME_MIN for volume sliders) + * @param logMax Volume range maximum (usually 0) + * @return Linear volume in the range [0..1] + */ float VolumeLogToLinear(int logVolume, int logMin, int logMax) { const auto logScaled = math::Remap(static_cast(logMin), static_cast(logMax), MillibelMin, MillibelMax, static_cast(logVolume)); return std::pow(LogBase, logScaled / VolumeScale); // linVolume } +} // namespace + ///// SoundSample ///// void SoundSample::Release() @@ -97,34 +104,13 @@ bool SoundSample::IsPlaying() return stream_ && stream_->isPlaying(); } -/** - * @brief Start playing the sound - */ -void SoundSample::Play(int logSoundVolume, int logUserVolume, int logPan) +bool SoundSample::Play(int numIterations) { - if (!stream_) - return; - - const int combinedLogVolume = logSoundVolume + logUserVolume * (ATTENUATION_MIN / VOLUME_MIN); - const float linearVolume = VolumeLogToLinear(combinedLogVolume, ATTENUATION_MIN, 0); - stream_->setVolume(linearVolume); - - const float linearPan = PanLogToLinear(logPan); - stream_->setStereoPosition(linearPan); - - if (!stream_->play()) { + if (!stream_->play(numIterations)) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); - return; + return false; } -} - -/** - * @brief Stop playing the sound - */ -void SoundSample::Stop() -{ - if (stream_) - stream_->stop(); + return true; } int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) @@ -170,9 +156,16 @@ int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwB } #endif -/** - * @return Audio duration in ms - */ +void SoundSample::SetVolume(int logVolume, int logMin, int logMax) +{ + stream_->setVolume(VolumeLogToLinear(logVolume, logMin, logMax)); +} + +void SoundSample::SetStereoPosition(int logPan) +{ + stream_->setStereoPosition(PanLogToLinear(logPan)); +} + int SoundSample::GetLength() const { if (!stream_) diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index 62efda66d..f84a94ec8 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -6,29 +6,24 @@ #include +#include "sound_defs.hpp" #include "utils/stdcompat/shared_ptr_array.hpp" namespace devilution { -/** - * @brief Converts log volume passed in into linear volume. - * @param logVolume Logarithmic volume in the range [logMin..logMax] - * @param logMin Volume range minimum (usually ATTENUATION_MIN for game sounds and VOLUME_MIN for volume sliders) - * @param logMax Volume range maximum (usually 0) - * @return Linear volume in the range [0..1] - */ -float VolumeLogToLinear(int logVolume, int logMin, int logMax); - class SoundSample final { public: SoundSample() = default; SoundSample(SoundSample &&) noexcept = default; SoundSample &operator=(SoundSample &&) noexcept = default; + [[nodiscard]] bool IsLoaded() const + { + return stream_ != nullptr; + } + void Release(); bool IsPlaying(); - void Play(int logSoundVolume, int logUserVolume, int logPan); - void Stop(); // Returns 0 on success. int SetChunkStream(std::string filePath, bool isMp3, bool logErrors = true); @@ -66,6 +61,45 @@ public: #endif } + /** + * @brief Start playing the sound for a given number of iterations (0 means loop). + */ + bool Play(int numIterations = 1); + + /** + * @brief Start playing the sound with the given sound and user volume, and a stereo position. + */ + bool PlayWithVolumeAndPan(int logSoundVolume, int logUserVolume, int logPan) + { + SetVolume(logSoundVolume + logUserVolume * (ATTENUATION_MIN / VOLUME_MIN), ATTENUATION_MIN, 0); + SetStereoPosition(logPan); + return Play(); + } + + /** + * @brief Stop playing the sound + */ + void Stop() + { + stream_->stop(); + } + + void SetVolume(int logVolume, int logMin, int logMax); + void SetStereoPosition(int logPan); + + void Mute() + { + stream_->mute(); + } + + void Unmute() + { + stream_->unmute(); + } + + /** + * @return Audio duration in ms + */ int GetLength() const; private: