Merge branch 'clang-format-adjustments' into 'master'

cxx: clang-format adjustments

See merge request KartKrew/Kart!880
This commit is contained in:
James R 2023-01-12 19:09:00 -08:00
commit d5302d9e5d
38 changed files with 849 additions and 411 deletions

View file

@ -6,6 +6,8 @@ UseTab: Always
TabWidth: 4 TabWidth: 4
ColumnLimit: 120 ColumnLimit: 120
AccessModifierOffset: -4 AccessModifierOffset: -4
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false AllowShortEnumsOnASingleLine: false
@ -13,10 +15,11 @@ AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: All AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AlignAfterOpenBracket: BlockIndent
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakBeforeBraces: Attach # K&R/OTBS, braces on same line, Java style BreakBeforeBraces: Allman # Always break before braces, to match existing SRB2 code
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
CompactNamespaces: true CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerAllOnOneLineOrOnePerLine: true

View file

@ -30,20 +30,26 @@ using std::size_t;
using namespace srb2::audio; using namespace srb2::audio;
using namespace srb2; using namespace srb2;
namespace { namespace
{
// Utility for leveraging Resampler... // Utility for leveraging Resampler...
class SoundChunkSource : public Source<1> { class SoundChunkSource : public Source<1>
{
public: public:
explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk) explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk)
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk)) {} : chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk))
{
}
virtual size_t generate(tcb::span<Sample<1>> buffer) override final { virtual size_t generate(tcb::span<Sample<1>> buffer) override final
{
if (!chunk_) if (!chunk_)
return 0; return 0;
size_t written = 0; size_t written = 0;
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) { for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++)
{
buffer[written] = chunk_->samples[pos_]; buffer[written] = chunk_->samples[pos_];
written++; written++;
} }
@ -56,13 +62,15 @@ private:
}; };
template <class I> template <class I>
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) { std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0)
{
std::vector<Sample<1>> generated; std::vector<Sample<1>> generated;
size_t total = 0; size_t total = 0;
size_t read = 0; size_t read = 0;
generated.reserve(estimate); generated.reserve(estimate);
do { do
{
generated.resize(total + 4096); generated.resize(total + 4096);
read = source.generate(tcb::span {generated.data() + total, 4096}); read = source.generate(tcb::span {generated.data() + total, 4096});
total += read; total += read;
@ -71,7 +79,8 @@ std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) {
return generated; return generated;
} }
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) { optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data)
{
io::SpanStream stream {data}; io::SpanStream stream {data};
if (io::remaining(stream) < 8) if (io::remaining(stream) < 8)
@ -90,14 +99,16 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
stream.seek(io::SeekFrom::kCurrent, 16); stream.seek(io::SeekFrom::kCurrent, 16);
std::vector<Sample<1>> samples; std::vector<Sample<1>> samples;
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++)
{
uint8_t doom_sample = io::read_uint8(stream); uint8_t doom_sample = io::read_uint8(stream);
float float_sample = audio::sample_to_float(doom_sample); float float_sample = audio::sample_to_float(doom_sample);
samples.push_back(Sample<1> {float_sample}); samples.push_back(Sample<1> {float_sample});
} }
size_t samples_len = samples.size(); size_t samples_len = samples.size();
if (rate == 44100) { if (rate == 44100)
{
return SoundChunk {samples}; return SoundChunk {samples};
} }
@ -110,7 +121,8 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
size_t total = 0; size_t total = 0;
size_t read = 0; size_t read = 0;
resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate)); resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate));
do { do
{
resampled.resize(total + 4096); resampled.resize(total + 4096);
read = resampler.generate(tcb::span {resampled.data() + total, 4096}); read = resampler.generate(tcb::span {resampled.data() + total, 4096});
total += read; total += read;
@ -120,34 +132,44 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
return SoundChunk {std::move(resampled)}; return SoundChunk {std::move(resampled)};
} }
optional<SoundChunk> try_load_wav(tcb::span<std::byte> data) { optional<SoundChunk> try_load_wav(tcb::span<std::byte> data)
{
io::SpanStream stream {data}; io::SpanStream stream {data};
audio::Wav wav; audio::Wav wav;
std::size_t sample_rate; std::size_t sample_rate;
try { try
{
wav = audio::load_wav(stream); wav = audio::load_wav(stream);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
return nullopt; return nullopt;
} }
sample_rate = wav.sample_rate(); sample_rate = wav.sample_rate();
audio::Resampler<1> resampler(std::make_unique<WavPlayer>(std::move(wav)), audio::Resampler<1> resampler(
sample_rate / static_cast<float>(kSampleRate)); std::make_unique<WavPlayer>(std::move(wav)),
sample_rate / static_cast<float>(kSampleRate)
);
SoundChunk chunk {generate_to_vec(resampler)}; SoundChunk chunk {generate_to_vec(resampler)};
return chunk; return chunk;
} }
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) { optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data)
{
std::shared_ptr<audio::OggPlayer<1>> player; std::shared_ptr<audio::OggPlayer<1>> player;
try { try
{
io::SpanStream data_stream {data}; io::SpanStream data_stream {data};
audio::Ogg ogg = audio::load_ogg(data_stream); audio::Ogg ogg = audio::load_ogg(data_stream);
player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg)); player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg));
} catch (...) { }
catch (...)
{
return nullopt; return nullopt;
} }
player->looping(false); player->looping(false);
@ -161,19 +183,26 @@ optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) {
return chunk; return chunk;
} }
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) { optional<SoundChunk> try_load_gme(tcb::span<std::byte> data)
{
std::shared_ptr<audio::GmePlayer<1>> player; std::shared_ptr<audio::GmePlayer<1>> player;
try { try
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { {
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B})
{
io::SpanStream stream {data}; io::SpanStream stream {data};
audio::Gme gme = audio::load_gme(stream); audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme)); player = std::make_shared<GmePlayer<1>>(std::move(gme));
} else { }
else
{
io::ZlibInputStream stream {io::SpanStream(data)}; io::ZlibInputStream stream {io::SpanStream(data)};
audio::Gme gme = audio::load_gme(stream); audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme)); player = std::make_shared<GmePlayer<1>>(std::move(gme));
} }
} catch (...) { }
catch (...)
{
return nullopt; return nullopt;
} }
std::vector<Sample<1>> samples {generate_to_vec(*player)}; std::vector<Sample<1>> samples {generate_to_vec(*player)};
@ -183,7 +212,8 @@ optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) {
} // namespace } // namespace
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data) { optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data)
{
optional<SoundChunk> ret; optional<SoundChunk> ret;
ret = try_load_dmx(data); ret = try_load_dmx(data);

View file

@ -17,7 +17,8 @@
#include "sound_chunk.hpp" #include "sound_chunk.hpp"
namespace srb2::audio { namespace srb2::audio
{
/// @brief Try to load a chunk from the given byte span. /// @brief Try to load a chunk from the given byte span.
std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data); std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data);

View file

@ -17,8 +17,10 @@ using namespace srb2::audio;
ExpandMono::~ExpandMono() = default; ExpandMono::~ExpandMono() = default;
size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) { size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer)
for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++) { {
for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++)
{
buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0]; buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0];
buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0]; buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0];
} }

View file

@ -14,9 +14,11 @@
#include "filter.hpp" #include "filter.hpp"
namespace srb2::audio { namespace srb2::audio
{
class ExpandMono : public Filter<1, 2> { class ExpandMono : public Filter<1, 2>
{
public: public:
virtual ~ExpandMono(); virtual ~ExpandMono();
virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final; virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final;

View file

@ -17,7 +17,8 @@ using srb2::audio::Sample;
using srb2::audio::Source; using srb2::audio::Source;
template <size_t IC, size_t OC> template <size_t IC, size_t OC>
size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) { size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer)
{
input_buffer_.clear(); input_buffer_.clear();
input_buffer_.resize(buffer.size()); input_buffer_.resize(buffer.size());
@ -27,7 +28,8 @@ size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) {
} }
template <size_t IC, size_t OC> template <size_t IC, size_t OC>
void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input) { void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input)
{
input_ = input; input_ = input;
} }

View file

