Browse Source

Use SoundSample for music

Unifies audio handling between sound effects and music.
pull/4428/head
Gleb Mazovetskiy 4 years ago committed by Anders Jenbo
parent
commit
7f695c2835
  1. 8
      Source/effects.cpp
  2. 188
      Source/sound.cpp
  3. 10
      Source/sound.h
  4. 11
      Source/sound_defs.hpp
  5. 53
      Source/utils/soundsample.cpp
  6. 56
      Source/utils/soundsample.h

8
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();
}
}

188
Source/sound.cpp

@ -10,12 +10,7 @@
#include <memory>
#include <mutex>
#include <Aulib/DecoderDrmp3.h>
#include <Aulib/DecoderDrwav.h>
#include <Aulib/ResamplerSpeex.h>
#include <Aulib/Stream.h>
#include <SDL.h>
#include <aulib.h>
#include "engine/assets.hpp"
#include "init.h"
@ -40,18 +35,7 @@ bool gbSoundOn = true;
namespace {
std::optional<Aulib::Stream> music;
#ifdef DISABLE_STREAMING_MUSIC
std::unique_ptr<char[]> musicBuffer;
#endif
std::unique_ptr<Aulib::Decoder> CreateDecoder(bool isMp3)
{
if (isMp3)
return std::make_unique<Aulib::DecoderDrmp3>();
return std::make_unique<Aulib::DecoderDrwav>();
}
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<Aulib::ResamplerSpeex>(*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<std::uint8_t>(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<std::unique_ptr<SoundSample>> 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<TSnd> sound_file_load(const char *path, bool stream)
{
auto snd = std::make_unique<TSnd>();
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<std::uint8_t>(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

10
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,

11
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

53
Source/utils/soundsample.cpp

@ -70,14 +70,21 @@ std::unique_ptr<Aulib::Stream> CreateStream(SDL_RWops *handle, bool isMp3)
std::make_unique<Aulib::ResamplerSpeex>(*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<float>(logMin), static_cast<float>(logMax), MillibelMin, MillibelMax, static_cast<float>(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<std::uint8_t> 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_)

56
Source/utils/soundsample.h

@ -6,29 +6,24 @@
#include <Aulib/Stream.h>
#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:

Loading…
Cancel
Save