#include "utils/soundsample.h" #include #include #include #include #include #include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" #else #include "utils/sdl2_backports.h" #endif #include "engine/assets.hpp" #include "options.h" #include "utils/aulib.hpp" #include "utils/log.hpp" #include "utils/math.h" #include "utils/stubs.h" namespace devilution { namespace { constexpr float LogBase = 10.0; /** * Scaling factor for attenuating volume. * Picked so that a volume change of -10 dB results in half perceived loudness. * VolumeScale = -1000 / log(0.5) */ constexpr float VolumeScale = 3321.9281; /** * Min and max volume range, in millibel. * -100 dB (muted) to 0 dB (max. loudness). */ constexpr float MillibelMin = -10000.F; constexpr float MillibelMax = 0.F; /** * Stereo separation factor for left/right speaker panning. Lower values increase separation, moving * sounds further left/right, while higher values will pull sounds more towards the middle, reducing separation. * Current value is tuned to have ~2:1 mix for sounds that happen on the edge of a 640x480 screen. */ constexpr float StereoSeparation = 6000.F; float PanLogToLinear(int logPan) { if (logPan == 0) return 0; auto factor = std::pow(LogBase, static_cast(-std::abs(logPan)) / StereoSeparation); return copysign(1.F - factor, static_cast(logPan)); } std::unique_ptr CreateDecoder(bool isMp3) { if (isMp3) return std::make_unique(); return std::make_unique(); } std::unique_ptr CreateStream(SDL_RWops *handle, bool isMp3) { auto decoder = CreateDecoder(isMp3); if (!decoder->open(handle)) // open for `getRate` return nullptr; auto resampler = CreateAulibResampler(decoder->getRate()); return std::make_unique(handle, std::move(decoder), std::move(resampler), /*closeRw=*/true); } /** * @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() { stream_ = nullptr; #ifndef STREAM_ALL_AUDIO file_data_ = nullptr; file_data_size_ = 0; #endif } /** * @brief Check if a the sound is being played atm */ bool SoundSample::IsPlaying() { return stream_ && stream_->isPlaying(); } bool SoundSample::Play(int numIterations) { if (!stream_->play(numIterations)) { LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); return false; } return true; } int SoundSample::SetChunkStream(std::string filePath, bool isMp3, bool logErrors) { SDL_RWops *handle = OpenAssetAsSdlRwOps(filePath.c_str(), /*threadsafe=*/true); if (handle == nullptr) { if (logErrors) LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream) for {}: {}", filePath, SDL_GetError()); return -1; } file_path_ = std::move(filePath); isMp3_ = isMp3; stream_ = CreateStream(handle, isMp3); if (!stream_->open()) { stream_ = nullptr; if (logErrors) LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream) for {}: {}", file_path_, SDL_GetError()); return -1; } return 0; } #ifndef STREAM_ALL_AUDIO int SoundSample::SetChunk(ArraySharedPtr fileData, std::size_t dwBytes, bool isMp3) { isMp3_ = isMp3; file_data_ = std::move(fileData); file_data_size_ = dwBytes; SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), dwBytes); if (buf == nullptr) { return -1; } stream_ = CreateStream(buf, isMp3_); if (!stream_->open()) { stream_ = nullptr; file_data_ = nullptr; LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunk): {}", SDL_GetError()); return -1; } return 0; } #endif 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_) return 0; return std::chrono::duration_cast(stream_->duration()).count(); } } // namespace devilution