You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
4.9 KiB
179 lines
4.9 KiB
#include "utils/soundsample.h" |
|
|
|
#include <chrono> |
|
#include <cmath> |
|
#include <utility> |
|
|
|
#include <Aulib/DecoderDrmp3.h> |
|
#include <Aulib/DecoderDrwav.h> |
|
#include <SDL.h> |
|
#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<float>(-std::abs(logPan)) / StereoSeparation); |
|
|
|
return copysign(1.F - factor, static_cast<float>(logPan)); |
|
} |
|
|
|
std::unique_ptr<Aulib::Decoder> CreateDecoder(bool isMp3) |
|
{ |
|
if (isMp3) |
|
return std::make_unique<Aulib::DecoderDrmp3>(); |
|
return std::make_unique<Aulib::DecoderDrwav>(); |
|
} |
|
|
|
std::unique_ptr<Aulib::Stream> 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<Aulib::Stream>(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<float>(logMin), static_cast<float>(logMax), MillibelMin, MillibelMax, static_cast<float>(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<std::uint8_t> 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<std::chrono::milliseconds>(stream_->duration()).count(); |
|
} |
|
|
|
} // namespace devilution
|
|
|