@ -18,10 +18,12 @@
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t IC, size_t OC> template <size_t IC, size_t OC>
class Filter : public Source<OC> { class Filter : public Source<OC>
{
public: public:
virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override; virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override;

View file

@ -20,9 +20,11 @@ using srb2::audio::Sample;
constexpr const float kGainInterpolationAlpha = 0.8f; constexpr const float kGainInterpolationAlpha = 0.8f;
template <size_t C> template <size_t C>
size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) { size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer)
{
size_t written = std::min(buffer.size(), input_buffer.size()); size_t written = std::min(buffer.size(), input_buffer.size());
for (size_t i = 0; i < written; i++) { for (size_t i = 0; i < written; i++)
{
buffer[i] = input_buffer[i]; buffer[i] = input_buffer[i];
buffer[i] *= gain_; buffer[i] *= gain_;
gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha; gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha;
@ -32,7 +34,8 @@ size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> b
} }
template <size_t C> template <size_t C>
void Gain<C>::gain(float new_gain) { void Gain<C>::gain(float new_gain)
{
new_gain_ = std::clamp(new_gain, 0.0f, 1.0f); new_gain_ = std::clamp(new_gain, 0.0f, 1.0f);
} }

View file

@ -14,10 +14,12 @@
#include "filter.hpp" #include "filter.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class Gain : public Filter<C, C> { class Gain : public Filter<C, C>
{
public: public:
virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final; virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final;
void gain(float new_gain); void gain(float new_gain);

View file

@ -17,37 +17,45 @@
using namespace srb2; using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
Gme::Gme() : memory_data_(), instance_(nullptr) { Gme::Gme() : memory_data_(), instance_(nullptr)
{
} }
Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr) { Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr)
{
std::swap(memory_data_, rhs.memory_data_); std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
} }
Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr) { Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr)
{
_init_with_data(); _init_with_data();
} }
Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr)
{
_init_with_data(); _init_with_data();
} }
Gme& Gme::operator=(Gme&& rhs) noexcept { Gme& Gme::operator=(Gme&& rhs) noexcept
{
std::swap(memory_data_, rhs.memory_data_); std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
return *this; return *this;
} }
Gme::~Gme() { Gme::~Gme()
if (instance_) { {
if (instance_)
{
gme_delete(instance_); gme_delete(instance_);
instance_ = nullptr; instance_ = nullptr;
} }
} }
std::size_t Gme::get_samples(tcb::span<short> buffer) { std::size_t Gme::get_samples(tcb::span<short> buffer)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
gme_err_t err = gme_play(instance_, buffer.size(), buffer.data()); gme_err_t err = gme_play(instance_, buffer.size(), buffer.data());
@ -57,13 +65,15 @@ std::size_t Gme::get_samples(tcb::span<short> buffer) {
return buffer.size(); return buffer.size();
} }
void Gme::seek(int sample) { void Gme::seek(int sample)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
gme_seek_samples(instance_, sample); gme_seek_samples(instance_, sample);
} }
std::optional<float> Gme::duration_seconds() const { std::optional<float> Gme::duration_seconds() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr; gme_info_t* info = nullptr;
@ -79,7 +89,8 @@ std::optional<float> Gme::duration_seconds() const {
return static_cast<float>(info->length) / 1000.f; return static_cast<float>(info->length) / 1000.f;
} }
std::optional<float> Gme::loop_point_seconds() const { std::optional<float> Gme::loop_point_seconds() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr; gme_info_t* info = nullptr;
@ -95,7 +106,8 @@ std::optional<float> Gme::loop_point_seconds() const {
return loop_point_ms / 44100.f; return loop_point_ms / 44100.f;
} }
float Gme::position_seconds() const { float Gme::position_seconds() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr; gme_info_t* info = nullptr;
@ -117,8 +129,10 @@ float Gme::position_seconds() const {
return position / 1000.f; return position / 1000.f;
} }
void Gme::_init_with_data() { void Gme::_init_with_data()
if (instance_) { {
if (instance_)
{
return; return;
} }

View file

@ -24,9 +24,11 @@
#include "../io/streams.hpp" #include "../io/streams.hpp"
namespace srb2::audio { namespace srb2::audio
{
class GmeException : public std::exception { class GmeException : public std::exception
{
std::string msg_; std::string msg_;
public: public:
@ -35,7 +37,8 @@ public:
virtual const char* what() const noexcept override { return msg_.c_str(); } virtual const char* what() const noexcept override { return msg_.c_str(); }
}; };
class Gme { class Gme
{
std::vector<std::byte> memory_data_; std::vector<std::byte> memory_data_;
Music_Emu* instance_; Music_Emu* instance_;
@ -64,7 +67,8 @@ private:
}; };
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Gme load_gme(I& stream) { inline Gme load_gme(I& stream)
{
std::vector<std::byte> data = srb2::io::read_to_vec(stream); std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Gme {std::move(data)}; return Gme {std::move(data)};
} }

View file

@ -13,7 +13,8 @@ using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
template <size_t C> template <size_t C>
GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_() { GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_()
{
} }
template <size_t C> template <size_t C>
@ -26,17 +27,22 @@ template <size_t C>
GmePlayer<C>::~GmePlayer() = default; GmePlayer<C>::~GmePlayer() = default;
template <size_t C> template <size_t C>
std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) { std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer)
{
buf_.clear(); buf_.clear();
buf_.resize(buffer.size() * 2); buf_.resize(buffer.size() * 2);
std::size_t read = gme_.get_samples(tcb::make_span(buf_)); std::size_t read = gme_.get_samples(tcb::make_span(buf_));
buf_.resize(read); buf_.resize(read);
std::size_t new_samples = std::min((read / 2), buffer.size()); std::size_t new_samples = std::min((read / 2), buffer.size());
for (std::size_t i = 0; i < new_samples; i++) { for (std::size_t i = 0; i < new_samples; i++)
if constexpr (C == 1) { {
if constexpr (C == 1)
{
buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f; buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f;
} else if constexpr (C == 2) { }
else if constexpr (C == 2)
{
buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f; buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f;
buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f; buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f;
} }
@ -45,27 +51,32 @@ std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) {
} }
template <size_t C> template <size_t C>
void GmePlayer<C>::seek(float position_seconds) { void GmePlayer<C>::seek(float position_seconds)
{
gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f)); gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f));
} }
template <size_t C> template <size_t C>
void GmePlayer<C>::reset() { void GmePlayer<C>::reset()
{
gme_.seek(0); gme_.seek(0);
} }
template <size_t C> template <size_t C>
std::optional<float> GmePlayer<C>::duration_seconds() const { std::optional<float> GmePlayer<C>::duration_seconds() const
{
return gme_.duration_seconds(); return gme_.duration_seconds();
} }
template <size_t C> template <size_t C>
std::optional<float> GmePlayer<C>::loop_point_seconds() const { std::optional<float> GmePlayer<C>::loop_point_seconds() const
{
return gme_.loop_point_seconds(); return gme_.loop_point_seconds();
} }
template <size_t C> template <size_t C>
float GmePlayer<C>::position_seconds() const { float GmePlayer<C>::position_seconds() const
{
return gme_.position_seconds(); return gme_.position_seconds();
} }

View file

@ -15,10 +15,12 @@
#include "gme.hpp" #include "gme.hpp"
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class GmePlayer : public Source<C> { class GmePlayer : public Source<C>
{
Gme gme_; Gme gme_;
std::vector<short> buf_; std::vector<short> buf_;

View file

@ -18,16 +18,20 @@ using srb2::audio::Mixer;
using srb2::audio::Sample; using srb2::audio::Sample;
using srb2::audio::Source; using srb2::audio::Source;
namespace { namespace
{
template <size_t C> template <size_t C>
void default_init_sample_buffer(Sample<C>* buffer, size_t size) { void default_init_sample_buffer(Sample<C>* buffer, size_t size)
{
std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; }); std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; });
} }
template <size_t C> template <size_t C>
void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size) { void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size)
for (size_t i = 0; i < size && i < src_size; i++) { {
for (size_t i = 0; i < size && i < src_size; i++)
{
dst[i] += src[i]; dst[i] += src[i];
} }
} }
@ -35,12 +39,14 @@ void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_
} // namespace } // namespace
template <size_t C> template <size_t C>
size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) { size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer)
{
buffer_.resize(buffer.size()); buffer_.resize(buffer.size());
default_init_sample_buffer<C>(buffer.data(), buffer.size()); default_init_sample_buffer<C>(buffer.data(), buffer.size());
for (auto& source : sources_) { for (auto& source : sources_)
{
size_t read = source->generate(buffer_); size_t read = source->generate(buffer_);
mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read); mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read);
@ -51,7 +57,8 @@ size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) {
} }
template <size_t C> template <size_t C>
void Mixer<C>::add_source(const shared_ptr<Source<C>>& source) { void Mixer<C>::add_source(const shared_ptr<Source<C>>& source)
{
sources_.push_back(source); sources_.push_back(source);
} }

View file

