Browse Source

🐞 Support multiple playback of the same sound

pull/1811/head
Gleb Mazovetskiy 5 years ago committed by Anders Jenbo
parent
commit
7bdfb0655c
  1. 2
      Source/effects.cpp
  2. 54
      Source/sound.cpp
  3. 7
      Source/sound.h
  4. 4
      Source/sound_stubs.cpp
  5. 1
      Source/storm/storm_svid.cpp
  6. 41
      Source/utils/soundsample.cpp
  7. 37
      Source/utils/soundsample.h
  8. 29
      Source/utils/stdcompat/shared_ptr_array.hpp

2
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()

54
Source/sound.cpp

@ -3,6 +3,8 @@
*
* Implementation of functions setting up the audio pipeline.
*/
#include "sound.h"
#include <cstdint>
#include <memory>
@ -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<std::unique_ptr<SoundSample>> duplicateSounds;
SoundSample *DuplicateSound(const SoundSample &sound) {
auto duplicate = std::make_unique<SoundSample>();
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<TSnd> 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<TSnd>();
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<std::uint8_t[]>(dwBytes);
auto wave_file = MakeArraySharedPtr<std::uint8_t>(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

7
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<TSnd> sound_file_load(const char *path, bool stream = false);

4
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<TSnd> sound_file_load(const char *path, bool stream) { return nullptr; }
void sound_file_cleanup(TSnd *sound_file) { }

1
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 {

41
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<float>(-std::fabs(lPan) / Scale)),
static_cast<float>(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<Aulib::DecoderDrwav>(),
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<Aulib::Stream>(SFileRw_FromStormHandle(file_handle_), std::make_unique<Aulib::DecoderDrwav>(),
std::make_unique<Aulib::ResamplerSpeex>(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<std::uint8_t[]> fileData, size_t dwBytes)
int SoundSample::SetChunk(ArraySharedPtr<std::uint8_t> 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<Aulib::DecoderDrwav>(),
stream_ = std::make_unique<Aulib::Stream>(buf, std::make_unique<Aulib::DecoderDrwav>(),
std::make_unique<Aulib::ResamplerSpeex>(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<std::uint8_t[]> fileData, size_t dwByt
/**
* @return Audio duration in ms
*/
int SoundSample::GetLength()
int SoundSample::GetLength() const
{
if (!stream_)
return 0;

37
Source/utils/soundsample.h

@ -1,22 +1,28 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <Aulib/Stream.h>
#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<std::uint8_t[]> file_data, size_t dwBytes);
int SetChunk(ArraySharedPtr<std::uint8_t> 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<std::uint8_t[]> file_data_;
std::optional<Aulib::Stream> stream_;
// Non-streaming audio fields:
ArraySharedPtr<std::uint8_t> file_data_;
std::size_t file_data_size_;
// Streaming audio fields:
HANDLE file_handle_ = nullptr;
std::string file_path_;
std::unique_ptr<Aulib::Stream> stream_;
};
} // namespace devilution

29
Source/utils/stdcompat/shared_ptr_array.hpp

@ -0,0 +1,29 @@
#pragma once
#include <cstddef>
#include <memory>
namespace devilution {
// Apple Clang 12 has a buggy implementation that fails to compile `std::shared_ptr<T[]>(new T[size])`.
#if __cplusplus >= 201611L && (!defined(__clang_major__) || __clang_major__ >= 13)
template <typename T>
using ArraySharedPtr = std::shared_ptr<T[]>;
template <typename T>
ArraySharedPtr<T> MakeArraySharedPtr(std::size_t size)
{
return ArraySharedPtr<T>(new T[size]);
}
#else
template <typename T>
using ArraySharedPtr = std::shared_ptr<T>;
template <typename T>
ArraySharedPtr<T> MakeArraySharedPtr(std::size_t size)
{
return ArraySharedPtr<T> { new T[size], std::default_delete<T[]>() };
}
#endif
} // namespace devilution
Loading…
Cancel
Save