@ -17,10 +17,12 @@
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class Mixer : public Source<C> { class Mixer : public Source<C>
{
public: public:
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final; virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;

View file

@ -43,12 +43,14 @@ using srb2::audio::Sample;
using srb2::audio::Source; using srb2::audio::Source;
using namespace srb2; using namespace srb2;
class MusicPlayer::Impl { class MusicPlayer::Impl
{
public: public:
Impl() = default; Impl() = default;
Impl(tcb::span<std::byte> data) : Impl() { _load(data); } Impl(tcb::span<std::byte> data) : Impl() { _load(data); }
size_t generate(tcb::span<Sample<2>> buffer) { size_t generate(tcb::span<Sample<2>> buffer)
{
if (!resampler_) if (!resampler_)
return 0; return 0;
@ -57,12 +59,14 @@ public:
size_t total_written = 0; size_t total_written = 0;
while (total_written < buffer.size()) { while (total_written < buffer.size())
{
const size_t generated = resampler_->generate(buffer.subspan(total_written)); const size_t generated = resampler_->generate(buffer.subspan(total_written));
// To avoid a branch preventing optimizations, we're always going to apply // To avoid a branch preventing optimizations, we're always going to apply
// the fade gain, even if it would clamp anyway. // the fade gain, even if it would clamp anyway.
for (std::size_t i = 0; i < generated; i++) { for (std::size_t i = 0; i < generated; i++)
{
const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) / const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) /
static_cast<double>(gain_samples_target_); static_cast<double>(gain_samples_target_);
const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_; const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_;
@ -71,7 +75,8 @@ public:
gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_); gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_);
if (gain_samples_ >= gain_samples_target_) { if (gain_samples_ >= gain_samples_target_)
{
fading_ = false; fading_ = false;
gain_samples_ = gain_samples_target_; gain_samples_ = gain_samples_target_;
gain_ = gain_target_; gain_ = gain_target_;
@ -79,7 +84,8 @@ public:
total_written += generated; total_written += generated;
if (generated == 0) { if (generated == 0)
{
playing_ = false; playing_ = false;
break; break;
} }
@ -88,53 +94,68 @@ public:
return total_written; return total_written;
} }
void _load(tcb::span<std::byte> data) { void _load(tcb::span<std::byte> data)
{
ogg_inst_ = nullptr; ogg_inst_ = nullptr;
gme_inst_ = nullptr; gme_inst_ = nullptr;
xmp_inst_ = nullptr; xmp_inst_ = nullptr;
resampler_ = std::nullopt; resampler_ = std::nullopt;
try { try
{
io::SpanStream stream {data}; io::SpanStream stream {data};
audio::Ogg ogg = audio::load_ogg(stream); audio::Ogg ogg = audio::load_ogg(stream);
ogg_inst_ = std::make_shared<audio::OggPlayer<2>>(std::move(ogg)); ogg_inst_ = std::make_shared<audio::OggPlayer<2>>(std::move(ogg));
ogg_inst_->looping(looping_); ogg_inst_->looping(looping_);
resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f); resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
// it's probably not ogg // it's probably not ogg
ogg_inst_ = nullptr; ogg_inst_ = nullptr;
resampler_ = std::nullopt; resampler_ = std::nullopt;
} }
if (!resampler_) { if (!resampler_)
try { {
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { try
{
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B})
{
io::ZlibInputStream stream {io::SpanStream(data)}; io::ZlibInputStream stream {io::SpanStream(data)};
audio::Gme gme = audio::load_gme(stream); audio::Gme gme = audio::load_gme(stream);
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme)); gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
} else { }
else
{
io::SpanStream stream {data}; io::SpanStream stream {data};
audio::Gme gme = audio::load_gme(stream); audio::Gme gme = audio::load_gme(stream);
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme)); gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
} }
resampler_ = Resampler<2>(gme_inst_, 1.f); resampler_ = Resampler<2>(gme_inst_, 1.f);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
// it's probably not gme // it's probably not gme
gme_inst_ = nullptr; gme_inst_ = nullptr;
resampler_ = std::nullopt; resampler_ = std::nullopt;
} }
} }
if (!resampler_) { if (!resampler_)
try { {
try
{
io::SpanStream stream {data}; io::SpanStream stream {data};
audio::Xmp<2> xmp = audio::load_xmp<2>(stream); audio::Xmp<2> xmp = audio::load_xmp<2>(stream);
xmp_inst_ = std::make_shared<XmpPlayer<2>>(std::move(xmp)); xmp_inst_ = std::make_shared<XmpPlayer<2>>(std::move(xmp));
xmp_inst_->looping(looping_); xmp_inst_->looping(looping_);
resampler_ = Resampler<2>(xmp_inst_, 1.f); resampler_ = Resampler<2>(xmp_inst_, 1.f);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
// it's probably not xmp // it's probably not xmp
xmp_inst_ = nullptr; xmp_inst_ = nullptr;
resampler_ = std::nullopt; resampler_ = std::nullopt;
@ -146,74 +167,103 @@ public:
internal_gain(1.f); internal_gain(1.f);
} }
void play(bool looping) { void play(bool looping)
if (ogg_inst_) { {
if (ogg_inst_)
{
ogg_inst_->looping(looping); ogg_inst_->looping(looping);
ogg_inst_->playing(true); ogg_inst_->playing(true);
playing_ = true; playing_ = true;
ogg_inst_->reset(); ogg_inst_->reset();
} else if (gme_inst_) { }
else if (gme_inst_)
{
playing_ = true; playing_ = true;
gme_inst_->reset(); gme_inst_->reset();
} else if (xmp_inst_) { }
else if (xmp_inst_)
{
xmp_inst_->looping(looping); xmp_inst_->looping(looping);
playing_ = true; playing_ = true;
xmp_inst_->reset(); xmp_inst_->reset();
} }
} }
void unpause() { void unpause()
if (ogg_inst_) { {
if (ogg_inst_)
{
ogg_inst_->playing(true); ogg_inst_->playing(true);
playing_ = true; playing_ = true;
} else if (gme_inst_) { }
else if (gme_inst_)
{
playing_ = true; playing_ = true;
} else if (xmp_inst_) { }
else if (xmp_inst_)
{
playing_ = true; playing_ = true;
} }
} }
void pause() { void pause()
if (ogg_inst_) { {
if (ogg_inst_)
{
ogg_inst_->playing(false); ogg_inst_->playing(false);
playing_ = false; playing_ = false;
} else if (gme_inst_) { }
else if (gme_inst_)
{
playing_ = false; playing_ = false;
} else if (xmp_inst_) { }
else if (xmp_inst_)
{
playing_ = false; playing_ = false;
} }
} }
void stop() { void stop()
if (ogg_inst_) { {
if (ogg_inst_)
{
ogg_inst_->reset(); ogg_inst_->reset();
ogg_inst_->playing(false); ogg_inst_->playing(false);
playing_ = false; playing_ = false;
} else if (gme_inst_) { }
else if (gme_inst_)
{
gme_inst_->reset(); gme_inst_->reset();
playing_ = false; playing_ = false;
} else if (xmp_inst_) { }
else if (xmp_inst_)
{
xmp_inst_->reset(); xmp_inst_->reset();
playing_ = false; playing_ = false;
} }
} }
void seek(float position_seconds) { void seek(float position_seconds)
if (ogg_inst_) { {
if (ogg_inst_)
{
ogg_inst_->seek(position_seconds); ogg_inst_->seek(position_seconds);
return; return;
} }
if (gme_inst_) { if (gme_inst_)
{
gme_inst_->seek(position_seconds); gme_inst_->seek(position_seconds);
return; return;
} }
if (xmp_inst_) { if (xmp_inst_)
{
xmp_inst_->seek(position_seconds); xmp_inst_->seek(position_seconds);
return; return;
} }
} }
bool playing() const { bool playing() const
{
if (ogg_inst_) if (ogg_inst_)
return ogg_inst_->playing(); return ogg_inst_->playing();
else if (gme_inst_) else if (gme_inst_)
@ -224,7 +274,8 @@ public:
return false; return false;
} }
std::optional<audio::MusicType> music_type() const { std::optional<audio::MusicType> music_type() const
{
if (ogg_inst_) if (ogg_inst_)
return audio::MusicType::kOgg; return audio::MusicType::kOgg;
else if (gme_inst_) else if (gme_inst_)
@ -235,7 +286,8 @@ public:
return std::nullopt; return std::nullopt;
} }
std::optional<float> duration_seconds() const { std::optional<float> duration_seconds() const
{
if (ogg_inst_) if (ogg_inst_)
return ogg_inst_->duration_seconds(); return ogg_inst_->duration_seconds();
if (gme_inst_) if (gme_inst_)
@ -246,7 +298,8 @@ public:
return std::nullopt; return std::nullopt;
} }
std::optional<float> loop_point_seconds() const { std::optional<float> loop_point_seconds() const
{
if (ogg_inst_) if (ogg_inst_)
return ogg_inst_->loop_point_seconds(); return ogg_inst_->loop_point_seconds();
if (gme_inst_) if (gme_inst_)
@ -255,7 +308,8 @@ public:
return std::nullopt; return std::nullopt;
} }
std::optional<float> position_seconds() const { std::optional<float> position_seconds() const
{
if (ogg_inst_) if (ogg_inst_)
return ogg_inst_->position_seconds(); return ogg_inst_->position_seconds();
if (gme_inst_) if (gme_inst_)
@ -266,13 +320,14 @@ public:
void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); } void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); }
void fade_from_to(float from, float to, float seconds) { void fade_from_to(float from, float to, float seconds)
{
fading_ = true; fading_ = true;
gain_ = from; gain_ = from;
gain_target_ = to; gain_target_ = to;
// Gain samples target must always be at least 1 to avoid a div-by-zero. // Gain samples target must always be at least 1 to avoid a div-by-zero.
gain_samples_target_ = std::max( gain_samples_target_ =
static_cast<uint64_t>(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal std::max(static_cast<uint64_t>(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal
gain_samples_ = 0; gain_samples_ = 0;
} }
@ -280,12 +335,14 @@ public:
void stop_fade() { internal_gain(gain_target_); } void stop_fade() { internal_gain(gain_target_); }
void loop_point_seconds(float loop_point) { void loop_point_seconds(float loop_point)
{
if (ogg_inst_) if (ogg_inst_)
ogg_inst_->loop_point_seconds(loop_point); ogg_inst_->loop_point_seconds(loop_point);
} }
void internal_gain(float gain) { void internal_gain(float gain)
{
fading_ = false; fading_ = false;
gain_ = gain; gain_ = gain;
gain_target_ = gain; gain_target_ = gain;
@ -310,112 +367,131 @@ private:
}; };
// The special member functions MUST be declared in this unit, where Impl is complete. // The special member functions MUST be declared in this unit, where Impl is complete.
MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>()) { MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>())
{
} }
MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data)) { MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data))
{
} }
MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default; MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default;
MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default; MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default;
MusicPlayer::~MusicPlayer() = default; MusicPlayer::~MusicPlayer() = default;
void MusicPlayer::play(bool looping) { void MusicPlayer::play(bool looping)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->play(looping); return impl_->play(looping);
} }
void MusicPlayer::unpause() { void MusicPlayer::unpause()
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->unpause(); return impl_->unpause();
} }
void MusicPlayer::pause() { void MusicPlayer::pause()
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->pause(); return impl_->pause();
} }
void MusicPlayer::stop() { void MusicPlayer::stop()
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->stop(); return impl_->stop();
} }
void MusicPlayer::seek(float position_seconds) { void MusicPlayer::seek(float position_seconds)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->seek(position_seconds); return impl_->seek(position_seconds);
} }
bool MusicPlayer::playing() const { bool MusicPlayer::playing() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->playing(); return impl_->playing();
} }
size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer) { size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->generate(buffer); return impl_->generate(buffer);
} }
std::optional<audio::MusicType> MusicPlayer::music_type() const { std::optional<audio::MusicType> MusicPlayer::music_type() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->music_type(); return impl_->music_type();
} }
std::optional<float> MusicPlayer::duration_seconds() const { std::optional<float> MusicPlayer::duration_seconds() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->duration_seconds(); return impl_->duration_seconds();
} }
std::optional<float> MusicPlayer::loop_point_seconds() const { std::optional<float> MusicPlayer::loop_point_seconds() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->loop_point_seconds(); return impl_->loop_point_seconds();
} }
std::optional<float> MusicPlayer::position_seconds() const { std::optional<float> MusicPlayer::position_seconds() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->position_seconds(); return impl_->position_seconds();
} }
void MusicPlayer::fade_to(float gain, float seconds) { void MusicPlayer::fade_to(float gain, float seconds)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
impl_->fade_to(gain, seconds); impl_->fade_to(gain, seconds);
} }
void MusicPlayer::fade_from_to(float from, float to, float seconds) { void MusicPlayer::fade_from_to(float from, float to, float seconds)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
impl_->fade_from_to(from, to, seconds); impl_->fade_from_to(from, to, seconds);
} }
void MusicPlayer::internal_gain(float gain) { void MusicPlayer::internal_gain(float gain)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
impl_->internal_gain(gain); impl_->internal_gain(gain);
} }
bool MusicPlayer::fading() const { bool MusicPlayer::fading() const
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
return impl_->fading(); return impl_->fading();
} }
void MusicPlayer::stop_fade() { void MusicPlayer::stop_fade()
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
impl_->stop_fade(); impl_->stop_fade();
} }
void MusicPlayer::loop_point_seconds(float loop_point) { void MusicPlayer::loop_point_seconds(float loop_point)
{
SRB2_ASSERT(impl_ != nullptr); SRB2_ASSERT(impl_ != nullptr);
impl_->loop_point_seconds(loop_point); impl_->loop_point_seconds(loop_point);

View file

@ -19,15 +19,18 @@
struct stb_vorbis; struct stb_vorbis;
namespace srb2::audio { namespace srb2::audio
{
enum class MusicType { enum class MusicType
{
kOgg, kOgg,
kGme, kGme,
kMod kMod
}; };
class MusicPlayer : public Source<2> { class MusicPlayer : public Source<2>
{
public: public:
MusicPlayer(); MusicPlayer();
MusicPlayer(tcb::span<std::byte> data); MusicPlayer(tcb::span<std::byte> data);

View file

@ -16,11 +16,14 @@
using namespace srb2; using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
StbVorbisException::StbVorbisException(int code) noexcept : code_(code) { StbVorbisException::StbVorbisException(int code) noexcept : code_(code)
{
} }
const char* StbVorbisException::what() const noexcept { const char* StbVorbisException::what() const noexcept
switch (code_) { {
switch (code_)
{
case VORBIS__no_error: case VORBIS__no_error:
return "No error"; return "No error";
case VORBIS_need_more_data: case VORBIS_need_more_data:
@ -68,54 +71,73 @@ const char* StbVorbisException::what() const noexcept {
} }
} }
Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr)
{
} }
Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr) { Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr)
{
_init_with_data(); _init_with_data();
} }
Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr)
{
_init_with_data(); _init_with_data();
} }
Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) { Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr)
{
std::swap(memory_data_, rhs.memory_data_); std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
} }
Ogg& Ogg::operator=(Ogg&& rhs) noexcept { Ogg& Ogg::operator=(Ogg&& rhs) noexcept
{
std::swap(memory_data_, rhs.memory_data_); std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
return *this; return *this;
} }
Ogg::~Ogg() { Ogg::~Ogg()
if (instance_) { {
if (instance_)
{
stb_vorbis_close(instance_); stb_vorbis_close(instance_);
instance_ = nullptr; instance_ = nullptr;
} }
} }
std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer) { std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
size_t read = stb_vorbis_get_samples_float_interleaved( size_t read = stb_vorbis_get_samples_float_interleaved(
instance_, 1, reinterpret_cast<float*>(buffer.data()), buffer.size() * 1); instance_,
1,
reinterpret_cast<float*>(buffer.data()),
buffer.size() * 1
);
return read; return read;
} }
std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) { std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
size_t read = stb_vorbis_get_samples_float_interleaved( size_t read = stb_vorbis_get_samples_float_interleaved(
instance_, 2, reinterpret_cast<float*>(buffer.data()), buffer.size() * 2); instance_,
2,
reinterpret_cast<float*>(buffer.data()),
buffer.size() * 2
);
stb_vorbis_info info = stb_vorbis_get_info(instance_); stb_vorbis_info info = stb_vorbis_get_info(instance_);
if (info.channels == 1) { if (info.channels == 1)
for (auto& sample : buffer.subspan(0, read)) { {
for (auto& sample : buffer.subspan(0, read))
{
sample.amplitudes[1] = sample.amplitudes[0]; sample.amplitudes[1] = sample.amplitudes[0];
} }
} }
@ -123,7 +145,8 @@ std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) {
return read; return read;
} }
OggComment Ogg::comment() const { OggComment Ogg::comment() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_);
@ -133,50 +156,59 @@ OggComment Ogg::comment() const {
std::vector<std::string>(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; std::vector<std::string>(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)};
} }
std::size_t Ogg::sample_rate() const { std::size_t Ogg::sample_rate() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_info info = stb_vorbis_get_info(instance_); stb_vorbis_info info = stb_vorbis_get_info(instance_);
return info.sample_rate; return info.sample_rate;
} }
void Ogg::seek(std::size_t sample) { void Ogg::seek(std::size_t sample)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_seek(instance_, sample); stb_vorbis_seek(instance_, sample);
} }
std::size_t Ogg::position() const { std::size_t Ogg::position() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_get_sample_offset(instance_); return stb_vorbis_get_sample_offset(instance_);
} }
float Ogg::position_seconds() const { float Ogg::position_seconds() const
{
return position() / static_cast<float>(sample_rate()); return position() / static_cast<float>(sample_rate());
} }
std::size_t Ogg::duration_samples() const { std::size_t Ogg::duration_samples() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_stream_length_in_samples(instance_); return stb_vorbis_stream_length_in_samples(instance_);
} }
float Ogg::duration_seconds() const { float Ogg::duration_seconds() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_stream_length_in_seconds(instance_); return stb_vorbis_stream_length_in_seconds(instance_);
} }
std::size_t Ogg::channels() const { std::size_t Ogg::channels() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_info info = stb_vorbis_get_info(instance_); stb_vorbis_info info = stb_vorbis_get_info(instance_);
return info.channels; return info.channels;
} }
void Ogg::_init_with_data() { void Ogg::_init_with_data()
if (instance_) { {
if (instance_)
{
return; return;
} }
@ -187,7 +219,11 @@ void Ogg::_init_with_data() {
int vorbis_result; int vorbis_result;
instance_ = stb_vorbis_open_memory( instance_ = stb_vorbis_open_memory(
reinterpret_cast<const unsigned char*>(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL); reinterpret_cast<const unsigned char*>(memory_data_.data()),
memory_data_.size(),
&vorbis_result,
NULL
);
if (vorbis_result != VORBIS__no_error) if (vorbis_result != VORBIS__no_error)
throw StbVorbisException(vorbis_result); throw StbVorbisException(vorbis_result);

View file

@ -20,9 +20,11 @@
#include "../io/streams.hpp" #include "../io/streams.hpp"
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
class StbVorbisException final : public std::exception { class StbVorbisException final : public std::exception
{
int code_; int code_;
public: public:
@ -31,12 +33,14 @@ public:
virtual const char* what() const noexcept; virtual const char* what() const noexcept;
}; };
struct OggComment { struct OggComment
{
std::string vendor; std::string vendor;
std::vector<std::string> comments; std::vector<std::string> comments;
}; };
class Ogg final { class Ogg final
{
std::vector<std::byte> memory_data_; std::vector<std::byte> memory_data_;
stb_vorbis* instance_; stb_vorbis* instance_;
@ -71,7 +75,8 @@ private:
}; };
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Ogg load_ogg(I& stream) { inline Ogg load_ogg(I& stream)
{
std::vector<std::byte> data = srb2::io::read_to_vec(stream); std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Ogg {std::move(data)}; return Ogg {std::move(data)};
} }

View file

@ -18,35 +18,46 @@
using namespace srb2; using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
namespace { namespace
{
std::optional<std::size_t> find_loop_point(const Ogg& ogg) { std::optional<std::size_t> find_loop_point(const Ogg& ogg)
{
OggComment comment = ogg.comment(); OggComment comment = ogg.comment();
std::size_t rate = ogg.sample_rate(); std::size_t rate = ogg.sample_rate();
for (auto& comment : comment.comments) { for (auto& comment : comment.comments)
if (comment.find("LOOPPOINT=") == 0) { {
if (comment.find("LOOPPOINT=") == 0)
{
std::string_view comment_view(comment); std::string_view comment_view(comment);
comment_view.remove_prefix(10); comment_view.remove_prefix(10);
std::string copied {comment_view}; std::string copied {comment_view};
try { try
{
int loop_point = std::stoi(copied); int loop_point = std::stoi(copied);
return loop_point; return loop_point;
} catch (...) { }
catch (...)
{
} }
} }
if (comment.find("LOOPMS=") == 0) { if (comment.find("LOOPMS=") == 0)
{
std::string_view comment_view(comment); std::string_view comment_view(comment);
comment_view.remove_prefix(7); comment_view.remove_prefix(7);
std::string copied {comment_view}; std::string copied {comment_view};
try { try
{
int loop_ms = std::stoi(copied); int loop_ms = std::stoi(copied);
int loop_point = std::round(static_cast<double>(loop_ms) / (rate / 1000.)); int loop_point = std::round(static_cast<double>(loop_ms) / (rate / 1000.));
return loop_point; return loop_point;
} catch (...) { }
catch (...)
{
} }
} }
} }
@ -58,7 +69,8 @@ std::optional<std::size_t> find_loop_point(const Ogg& ogg) {
template <size_t C> template <size_t C>
OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept
: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg)) { : playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg))
{
loop_point_ = find_loop_point(ogg_); loop_point_ = find_loop_point(ogg_);
} }
@ -72,25 +84,30 @@ template <size_t C>
OggPlayer<C>::~OggPlayer() = default; OggPlayer<C>::~OggPlayer() = default;
template <size_t C> template <size_t C>
std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) { std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer)
{
if (!playing_) if (!playing_)
return 0; return 0;
std::size_t total = 0; std::size_t total = 0;
do { do
{
std::size_t read = ogg_.get_samples(buffer.subspan(total)); std::size_t read = ogg_.get_samples(buffer.subspan(total));
total += read; total += read;
if (read == 0 && !looping_) { if (read == 0 && !looping_)
{
playing_ = false; playing_ = false;
break; break;
} }
if (read == 0 && loop_point_) { if (read == 0 && loop_point_)
{
ogg_.seek(*loop_point_); ogg_.seek(*loop_point_);
} }
if (read == 0 && !loop_point_) { if (read == 0 && !loop_point_)
{
ogg_.seek(0); ogg_.seek(0);
} }
} while (total < buffer.size()); } while (total < buffer.size());
@ -99,33 +116,39 @@ std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
} }
template <size_t C> template <size_t C>
void OggPlayer<C>::seek(float position_seconds) { void OggPlayer<C>::seek(float position_seconds)
{
ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate())); ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate()));
} }
template <size_t C> template <size_t C>
void OggPlayer<C>::loop_point_seconds(float loop_point) { void OggPlayer<C>::loop_point_seconds(float loop_point)
{
std::size_t rate = sample_rate(); std::size_t rate = sample_rate();
loop_point = static_cast<std::size_t>(std::round(loop_point * rate)); loop_point = static_cast<std::size_t>(std::round(loop_point * rate));
} }
template <size_t C> template <size_t C>
void OggPlayer<C>::reset() { void OggPlayer<C>::reset()
{
ogg_.seek(0); ogg_.seek(0);
} }
template <size_t C> template <size_t C>
std::size_t OggPlayer<C>::sample_rate() const { std::size_t OggPlayer<C>::sample_rate() const
{
return ogg_.sample_rate(); return ogg_.sample_rate();
} }
template <size_t C> template <size_t C>
float OggPlayer<C>::duration_seconds() const { float OggPlayer<C>::duration_seconds() const
{
return ogg_.duration_seconds(); return ogg_.duration_seconds();
} }
template <size_t C> template <size_t C>
std::optional<float> OggPlayer<C>::loop_point_seconds() const { std::optional<float> OggPlayer<C>::loop_point_seconds() const
{
if (!loop_point_) if (!loop_point_)
return std::nullopt; return std::nullopt;
@ -133,7 +156,8 @@ std::optional<float> OggPlayer<C>::loop_point_seconds() const {
} }
template <size_t C> template <size_t C>
float OggPlayer<C>::position_seconds() const { float OggPlayer<C>::position_seconds() const
{
return ogg_.position_seconds(); return ogg_.position_seconds();
} }

View file

@ -25,10 +25,12 @@
#include "ogg.hpp" #include "ogg.hpp"
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class OggPlayer final : public Source<C> { class OggPlayer final : public Source<C>
{
bool playing_; bool playing_;
bool looping_; bool looping_;
std::optional<std::size_t> loop_point_; std::optional<std::size_t> loop_point_;

View file

@ -23,7 +23,8 @@ using namespace srb2::audio;
template <size_t C> template <size_t C>
Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio) Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio)
: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio) { : source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio)
{
} }
template <size_t C> template <size_t C>
@ -36,11 +37,13 @@ template <size_t C>
Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default; Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default;
template <size_t C> template <size_t C>
size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) { size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer)
{
if (!source_) if (!source_)
return 0; return 0;
if (ratio_ == 1.f) { if (ratio_ == 1.f)
{
// fast path - generate directly from source // fast path - generate directly from source
size_t source_read = source_->generate(buffer); size_t source_read = source_->generate(buffer);
return source_read; return source_read;
@ -48,21 +51,25 @@ size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) {
size_t written = 0; size_t written = 0;
while (written < buffer.size()) { while (written < buffer.size())
{
// do we need a refill? // do we need a refill?
if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1)) { if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1))
{
pos_ -= buf_.size(); pos_ -= buf_.size();
last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back(); last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back();
buf_.clear(); buf_.clear();
buf_.resize(512); buf_.resize(512);
size_t source_read = source_->generate(buf_); size_t source_read = source_->generate(buf_);
buf_.resize(source_read); buf_.resize(source_read);
if (source_read == 0) { if (source_read == 0)
{
break; break;
} }
} }
if (pos_ < 0) { if (pos_ < 0)
{
buffer[written] = (buf_[0] - last_) * pos_frac_ + last_; buffer[written] = (buf_[0] - last_) * pos_frac_ + last_;
advance(ratio_); advance(ratio_);
written++; written++;

View file

@ -21,10 +21,12 @@
#include "sound_chunk.hpp" #include "sound_chunk.hpp"
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class Resampler : public Source<C> { class Resampler : public Source<C>
{
public: public:
Resampler(std::shared_ptr<Source<C>>&& source_, float ratio); Resampler(std::shared_ptr<Source<C>>&& source_, float ratio);
Resampler(const Resampler<C>& r) = delete; Resampler(const Resampler<C>& r) = delete;
@ -44,7 +46,8 @@ private:
int pos_ {0}; int pos_ {0};
float pos_frac_ {0.f}; float pos_frac_ {0.f};
void advance(float samples) { void advance(float samples)
{
pos_frac_ += samples; pos_frac_ += samples;
float integer; float integer;
std::modf(pos_frac_, &integer); std::modf(pos_frac_, &integer);

View file

@ -12,21 +12,27 @@
#include <cstddef> #include <cstddef>
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
struct Sample { struct Sample
{
std::array<float, C> amplitudes; std::array<float, C> amplitudes;
constexpr Sample& operator+=(const Sample& rhs) noexcept { constexpr Sample& operator+=(const Sample& rhs) noexcept
for (std::size_t i = 0; i < C; i++) { {
for (std::size_t i = 0; i < C; i++)
{
amplitudes[i] += rhs.amplitudes[i]; amplitudes[i] += rhs.amplitudes[i];
} }
return *this; return *this;
} }
constexpr Sample& operator*=(float rhs) noexcept { constexpr Sample& operator*=(float rhs) noexcept
for (std::size_t i = 0; i < C; i++) { {
for (std::size_t i = 0; i < C; i++)
{
amplitudes[i] *= rhs; amplitudes[i] *= rhs;
} }
return *this; return *this;
@ -34,27 +40,33 @@ struct Sample {
}; };
template <size_t C> template <size_t C>
constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept { constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept
{
Sample<C> out; Sample<C> out;
for (std::size_t i = 0; i < C; i++) { for (std::size_t i = 0; i < C; i++)
{
out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i]; out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i];
} }
return out; return out;
} }
template <size_t C> template <size_t C>
constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept { constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept
{
Sample<C> out; Sample<C> out;
for (std::size_t i = 0; i < C; i++) { for (std::size_t i = 0; i < C; i++)
{
out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i]; out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i];
} }
return out; return out;
} }
template <size_t C> template <size_t C>
constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept { constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept
{
Sample<C> out; Sample<C> out;
for (std::size_t i = 0; i < C; i++) { for (std::size_t i = 0; i < C; i++)
{
out.amplitudes[i] = lhs.amplitudes[i] * rhs; out.amplitudes[i] = lhs.amplitudes[i] * rhs;
} }
return out; return out;
@ -64,12 +76,14 @@ template <class T>
static constexpr float sample_to_float(T sample) noexcept; static constexpr float sample_to_float(T sample) noexcept;
template <> template <>
constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept { constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept
{
return (sample / 128.f) - 1.f; return (sample / 128.f) - 1.f;
} }
template <> template <>
constexpr float sample_to_float<int16_t>(int16_t sample) noexcept { constexpr float sample_to_float<int16_t>(int16_t sample) noexcept
{
return sample / 32768.f; return sample / 32768.f;
} }

View file

@ -14,9 +14,11 @@
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
struct SoundChunk { struct SoundChunk
{
std::vector<Sample<1>> samples; std::vector<Sample<1>> samples;
}; };

View file

@ -20,15 +20,18 @@ using srb2::audio::Sample;
using srb2::audio::SoundEffectPlayer; using srb2::audio::SoundEffectPlayer;
using srb2::audio::Source; using srb2::audio::Source;
size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) { size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer)
{
if (!chunk_) if (!chunk_)
return 0; return 0;
if (position_ >= chunk_->samples.size()) { if (position_ >= chunk_->samples.size())
{
return 0; return 0;
} }
size_t written = 0; size_t written = 0;
for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++) { for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++)
{
float mono_sample = chunk_->samples[position_].amplitudes[0]; float mono_sample = chunk_->samples[position_].amplitudes[0];
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f); float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
@ -41,23 +44,27 @@ size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) {
return written; return written;
} }
void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep) { void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep)
{
this->update(volume, sep); this->update(volume, sep);
position_ = 0; position_ = 0;
chunk_ = chunk; chunk_ = chunk;
} }
void SoundEffectPlayer::update(float volume, float sep) { void SoundEffectPlayer::update(float volume, float sep)
{
volume_ = volume; volume_ = volume;
sep_ = sep; sep_ = sep;
} }
void SoundEffectPlayer::reset() { void SoundEffectPlayer::reset()
{
position_ = 0; position_ = 0;
chunk_ = nullptr; chunk_ = nullptr;
} }
bool SoundEffectPlayer::finished() const { bool SoundEffectPlayer::finished() const
{
if (!chunk_) if (!chunk_)
return true; return true;
if (position_ >= chunk_->samples.size()) if (position_ >= chunk_->samples.size())
@ -65,7 +72,8 @@ bool SoundEffectPlayer::finished() const {
return false; return false;
} }
bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const { bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const
{
return chunk_ == chunk; return chunk_ == chunk;
} }

View file

@ -17,9 +17,11 @@
#include "sound_chunk.hpp" #include "sound_chunk.hpp"
#include "source.hpp" #include "source.hpp"
namespace srb2::audio { namespace srb2::audio
{
class SoundEffectPlayer : public Source<2> { class SoundEffectPlayer : public Source<2>
{
public: public:
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final; virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final;

View file

@ -16,10 +16,12 @@
#include "sample.hpp" #include "sample.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class Source { class Source
{
public: public:
virtual std::size_t generate(tcb::span<Sample<C>> buffer) = 0; virtual std::size_t generate(tcb::span<Sample<C>> buffer) = 0;

View file

@ -18,7 +18,8 @@
using namespace srb2; using namespace srb2;
using srb2::audio::Wav; using srb2::audio::Wav;
namespace { namespace
{
constexpr const uint32_t kMagicRIFF = 0x46464952; constexpr const uint32_t kMagicRIFF = 0x46464952;
constexpr const uint32_t kMagicWAVE = 0x45564157; constexpr const uint32_t kMagicWAVE = 0x45564157;
@ -29,17 +30,20 @@ constexpr const uint16_t kFormatPcm = 1;
constexpr const std::size_t kRiffHeaderLength = 8; constexpr const std::size_t kRiffHeaderLength = 8;
struct RiffHeader { struct RiffHeader
{
uint32_t magic; uint32_t magic;
std::size_t filesize; std::size_t filesize;
}; };
struct TagHeader { struct TagHeader
{
uint32_t type; uint32_t type;
std::size_t length; std::size_t length;
}; };
struct FmtTag { struct FmtTag
{
uint16_t format; uint16_t format;
uint16_t channels; uint16_t channels;
uint32_t rate; uint32_t rate;
@ -48,9 +52,12 @@ struct FmtTag {
uint16_t bit_width; uint16_t bit_width;
}; };
struct DataTag {}; struct DataTag
{
};
RiffHeader parse_riff_header(io::SpanStream& stream) { RiffHeader parse_riff_header(io::SpanStream& stream)
{
if (io::remaining(stream) < kRiffHeaderLength) if (io::remaining(stream) < kRiffHeaderLength)
throw std::runtime_error("insufficient bytes remaining in stream"); throw std::runtime_error("insufficient bytes remaining in stream");
@ -60,7 +67,8 @@ RiffHeader parse_riff_header(io::SpanStream& stream) {
return ret; return ret;
} }
TagHeader parse_tag_header(io::SpanStream& stream) { TagHeader parse_tag_header(io::SpanStream& stream)
{
if (io::remaining(stream) < 8) if (io::remaining(stream) < 8)
throw std::runtime_error("insufficient bytes remaining in stream"); throw std::runtime_error("insufficient bytes remaining in stream");
@ -70,7 +78,8 @@ TagHeader parse_tag_header(io::SpanStream& stream) {
return header; return header;
} }
FmtTag parse_fmt_tag(io::SpanStream& stream) { FmtTag parse_fmt_tag(io::SpanStream& stream)
{
if (io::remaining(stream) < 16) if (io::remaining(stream) < 16)
throw std::runtime_error("insufficient bytes in stream"); throw std::runtime_error("insufficient bytes in stream");
@ -86,14 +95,16 @@ FmtTag parse_fmt_tag(io::SpanStream& stream) {
} }
template <typename Visitor> template <typename Visitor>
void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header) { void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header)
{
if (io::remaining(stream) < header.length) if (io::remaining(stream) < header.length)
throw std::runtime_error("insufficient bytes in stream"); throw std::runtime_error("insufficient bytes in stream");
const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0); const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0);
const io::StreamSize dest = start + header.length; const io::StreamSize dest = start + header.length;
switch (header.type) { switch (header.type)
{
case kMagicFmt: case kMagicFmt:
{ {
FmtTag fmt_tag {parse_fmt_tag(stream)}; FmtTag fmt_tag {parse_fmt_tag(stream)};
@ -114,19 +125,23 @@ void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header
stream.seek(io::SeekFrom::kStart, dest); stream.seek(io::SeekFrom::kStart, dest);
} }
std::vector<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { std::vector<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count)
{
std::vector<uint8_t> samples; std::vector<uint8_t> samples;
samples.reserve(count); samples.reserve(count);
for (std::size_t i = 0; i < count; i++) { for (std::size_t i = 0; i < count; i++)
{
samples.push_back(io::read_uint8(stream)); samples.push_back(io::read_uint8(stream));
} }
return samples; return samples;
} }
std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count)
{
std::vector<int16_t> samples; std::vector<int16_t> samples;
samples.reserve(count); samples.reserve(count);
for (std::size_t i = 0; i < count; i++) { for (std::size_t i = 0; i < count; i++)
{
samples.push_back(io::read_int16(stream)); samples.push_back(io::read_int16(stream));
} }
return samples; return samples;
@ -136,57 +151,70 @@ std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std:
Wav::Wav() = default; Wav::Wav() = default;
Wav::Wav(tcb::span<std::byte> data) { Wav::Wav(tcb::span<std::byte> data)
{
io::SpanStream stream {data}; io::SpanStream stream {data};
auto [magic, filesize] = parse_riff_header(stream); auto [magic, filesize] = parse_riff_header(stream);
if (magic != kMagicRIFF) { if (magic != kMagicRIFF)
{
throw std::runtime_error("invalid RIFF magic"); throw std::runtime_error("invalid RIFF magic");
} }
if (io::remaining(stream) < filesize) { if (io::remaining(stream) < filesize)
{
throw std::runtime_error("insufficient data in stream for RIFF's reported filesize"); throw std::runtime_error("insufficient data in stream for RIFF's reported filesize");
} }
const io::StreamSize riff_end = stream.seek(io::SeekFrom::kCurrent, 0) + filesize; const io::StreamSize riff_end = stream.seek(io::SeekFrom::kCurrent, 0) + filesize;
uint32_t type = io::read_uint32(stream); uint32_t type = io::read_uint32(stream);
if (type != kMagicWAVE) { if (type != kMagicWAVE)
{
throw std::runtime_error("RIFF in stream is not a WAVE"); throw std::runtime_error("RIFF in stream is not a WAVE");
} }
std::optional<FmtTag> read_fmt; std::optional<FmtTag> read_fmt;
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples; std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples;
while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end)
{
TagHeader tag_header {parse_tag_header(stream)}; TagHeader tag_header {parse_tag_header(stream)};
if (io::remaining(stream) < tag_header.length) { if (io::remaining(stream) < tag_header.length)
{
throw std::runtime_error("WAVE tag length exceeds stream length"); throw std::runtime_error("WAVE tag length exceeds stream length");
} }
auto tag_visitor = srb2::Overload { auto tag_visitor = srb2::Overload {
[&](const FmtTag& fmt) { [&](const FmtTag& fmt)
if (read_fmt) { {
if (read_fmt)
{
throw std::runtime_error("WAVE has multiple 'fmt' tags"); throw std::runtime_error("WAVE has multiple 'fmt' tags");
} }
if (fmt.format != kFormatPcm) { if (fmt.format != kFormatPcm)
{
throw std::runtime_error("Unsupported WAVE format (only PCM is supported)"); throw std::runtime_error("Unsupported WAVE format (only PCM is supported)");
} }
read_fmt = fmt; read_fmt = fmt;
}, },
[&](const DataTag& data) { [&](const DataTag& data)
if (!read_fmt) { {
if (!read_fmt)
{
throw std::runtime_error("unable to read data tag because no fmt tag was read"); throw std::runtime_error("unable to read data tag because no fmt tag was read");
} }
if (tag_header.length % read_fmt->bytes_per_sample != 0) { if (tag_header.length % read_fmt->bytes_per_sample != 0)
{
throw std::runtime_error("data tag length not divisible by bytes_per_sample"); throw std::runtime_error("data tag length not divisible by bytes_per_sample");
} }
const std::size_t sample_count = tag_header.length / read_fmt->bytes_per_sample; const std::size_t sample_count = tag_header.length / read_fmt->bytes_per_sample;
switch (read_fmt->bit_width) { switch (read_fmt->bit_width)
{
case 8: case 8:
interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count)); interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count));
break; break;
@ -201,7 +229,8 @@ Wav::Wav(tcb::span<std::byte> data) {
visit_tag(tag_visitor, stream, tag_header); visit_tag(tag_visitor, stream, tag_header);
} }
if (!read_fmt) { if (!read_fmt)
{
throw std::runtime_error("WAVE did not have a fmt tag"); throw std::runtime_error("WAVE did not have a fmt tag");
} }
@ -210,27 +239,34 @@ Wav::Wav(tcb::span<std::byte> data) {
sample_rate_ = read_fmt->rate; sample_rate_ = read_fmt->rate;
} }
namespace { namespace
{
template <typename T> template <typename T>
std::size_t read_samples(std::size_t channels, std::size_t read_samples(
std::size_t channels,
std::size_t offset, std::size_t offset,
const std::vector<T>& samples, const std::vector<T>& samples,
tcb::span<audio::Sample<1>> buffer) noexcept { tcb::span<audio::Sample<1>> buffer
) noexcept
{
const std::size_t offset_interleaved = offset * channels; const std::size_t offset_interleaved = offset * channels;
const std::size_t samples_size = samples.size(); const std::size_t samples_size = samples.size();
const std::size_t buffer_size = buffer.size(); const std::size_t buffer_size = buffer.size();
if (offset_interleaved >= samples_size) { if (offset_interleaved >= samples_size)
{
return 0; return 0;
} }
const std::size_t remainder = (samples_size - offset_interleaved) / channels; const std::size_t remainder = (samples_size - offset_interleaved) / channels;
const std::size_t samples_to_read = std::min(buffer_size, remainder); const std::size_t samples_to_read = std::min(buffer_size, remainder);
for (std::size_t i = 0; i < samples_to_read; i++) { for (std::size_t i = 0; i < samples_to_read; i++)
{
buffer[i].amplitudes[0] = 0.f; buffer[i].amplitudes[0] = 0.f;
for (std::size_t j = 0; j < channels; j++) { for (std::size_t j = 0; j < channels; j++)
{
buffer[i].amplitudes[0] += audio::sample_to_float(samples[i * channels + j + offset_interleaved]); buffer[i].amplitudes[0] += audio::sample_to_float(samples[i * channels + j + offset_interleaved]);
} }
buffer[i].amplitudes[0] /= static_cast<float>(channels); buffer[i].amplitudes[0] /= static_cast<float>(channels);
@ -241,18 +277,20 @@ std::size_t read_samples(std::size_t channels,
} // namespace } // namespace
std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept { std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept
{
auto samples_visitor = srb2::Overload { auto samples_visitor = srb2::Overload {
[&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); }, [&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); },
[&](const std::vector<int16_t>& samples) { [&](const std::vector<int16_t>& samples)
return read_samples<int16_t>(channels(), offset, samples, buffer); { return read_samples<int16_t>(channels(), offset, samples, buffer); }};
}};
return std::visit(samples_visitor, interleaved_samples_); return std::visit(samples_visitor, interleaved_samples_);
} }
std::size_t Wav::interleaved_length() const noexcept { std::size_t Wav::interleaved_length() const noexcept
auto samples_visitor = srb2::Overload {[](const std::vector<uint8_t>& samples) { return samples.size(); }, {
auto samples_visitor = srb2::Overload {
[](const std::vector<uint8_t>& samples) { return samples.size(); },
[](const std::vector<int16_t>& samples) { return samples.size(); }}; [](const std::vector<int16_t>& samples) { return samples.size(); }};
return std::visit(samples_visitor, interleaved_samples_); return std::visit(samples_visitor, interleaved_samples_);
} }

View file

@ -21,9 +21,11 @@
#include "../io/streams.hpp" #include "../io/streams.hpp"
#include "sample.hpp" #include "sample.hpp"
namespace srb2::audio { namespace srb2::audio
{
class Wav final { class Wav final
{
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_; std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_;
std::size_t channels_ = 1; std::size_t channels_ = 1;
std::size_t sample_rate_ = 44100; std::size_t sample_rate_ = 44100;
@ -41,7 +43,8 @@ public:
}; };
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Wav load_wav(I& stream) { inline Wav load_wav(I& stream)
{
std::vector<std::byte> data = srb2::io::read_to_vec(stream); std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Wav {data}; return Wav {data};
} }

View file

@ -13,7 +13,8 @@ using namespace srb2;
using srb2::audio::WavPlayer; using srb2::audio::WavPlayer;
WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) { WavPlayer::WavPlayer() : WavPlayer(audio::Wav {})
{
} }
WavPlayer::WavPlayer(const WavPlayer& rhs) = default; WavPlayer::WavPlayer(const WavPlayer& rhs) = default;
@ -24,20 +25,25 @@ WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default;
WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default; WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default;
WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward<Wav>(wav)), position_(0), looping_(false) { WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward<Wav>(wav)), position_(0), looping_(false)
{
} }
std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> buffer) { std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> buffer)
{
std::size_t samples_read = 0; std::size_t samples_read = 0;
while (samples_read < buffer.size()) { while (samples_read < buffer.size())
{
const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read)); const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read));
position_ += read_this_time; position_ += read_this_time;
samples_read += read_this_time; samples_read += read_this_time;
if (position_ > wav_.length() && looping_) { if (position_ > wav_.length() && looping_)
{
position_ = 0; position_ = 0;
} }
if (read_this_time == 0 && !looping_) { if (read_this_time == 0 && !looping_)
{
break; break;
} }
} }

View file

@ -17,9 +17,11 @@
#include "source.hpp" #include "source.hpp"
#include "wav.hpp" #include "wav.hpp"
namespace srb2::audio { namespace srb2::audio
{
class WavPlayer final : public Source<1> { class WavPlayer final : public Source<1>
{
Wav wav_; Wav wav_;
std::size_t position_; std::size_t position_;
bool looping_; bool looping_;

View file

@ -16,11 +16,14 @@
using namespace srb2; using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
XmpException::XmpException(int code) : code_(code) { XmpException::XmpException(int code) : code_(code)
{
} }
const char* XmpException::what() const noexcept { const char* XmpException::what() const noexcept
switch (code_) { {
switch (code_)
{
case -XMP_ERROR_INTERNAL: case -XMP_ERROR_INTERNAL:
return "XMP_ERROR_INTERNAL"; return "XMP_ERROR_INTERNAL";
case -XMP_ERROR_FORMAT: case -XMP_ERROR_FORMAT:
@ -41,23 +44,27 @@ const char* XmpException::what() const noexcept {
} }
template <size_t C> template <size_t C>
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) { Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false)
{
} }
template <size_t C> template <size_t C>
Xmp<C>::Xmp(std::vector<std::byte> data) Xmp<C>::Xmp(std::vector<std::byte> data)
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false)
{
_init(); _init();
} }
template <size_t C> template <size_t C>
Xmp<C>::Xmp(tcb::span<std::byte> data) Xmp<C>::Xmp(tcb::span<std::byte> data)
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) { : data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false)
{
_init(); _init();
} }
template <size_t C> template <size_t C>
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() { Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>()
{
std::swap(data_, rhs.data_); std::swap(data_, rhs.data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
std::swap(module_loaded_, rhs.module_loaded_); std::swap(module_loaded_, rhs.module_loaded_);
@ -65,7 +72,8 @@ Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
} }
template <size_t C> template <size_t C>
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept { Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept
{
std::swap(data_, rhs.data_); std::swap(data_, rhs.data_);
std::swap(instance_, rhs.instance_); std::swap(instance_, rhs.instance_);
std::swap(module_loaded_, rhs.module_loaded_); std::swap(module_loaded_, rhs.module_loaded_);
@ -75,15 +83,18 @@ Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept {
}; };
template <size_t C> template <size_t C>
Xmp<C>::~Xmp() { Xmp<C>::~Xmp()
if (instance_) { {
if (instance_)
{
xmp_free_context(instance_); xmp_free_context(instance_);
instance_ = nullptr; instance_ = nullptr;
} }
} }
template <size_t C> template <size_t C>
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) { std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true); SRB2_ASSERT(module_loaded_ == true);
@ -99,7 +110,8 @@ std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) {
} }
template <size_t C> template <size_t C>
void Xmp<C>::reset() { void Xmp<C>::reset()
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true); SRB2_ASSERT(module_loaded_ == true);
@ -107,7 +119,8 @@ void Xmp<C>::reset() {
} }
template <size_t C> template <size_t C>
float Xmp<C>::duration_seconds() const { float Xmp<C>::duration_seconds() const
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true); SRB2_ASSERT(module_loaded_ == true);
@ -117,7 +130,8 @@ float Xmp<C>::duration_seconds() const {
} }
template <size_t C> template <size_t C>
void Xmp<C>::seek(int position_ms) { void Xmp<C>::seek(int position_ms)
{
SRB2_ASSERT(instance_ != nullptr); SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true); SRB2_ASSERT(module_loaded_ == true);
@ -127,7 +141,8 @@ void Xmp<C>::seek(int position_ms) {
} }
template <size_t C> template <size_t C>
void Xmp<C>::_init() { void Xmp<C>::_init()
{
if (instance_) if (instance_)
return; return;
@ -137,12 +152,14 @@ void Xmp<C>::_init() {
throw std::logic_error("Insufficient data from stream"); throw std::logic_error("Insufficient data from stream");
instance_ = xmp_create_context(); instance_ = xmp_create_context();
if (instance_ == nullptr) { if (instance_ == nullptr)
{
throw std::bad_alloc(); throw std::bad_alloc();
} }
int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size()); int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size());
if (result != 0) { if (result != 0)
{
xmp_free_context(instance_); xmp_free_context(instance_);
instance_ = nullptr; instance_ = nullptr;
throw XmpException(result); throw XmpException(result);
@ -150,11 +167,13 @@ void Xmp<C>::_init() {
module_loaded_ = true; module_loaded_ = true;
int flags = 0; int flags = 0;
if constexpr (C == 1) { if constexpr (C == 1)
{
flags |= XMP_FORMAT_MONO; flags |= XMP_FORMAT_MONO;
} }
result = xmp_start_player(instance_, 44100, flags); result = xmp_start_player(instance_, 44100, flags);
if (result != 0) { if (result != 0)
{
xmp_release_module(instance_); xmp_release_module(instance_);
module_loaded_ = false; module_loaded_ = false;
xmp_free_context(instance_); xmp_free_context(instance_);

View file

@ -22,9 +22,11 @@
#include "../io/streams.hpp" #include "../io/streams.hpp"
namespace srb2::audio { namespace srb2::audio
{
class XmpException : public std::exception { class XmpException : public std::exception
{
int code_; int code_;
public: public:
@ -33,7 +35,8 @@ public:
}; };
template <size_t C> template <size_t C>
class Xmp final { class Xmp final
{
std::vector<std::byte> data_; std::vector<std::byte> data_;
xmp_context instance_; xmp_context instance_;
bool module_loaded_; bool module_loaded_;
@ -68,7 +71,8 @@ extern template class Xmp<1>;
extern template class Xmp<2>; extern template class Xmp<2>;
template <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> template <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Xmp<C> load_xmp(I& stream) { inline Xmp<C> load_xmp(I& stream)
{
std::vector<std::byte> data = srb2::io::read_to_vec(stream); std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Xmp<C> {std::move(data)}; return Xmp<C> {std::move(data)};
} }

View file

@ -15,7 +15,8 @@ using namespace srb2;
using namespace srb2::audio; using namespace srb2::audio;
template <size_t C> template <size_t C>
XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_() { XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_()
{
} }
template <size_t C> template <size_t C>
@ -28,14 +29,17 @@ template <size_t C>
XmpPlayer<C>::~XmpPlayer() = default; XmpPlayer<C>::~XmpPlayer() = default;
template <size_t C> template <size_t C>
std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) { std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer)
{
buf_.resize(buffer.size()); buf_.resize(buffer.size());
std::size_t read = xmp_.play_buffer(tcb::make_span(buf_)); std::size_t read = xmp_.play_buffer(tcb::make_span(buf_));
buf_.resize(read); buf_.resize(read);
std::size_t ret = std::min(buffer.size(), buf_.size()); std::size_t ret = std::min(buffer.size(), buf_.size());
for (std::size_t i = 0; i < ret; i++) { for (std::size_t i = 0; i < ret; i++)
for (std::size_t j = 0; j < C; j++) { {
for (std::size_t j = 0; j < C; j++)
{
buffer[i].amplitudes[j] = buf_[i][j] / 32768.f; buffer[i].amplitudes[j] = buf_[i][j] / 32768.f;
} }
} }
@ -44,12 +48,14 @@ std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
} }
template <size_t C> template <size_t C>
float XmpPlayer<C>::duration_seconds() const { float XmpPlayer<C>::duration_seconds() const
{
return xmp_.duration_seconds(); return xmp_.duration_seconds();
} }
template <size_t C> template <size_t C>
void XmpPlayer<C>::seek(float position_seconds) { void XmpPlayer<C>::seek(float position_seconds)
{
xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f))); xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f)));
} }

View file

@ -13,10 +13,12 @@
#include "source.hpp" #include "source.hpp"
#include "xmp.hpp" #include "xmp.hpp"
namespace srb2::audio { namespace srb2::audio
{
template <size_t C> template <size_t C>
class XmpPlayer final : public Source<C> { class XmpPlayer final : public Source<C>
{
Xmp<C> xmp_; Xmp<C> xmp_;
std::vector<std::array<int16_t, C>> buf_; std::vector<std::array<int16_t, C>> buf_;

View file

@ -62,10 +62,12 @@ struct SourceLocation {
class IErrorAssertHandler { class IErrorAssertHandler {
public: public:
static void handle(const SourceLocation& source_location, const char* expression) { static void handle(const SourceLocation& source_location, const char* expression) {
I_Error("Assertion failed at %s:%u: %s != true", I_Error(
"Assertion failed at %s:%u: %s != true",
source_location.file_name, source_location.file_name,
source_location.line_number, source_location.line_number,
expression); expression
);
} }
}; };
@ -102,8 +104,10 @@ class NotNull final {
T ptr_; T ptr_;
public: public:
static_assert(std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>, static_assert(
"T is not comparable with nullptr_t"); std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>,
"T is not comparable with nullptr_t"
);
/// @brief Move-construct from the pointer value U, asserting that it is not null. Allows construction of a /// @brief Move-construct from the pointer value U, asserting that it is not null. Allows construction of a
/// NotNull<T> from any compatible pointer U, for example with polymorphic classes. /// NotNull<T> from any compatible pointer U, for example with polymorphic classes.

View file

@ -59,7 +59,8 @@ static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
static void (*music_fade_callback)(); static void (*music_fade_callback)();
void* I_GetSfx(sfxinfo_t* sfx) { void* I_GetSfx(sfxinfo_t* sfx)
{
if (sfx->lumpnum == LUMPERROR) if (sfx->lumpnum == LUMPERROR)
sfx->lumpnum = S_GetSfxLumpNum(sfx); sfx->lumpnum = S_GetSfxLumpNum(sfx);
sfx->length = W_LumpLength(sfx->lumpnum); sfx->length = W_LumpLength(sfx->lumpnum);
@ -78,14 +79,18 @@ void* I_GetSfx(sfxinfo_t* sfx) {
return heap_chunk; return heap_chunk;
} }
void I_FreeSfx(sfxinfo_t* sfx) { void I_FreeSfx(sfxinfo_t* sfx)
if (sfx->data) { {
if (sfx->data)
{
SoundChunk* chunk = static_cast<SoundChunk*>(sfx->data); SoundChunk* chunk = static_cast<SoundChunk*>(sfx->data);
auto _ = srb2::finally([chunk]() { delete chunk; }); auto _ = srb2::finally([chunk]() { delete chunk; });
// Stop any channels playing this chunk // Stop any channels playing this chunk
for (auto& player : sound_effect_channels) { for (auto& player : sound_effect_channels)
if (player->is_playing_chunk(chunk)) { {
if (player->is_playing_chunk(chunk))
{
player->reset(); player->reset();
} }
} }
@ -94,22 +99,27 @@ void I_FreeSfx(sfxinfo_t* sfx) {
sfx->lumpnum = LUMPERROR; sfx->lumpnum = LUMPERROR;
} }
namespace { namespace
{
class SdlAudioLockHandle { class SdlAudioLockHandle
{
public: public:
SdlAudioLockHandle() { SDL_LockAudio(); } SdlAudioLockHandle() { SDL_LockAudio(); }
~SdlAudioLockHandle() { SDL_UnlockAudio(); } ~SdlAudioLockHandle() { SDL_UnlockAudio(); }
}; };
void audio_callback(void* userdata, Uint8* buffer, int len) { void audio_callback(void* userdata, Uint8* buffer, int len)
{
// The SDL Audio lock is implied to be held during callback. // The SDL Audio lock is implied to be held during callback.
try { try
{
Sample<2>* float_buffer = reinterpret_cast<Sample<2>*>(buffer); Sample<2>* float_buffer = reinterpret_cast<Sample<2>*>(buffer);
size_t float_len = len / 8; size_t float_len = len / 8;
for (size_t i = 0; i < float_len; i++) { for (size_t i = 0; i < float_len; i++)
{
float_buffer[i] = Sample<2> {0.f, 0.f}; float_buffer[i] = Sample<2> {0.f, 0.f};
} }
@ -118,20 +128,25 @@ void audio_callback(void* userdata, Uint8* buffer, int len) {
master->generate(tcb::span {float_buffer, float_len}); master->generate(tcb::span {float_buffer, float_len});
for (size_t i = 0; i < float_len; i++) { for (size_t i = 0; i < float_len; i++)
{
float_buffer[i] = { float_buffer[i] = {
std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f), std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f),
std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f), std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f),
}; };
} }
} catch (...) { }
catch (...)
{
} }
return; return;
} }
void initialize_sound() { void initialize_sound()
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError()); CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
return; return;
} }
@ -143,7 +158,8 @@ void initialize_sound() {
desired.freq = 44100; desired.freq = 44100;
desired.callback = audio_callback; desired.callback = audio_callback;
if (SDL_OpenAudio(&desired, NULL) < 0) { if (SDL_OpenAudio(&desired, NULL) < 0)
{
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError()); CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
return; return;
@ -165,7 +181,8 @@ void initialize_sound() {
master->add_source(gain_sound_effects); master->add_source(gain_sound_effects);
master->add_source(gain_music); master->add_source(gain_music);
mixer_music->add_source(music_player); mixer_music->add_source(music_player);
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++) { for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++)
{
shared_ptr<SoundEffectPlayer> player = make_shared<SoundEffectPlayer>(); shared_ptr<SoundEffectPlayer> player = make_shared<SoundEffectPlayer>();
sound_effect_channels.push_back(player); sound_effect_channels.push_back(player);
mixer_sound_effects->add_source(player); mixer_sound_effects->add_source(player);
@ -177,26 +194,31 @@ void initialize_sound() {
} // namespace } // namespace
void I_StartupSound(void) { void I_StartupSound(void)
{
if (!sound_started) if (!sound_started)
initialize_sound(); initialize_sound();
} }
void I_ShutdownSound(void) { void I_ShutdownSound(void)
{
SdlAudioLockHandle _; SdlAudioLockHandle _;
for (auto& channel : sound_effect_channels) { for (auto& channel : sound_effect_channels)
{
if (channel) if (channel)
*channel = audio::SoundEffectPlayer(); *channel = audio::SoundEffectPlayer();
} }
} }
void I_UpdateSound(void) { void I_UpdateSound(void)
{
// The SDL audio lock is re-entrant, so it is safe to lock twice // The SDL audio lock is re-entrant, so it is safe to lock twice
// for the "fade to stop music" callback later. // for the "fade to stop music" callback later.
SdlAudioLockHandle _; SdlAudioLockHandle _;
if (music_fade_callback && !music_player->fading()) { if (music_fade_callback && !music_player->fading())
{
auto old_callback = music_fade_callback; auto old_callback = music_fade_callback;
music_fade_callback = nullptr; music_fade_callback = nullptr;
(old_callback()); (old_callback());
@ -208,7 +230,8 @@ void I_UpdateSound(void) {
// SFX I/O // SFX I/O
// //
INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel) { INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
{
(void) pitch; (void) pitch;
(void) priority; (void) priority;
@ -218,16 +241,21 @@ INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priori
return -1; return -1;
shared_ptr<SoundEffectPlayer> player_channel; shared_ptr<SoundEffectPlayer> player_channel;
if (channel < 0) { if (channel < 0)
{
// find a free sfx channel // find a free sfx channel
for (size_t i = 0; i < sound_effect_channels.size(); i++) { for (size_t i = 0; i < sound_effect_channels.size(); i++)
if (sound_effect_channels[i]->finished()) { {
if (sound_effect_channels[i]->finished())
{
player_channel = sound_effect_channels[i]; player_channel = sound_effect_channels[i];
channel = i; channel = i;
break; break;
} }
} }
} else { }
else
{
player_channel = sound_effect_channels[channel]; player_channel = sound_effect_channels[channel];
} }
@ -246,7 +274,8 @@ INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priori
return channel; return channel;
} }
void I_StopSound(INT32 handle) { void I_StopSound(INT32 handle)
{
SdlAudioLockHandle _; SdlAudioLockHandle _;
if (sound_effect_channels.empty()) if (sound_effect_channels.empty())
@ -263,7 +292,8 @@ void I_StopSound(INT32 handle) {
sound_effect_channels[index]->reset(); sound_effect_channels[index]->reset();
} }
boolean I_SoundIsPlaying(INT32 handle) { boolean I_SoundIsPlaying(INT32 handle)
{
SdlAudioLockHandle _; SdlAudioLockHandle _;
// Handle is channel index // Handle is channel index
@ -281,7 +311,8 @@ boolean I_SoundIsPlaying(INT32 handle) {
return sound_effect_channels[index]->finished() ? 0 : 1; return sound_effect_channels[index]->finished() ? 0 : 1;
} }
void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) { void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch)
{
(void) pitch; (void) pitch;
SdlAudioLockHandle _; SdlAudioLockHandle _;
@ -298,18 +329,21 @@ void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) {
return; return;
shared_ptr<SoundEffectPlayer>& channel = sound_effect_channels[index]; shared_ptr<SoundEffectPlayer>& channel = sound_effect_channels[index];
if (!channel->finished()) { if (!channel->finished())
{
float vol_float = static_cast<float>(vol) / 255.f; float vol_float = static_cast<float>(vol) / 255.f;
float sep_float = static_cast<float>(sep) / 127.f - 1.f; float sep_float = static_cast<float>(sep) / 127.f - 1.f;
channel->update(vol_float, sep_float); channel->update(vol_float, sep_float);
} }
} }
void I_SetSfxVolume(int volume) { void I_SetSfxVolume(int volume)
{
SdlAudioLockHandle _; SdlAudioLockHandle _;
float vol = static_cast<float>(volume) / 100.f; float vol = static_cast<float>(volume) / 100.f;
if (gain_sound_effects) { if (gain_sound_effects)
{
gain_sound_effects->gain(vol * vol * vol); gain_sound_effects->gain(vol * vol * vol);
} }
} }
@ -318,7 +352,8 @@ void I_SetSfxVolume(int volume) {
// MUSIC SYSTEM // MUSIC SYSTEM
/// ------------------------ /// ------------------------
void I_InitMusic(void) { void I_InitMusic(void)
{
if (!sound_started) if (!sound_started)
initialize_sound(); initialize_sound();
@ -327,7 +362,8 @@ void I_InitMusic(void) {
*music_player = audio::MusicPlayer(); *music_player = audio::MusicPlayer();
} }
void I_ShutdownMusic(void) { void I_ShutdownMusic(void)
{
SdlAudioLockHandle _; SdlAudioLockHandle _;
if (music_player) if (music_player)
@ -338,7 +374,8 @@ void I_ShutdownMusic(void) {
// MUSIC PROPERTIES // MUSIC PROPERTIES
/// ------------------------ /// ------------------------
musictype_t I_SongType(void) { musictype_t I_SongType(void)
{
if (!music_player) if (!music_player)
return MU_NONE; return MU_NONE;
@ -346,11 +383,13 @@ musictype_t I_SongType(void) {
std::optional<audio::MusicType> music_type = music_player->music_type(); std::optional<audio::MusicType> music_type = music_player->music_type();
if (music_type == std::nullopt) { if (music_type == std::nullopt)
{
return MU_NONE; return MU_NONE;
} }
switch (*music_type) { switch (*music_type)
{
case audio::MusicType::kOgg: case audio::MusicType::kOgg:
return MU_OGG; return MU_OGG;
case audio::MusicType::kGme: case audio::MusicType::kGme:
@ -362,7 +401,8 @@ musictype_t I_SongType(void) {
} }
} }
boolean I_SongPlaying(void) { boolean I_SongPlaying(void)
{
if (!music_player) if (!music_player)
return false; return false;
@ -371,7 +411,8 @@ boolean I_SongPlaying(void) {
return music_player->music_type().has_value(); return music_player->music_type().has_value();
} }
boolean I_SongPaused(void) { boolean I_SongPaused(void)
{
if (!music_player) if (!music_player)
return false; return false;
@ -384,7 +425,8 @@ boolean I_SongPaused(void) {
// MUSIC EFFECTS // MUSIC EFFECTS
/// ------------------------ /// ------------------------
boolean I_SetSongSpeed(float speed) { boolean I_SetSongSpeed(float speed)
{
(void) speed; (void) speed;
return false; return false;
} }
@ -393,7 +435,8 @@ boolean I_SetSongSpeed(float speed) {
// MUSIC SEEKING // MUSIC SEEKING
/// ------------------------ /// ------------------------
UINT32 I_GetSongLength(void) { UINT32 I_GetSongLength(void)
{
if (!music_player) if (!music_player)
return 0; return 0;
@ -407,13 +450,15 @@ UINT32 I_GetSongLength(void) {
return static_cast<UINT32>(std::round(*duration * 1000.f)); return static_cast<UINT32>(std::round(*duration * 1000.f));
} }
boolean I_SetSongLoopPoint(UINT32 looppoint) { boolean I_SetSongLoopPoint(UINT32 looppoint)
{
if (!music_player) if (!music_player)
return 0; return 0;
SdlAudioLockHandle _; SdlAudioLockHandle _;
if (music_player->music_type() == audio::MusicType::kOgg) { if (music_player->music_type() == audio::MusicType::kOgg)
{
music_player->loop_point_seconds(looppoint / 1000.f); music_player->loop_point_seconds(looppoint / 1000.f);
return true; return true;
} }
@ -421,7 +466,8 @@ boolean I_SetSongLoopPoint(UINT32 looppoint) {
return false; return false;
} }
UINT32 I_GetSongLoopPoint(void) { UINT32 I_GetSongLoopPoint(void)
{
if (!music_player) if (!music_player)
return 0; return 0;
@ -435,7 +481,8 @@ UINT32 I_GetSongLoopPoint(void) {
return static_cast<UINT32>(std::round(*loop_point_seconds * 1000.f)); return static_cast<UINT32>(std::round(*loop_point_seconds * 1000.f));
} }
boolean I_SetSongPosition(UINT32 position) { boolean I_SetSongPosition(UINT32 position)
{
if (!music_player) if (!music_player)
return false; return false;
@ -445,7 +492,8 @@ boolean I_SetSongPosition(UINT32 position) {
return true; return true;
} }
UINT32 I_GetSongPosition(void) { UINT32 I_GetSongPosition(void)
{
if (!music_player) if (!music_player)
return 0; return 0;
@ -459,50 +507,66 @@ UINT32 I_GetSongPosition(void) {
return static_cast<UINT32>(std::round(*position_seconds * 1000.f)); return static_cast<UINT32>(std::round(*position_seconds * 1000.f));
} }
void I_UpdateSongLagThreshold(void) { void I_UpdateSongLagThreshold(void)
{
} }
void I_UpdateSongLagConditions(void) { void I_UpdateSongLagConditions(void)
{
} }
/// ------------------------ /// ------------------------
// MUSIC PLAYBACK // MUSIC PLAYBACK
/// ------------------------ /// ------------------------
namespace { namespace
void print_walk_ex_stack(const std::exception& ex) { {
void print_walk_ex_stack(const std::exception& ex)
{
CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what()); CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what());
try { try
{
std::rethrow_if_nested(ex); std::rethrow_if_nested(ex);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
print_walk_ex_stack(ex); print_walk_ex_stack(ex);
} }
} }
void print_ex(const std::exception& ex) { void print_ex(const std::exception& ex)
{
CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what()); CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what());
try { try
{
std::rethrow_if_nested(ex); std::rethrow_if_nested(ex);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
print_walk_ex_stack(ex); print_walk_ex_stack(ex);
} }
} }
} // namespace } // namespace
boolean I_LoadSong(char* data, size_t len) { boolean I_LoadSong(char* data, size_t len)
{
if (!music_player) if (!music_player)
return false; return false;
tcb::span<std::byte> data_span(reinterpret_cast<std::byte*>(data), len); tcb::span<std::byte> data_span(reinterpret_cast<std::byte*>(data), len);
audio::MusicPlayer new_player; audio::MusicPlayer new_player;
try { try
{
new_player = audio::MusicPlayer {data_span}; new_player = audio::MusicPlayer {data_span};
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
print_ex(ex); print_ex(ex);
return false; return false;
} }
if (music_fade_callback && music_player->fading()) { if (music_fade_callback && music_player->fading())
{
auto old_callback = music_fade_callback; auto old_callback = music_fade_callback;
music_fade_callback = nullptr; music_fade_callback = nullptr;
(old_callback)(); (old_callback)();
@ -510,9 +574,12 @@ boolean I_LoadSong(char* data, size_t len) {
SdlAudioLockHandle _; SdlAudioLockHandle _;
try { try
{
*music_player = std::move(new_player); *music_player = std::move(new_player);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
print_ex(ex); print_ex(ex);
return false; return false;
} }
@ -520,11 +587,13 @@ boolean I_LoadSong(char* data, size_t len) {
return true; return true;
} }
void I_UnloadSong(void) { void I_UnloadSong(void)
{
if (!music_player) if (!music_player)
return; return;
if (music_fade_callback && music_player->fading()) { if (music_fade_callback && music_player->fading())
{
auto old_callback = music_fade_callback; auto old_callback = music_fade_callback;
music_fade_callback = nullptr; music_fade_callback = nullptr;
(old_callback)(); (old_callback)();
@ -535,7 +604,8 @@ void I_UnloadSong(void) {
*music_player = audio::MusicPlayer(); *music_player = audio::MusicPlayer();
} }
boolean I_PlaySong(boolean looping) { boolean I_PlaySong(boolean looping)
{
if (!music_player) if (!music_player)
return false; return false;
@ -546,7 +616,8 @@ boolean I_PlaySong(boolean looping) {
return true; return true;
} }
void I_StopSong(void) { void I_StopSong(void)
{
if (!music_player) if (!music_player)
return; return;
@ -555,7 +626,8 @@ void I_StopSong(void) {
music_player->stop(); music_player->stop();
} }
void I_PauseSong(void) { void I_PauseSong(void)
{
if (!music_player) if (!music_player)
return; return;
@ -564,7 +636,8 @@ void I_PauseSong(void) {
music_player->pause(); music_player->pause();
} }
void I_ResumeSong(void) { void I_ResumeSong(void)
{
if (!music_player) if (!music_player)
return; return;
@ -573,15 +646,18 @@ void I_ResumeSong(void) {
music_player->unpause(); music_player->unpause();
} }
void I_SetMusicVolume(int volume) { void I_SetMusicVolume(int volume)
{
float vol = static_cast<float>(volume) / 100.f; float vol = static_cast<float>(volume) / 100.f;
if (gain_music) { if (gain_music)
{
gain_music->gain(vol * vol * vol); gain_music->gain(vol * vol * vol);
} }
} }
boolean I_SetSongTrack(int track) { boolean I_SetSongTrack(int track)
{
(void) track; (void) track;
return false; return false;
} }
@ -590,7 +666,8 @@ boolean I_SetSongTrack(int track) {
// MUSIC FADING // MUSIC FADING
/// ------------------------ /// ------------------------
void I_SetInternalMusicVolume(UINT8 volume) { void I_SetInternalMusicVolume(UINT8 volume)
{
if (!music_player) if (!music_player)
return; return;
@ -600,7 +677,8 @@ void I_SetInternalMusicVolume(UINT8 volume) {
music_player->internal_gain(gain); music_player->internal_gain(gain);
} }
void I_StopFadingSong(void) { void I_StopFadingSong(void)
{
if (!music_player) if (!music_player)
return; return;
@ -609,7 +687,8 @@ void I_StopFadingSong(void) {
music_player->stop_fade(); music_player->stop_fade();
} }
boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) { boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
{
if (!music_player) if (!music_player)
return false; return false;
@ -628,7 +707,8 @@ boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms
return true; return true;
} }
boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) { boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
{
if (!music_player) if (!music_player)
return false; return false;
@ -646,7 +726,8 @@ boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) {
return true; return true;
} }
static void stop_song_cb(void) { static void stop_song_cb(void)
{
if (!music_player) if (!music_player)
return; return;
@ -655,11 +736,13 @@ static void stop_song_cb(void) {
music_player->stop(); music_player->stop();
} }
boolean I_FadeOutStopSong(UINT32 ms) { boolean I_FadeOutStopSong(UINT32 ms)
{
return I_FadeSong(0.f, ms, stop_song_cb); return I_FadeSong(0.f, ms, stop_song_cb);
} }
boolean I_FadeInPlaySong(UINT32 ms, boolean looping) { boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
{
if (I_PlaySong(looping)) if (I_PlaySong(looping))
return I_FadeSongFromVolume(100, 0, ms, nullptr); return I_FadeSongFromVolume(100, 0, ms, nullptr);
else else