Merge branch 'new-audio-mixer' into 'master'

New Audio Mixer

See merge request KartKrew/Kart!847
This commit is contained in:
Eidolon 2023-01-04 22:52:50 +00:00
commit 181a159f33
50 changed files with 9460 additions and 420 deletions

View file

@ -130,9 +130,7 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}")
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
find_package(SDL2 REQUIRED)
find_package(SDL2_mixer REQUIRED)
find_package(CURL REQUIRED)
find_package(OPENMPT REQUIRED)
find_package(GME REQUIRED)
endif()

View file

@ -1,33 +0,0 @@
include(LibFindMacros)
libfind_pkg_check_modules(OPENMPT_PKGCONF OPENMPT)
find_path(OPENMPT_INCLUDE_DIR
NAMES libopenmpt.h
PATHS
${OPENMPT_PKGCONF_INCLUDE_DIRS}
"/usr/include/libopenmpt"
"/usr/local/include/libopenmpt"
)
find_library(OPENMPT_LIBRARY
NAMES openmpt
PATHS
${OPENMPT_PKGCONF_LIBRARY_DIRS}
"/usr/lib"
"/usr/local/lib"
)
set(OPENMPT_PROCESS_INCLUDES OPENMPT_INCLUDE_DIR)
set(OPENMPT_PROCESS_LIBS OPENMPT_LIBRARY)
libfind_process(OPENMPT)
if(OPENMPT_FOUND AND NOT TARGET openmpt)
add_library(openmpt UNKNOWN IMPORTED)
set_target_properties(
openmpt
PROPERTIES
IMPORTED_LOCATION "${OPENMPT_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${OPENMPT_INCLUDE_DIR}"
)
endif()

View file

@ -1,44 +0,0 @@
# Find SDL2
# Once done, this will define
#
# SDL2_MIXER_FOUND - system has SDL2
# SDL2_MIXER_INCLUDE_DIRS - SDL2 include directories
# SDL2_MIXER_LIBRARIES - link libraries
include(LibFindMacros)
libfind_pkg_check_modules(SDL2_MIXER_PKGCONF SDL2_mixer)
# includes
find_path(SDL2_MIXER_INCLUDE_DIR
NAMES SDL_mixer.h
PATHS
${SDL2_MIXER_PKGCONF_INCLUDE_DIRS}
"/usr/include/SDL2"
"/usr/local/include/SDL2"
)
# library
find_library(SDL2_MIXER_LIBRARY
NAMES SDL2_mixer
PATHS
${SDL2_MIXER_PKGCONF_LIBRARY_DIRS}
"/usr/lib"
"/usr/local/lib"
)
# set include dir variables
set(SDL2_MIXER_PROCESS_INCLUDES SDL2_MIXER_INCLUDE_DIR)
set(SDL2_MIXER_PROCESS_LIBS SDL2_MIXER_LIBRARY)
libfind_process(SDL2_MIXER)
if(SDL2_MIXER_FOUND AND NOT TARGET SDL2_mixer::SDL2_mixer)
add_library(SDL2_mixer::SDL2_mixer UNKNOWN IMPORTED)
set_target_properties(
SDL2_mixer::SDL2_mixer
PROPERTIES
IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}"
)
endif()

View file

@ -214,9 +214,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
target_include_directories(SRB2SDL2 PRIVATE "${libgme_SOURCE_DIR}")
endif()
target_link_libraries(SRB2SDL2 PRIVATE openmpt)
target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_OPENMPT)
target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB PNG::PNG CURL::libcurl)
target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB -DHAVE_PNG -DHAVE_CURL -D_LARGEFILE64_SOURCE)
target_sources(SRB2SDL2 PRIVATE apng.c)
@ -226,6 +223,8 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN)
target_sources(SRB2SDL2 PRIVATE discord.c stun.c)
target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span)
target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis)
target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite)
set(SRB2_HAVE_THREADS ON)
target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_THREADS)
@ -538,6 +537,7 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
target_link_options(SRB2SDL2 PRIVATE -pg)
endif()
add_subdirectory(audio)
add_subdirectory(io)
add_subdirectory(sdl)
add_subdirectory(objects)

37
src/audio/CMakeLists.txt Normal file
View file

@ -0,0 +1,37 @@
target_sources(SRB2SDL2 PRIVATE
chunk_load.cpp
chunk_load.hpp
expand_mono.cpp
expand_mono.hpp
filter.cpp
filter.hpp
gain.cpp
gain.hpp
gme_player.cpp
gme_player.hpp
gme.cpp
gme.hpp
mixer.cpp
mixer.hpp
music_player.cpp
music_player.hpp
ogg_player.cpp
ogg_player.hpp
ogg.cpp
ogg.hpp
resample.cpp
resample.hpp
sample.hpp
sound_chunk.hpp
sound_effect_player.cpp
sound_effect_player.hpp
source.hpp
wav_player.cpp
wav_player.hpp
wav.cpp
wav.hpp
xmp_player.cpp
xmp_player.hpp
xmp.cpp
xmp.hpp
)

206
src/audio/chunk_load.cpp Normal file
View file

@ -0,0 +1,206 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "chunk_load.hpp"
#include <stb_vorbis.h>
#include "../cxxutil.hpp"
#include "../io/streams.hpp"
#include "gme.hpp"
#include "gme_player.hpp"
#include "ogg.hpp"
#include "ogg_player.hpp"
#include "resample.hpp"
#include "sound_chunk.hpp"
#include "sound_effect_player.hpp"
#include "wav.hpp"
#include "wav_player.hpp"
using std::nullopt;
using std::optional;
using std::size_t;
using namespace srb2::audio;
using namespace srb2;
namespace {
// Utility for leveraging Resampler...
class SoundChunkSource : public Source<1> {
public:
explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk)
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk)) {}
virtual size_t generate(tcb::span<Sample<1>> buffer) override final {
if (!chunk_)
return 0;
size_t written = 0;
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) {
buffer[written] = chunk_->samples[pos_];
written++;
}
return written;
}
private:
std::unique_ptr<SoundChunk> chunk_;
size_t pos_ {0};
};
template <class I>
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) {
std::vector<Sample<1>> generated;
size_t total = 0;
size_t read = 0;
generated.reserve(estimate);
do {
generated.resize(total + 4096);
read = source.generate(tcb::span {generated.data() + total, 4096});
total += read;
} while (read != 0);
generated.resize(total);
return generated;
}
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
io::SpanStream stream {data};
if (io::remaining(stream) < 8)
return nullopt;
uint16_t version = io::read_uint16(stream);
if (version != 3)
return nullopt;
uint16_t rate = io::read_uint16(stream);
uint32_t length = io::read_uint32(stream) - 32u;
if (io::remaining(stream) < (length + 32u))
return nullopt;
stream.seek(io::SeekFrom::kCurrent, 16);
std::vector<Sample<1>> samples;
for (size_t i = 0; i < length; i++) {
uint8_t doom_sample = io::read_uint8(stream);
float float_sample = audio::sample_to_float(doom_sample);
samples.push_back(Sample<1> {float_sample});
}
size_t samples_len = samples.size();
if (rate == 44100) {
return SoundChunk {samples};
}
std::unique_ptr<SoundChunkSource> chunk_source =
std::make_unique<SoundChunkSource>(std::make_unique<SoundChunk>(SoundChunk {std::move(samples)}));
Resampler<1> resampler(std::move(chunk_source), rate / static_cast<float>(kSampleRate));
std::vector<Sample<1>> resampled;
size_t total = 0;
size_t read = 0;
resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate));
do {
resampled.resize(total + 4096);
read = resampler.generate(tcb::span {resampled.data() + total, 4096});
total += read;
} while (read != 0);
resampled.resize(total);
return SoundChunk {std::move(resampled)};
}
optional<SoundChunk> try_load_wav(tcb::span<std::byte> data) {
io::SpanStream stream {data};
audio::Wav wav;
std::size_t sample_rate;
try {
wav = audio::load_wav(stream);
} catch (const std::exception& ex) {
return nullopt;
}
sample_rate = wav.sample_rate();
audio::Resampler<1> resampler(std::make_unique<WavPlayer>(std::move(wav)),
sample_rate / static_cast<float>(kSampleRate));
SoundChunk chunk {generate_to_vec(resampler)};
return chunk;
}
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) {
std::shared_ptr<audio::OggPlayer<1>> player;
try {
io::SpanStream data_stream {data};
audio::Ogg ogg = audio::load_ogg(data_stream);
player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg));
} catch (...) {
return nullopt;
}
player->looping(false);
player->playing(true);
player->reset();
std::size_t sample_rate = player->sample_rate();
audio::Resampler<1> resampler(player, sample_rate / 44100.);
std::vector<Sample<1>> resampled {generate_to_vec(resampler)};
SoundChunk chunk {std::move(resampled)};
return chunk;
}
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) {
std::shared_ptr<audio::GmePlayer<1>> player;
try {
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) {
io::SpanStream stream {data};
audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme));
} else {
io::ZlibInputStream stream {io::SpanStream(data)};
audio::Gme gme = audio::load_gme(stream);
player = std::make_shared<GmePlayer<1>>(std::move(gme));
}
} catch (...) {
return nullopt;
}
std::vector<Sample<1>> samples {generate_to_vec(*player)};
SoundChunk chunk {std::move(samples)};
return chunk;
}
} // namespace
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data) {
optional<SoundChunk> ret;
ret = try_load_dmx(data);
if (ret)
return ret;
ret = try_load_wav(data);
if (ret)
return ret;
ret = try_load_ogg(data);
if (ret)
return ret;
ret = try_load_gme(data);
if (ret)
return ret;
return nullopt;
}

27
src/audio/chunk_load.hpp Normal file
View file

@ -0,0 +1,27 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_CHUNK_LOAD_HPP__
#define __SRB2_AUDIO_CHUNK_LOAD_HPP__
#include <cstddef>
#include <optional>
#include <tcb/span.hpp>
#include "sound_chunk.hpp"
namespace srb2::audio {
/// @brief Try to load a chunk from the given byte span.
std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data);
} // namespace srb2::audio
#endif // __SRB2_AUDIO_CHUNK_LOAD_HPP__

26
src/audio/expand_mono.cpp Normal file
View file

@ -0,0 +1,26 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "expand_mono.hpp"
#include <algorithm>
using std::size_t;
using namespace srb2::audio;
ExpandMono::~ExpandMono() = default;
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++) {
buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0];
buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0];
}
return std::min(input_buffer.size(), buffer.size());
}

27
src/audio/expand_mono.hpp Normal file
View file

@ -0,0 +1,27 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_EXPAND_MONO_HPP__
#define __SRB2_AUDIO_EXPAND_MONO_HPP__
#include <tcb/span.hpp>
#include "filter.hpp"
namespace srb2::audio {
class ExpandMono : public Filter<1, 2> {
public:
virtual ~ExpandMono();
virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final;
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_EXPAND_MONO_HPP__

40
src/audio/filter.cpp Normal file
View file

@ -0,0 +1,40 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "filter.hpp"
using std::shared_ptr;
using std::size_t;
using srb2::audio::Filter;
using srb2::audio::Sample;
using srb2::audio::Source;
template <size_t IC, size_t OC>
size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) {
input_buffer_.clear();
input_buffer_.resize(buffer.size());
input_->generate(input_buffer_);
return filter(input_buffer_, buffer);
}
template <size_t IC, size_t OC>
void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input) {
input_ = input;
}
template <size_t IC, size_t OC>
Filter<IC, OC>::~Filter() = default;
template class srb2::audio::Filter<1, 1>;
template class srb2::audio::Filter<1, 2>;
template class srb2::audio::Filter<2, 1>;
template class srb2::audio::Filter<2, 2>;

46
src/audio/filter.hpp Normal file
View file

@ -0,0 +1,46 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_FILTER_HPP__
#define __SRB2_AUDIO_FILTER_HPP__
#include <cstddef>
#include <memory>
#include <vector>
#include <tcb/span.hpp>
#include "source.hpp"
namespace srb2::audio {
template <size_t IC, size_t OC>
class Filter : public Source<OC> {
public:
virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override;
void bind(const std::shared_ptr<Source<IC>>& input);
virtual std::size_t filter(tcb::span<Sample<IC>> input_buffer, tcb::span<Sample<OC>> buffer) = 0;
virtual ~Filter();
private:
std::shared_ptr<Source<IC>> input_;
std::vector<Sample<IC>> input_buffer_;
};
extern template class Filter<1, 1>;
extern template class Filter<1, 2>;
extern template class Filter<2, 1>;
extern template class Filter<2, 2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_FILTER_HPP__

43
src/audio/gain.cpp Normal file
View file

@ -0,0 +1,43 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "gain.hpp"
#include <algorithm>
using std::size_t;
using srb2::audio::Filter;
using srb2::audio::Gain;
using srb2::audio::Sample;
constexpr const float kGainInterpolationAlpha = 0.8f;
template <size_t C>
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());
for (size_t i = 0; i < written; i++) {
buffer[i] = input_buffer[i];
buffer[i] *= gain_;
gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha;
}
return written;
}
template <size_t C>
void Gain<C>::gain(float new_gain) {
new_gain_ = std::clamp(new_gain, 0.0f, 1.0f);
}
template <size_t C>
Gain<C>::~Gain() = default;
template class srb2::audio::Gain<1>;
template class srb2::audio::Gain<2>;

33
src/audio/gain.hpp Normal file
View file

@ -0,0 +1,33 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_GAIN_HPP__
#define __SRB2_AUDIO_GAIN_HPP__
#include <tcb/span.hpp>
#include "filter.hpp"
namespace srb2::audio {
template <size_t C>
class Gain : public Filter<C, C> {
public:
virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final;
void gain(float new_gain);
virtual ~Gain();
private:
float new_gain_ {1.f};
float gain_ {1.f};
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_GAIN_HPP__

141
src/audio/gme.cpp Normal file
View file

@ -0,0 +1,141 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "gme.hpp"
#include <limits>
#include <stdexcept>
#include "../cxxutil.hpp"
using namespace srb2;
using namespace srb2::audio;
Gme::Gme() : memory_data_(), instance_(nullptr) {
}
Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr) {
std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_);
}
Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr) {
_init_with_data();
}
Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
_init_with_data();
}
Gme& Gme::operator=(Gme&& rhs) noexcept {
std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_);
return *this;
}
Gme::~Gme() {
if (instance_) {
gme_delete(instance_);
instance_ = nullptr;
}
}
std::size_t Gme::get_samples(tcb::span<short> buffer) {
SRB2_ASSERT(instance_ != nullptr);
gme_err_t err = gme_play(instance_, buffer.size(), buffer.data());
if (err)
throw GmeException(err);
return buffer.size();
}
void Gme::seek(int sample) {
SRB2_ASSERT(instance_ != nullptr);
gme_seek_samples(instance_, sample);
}
std::optional<float> Gme::duration_seconds() const {
SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr;
gme_err_t res = gme_track_info(instance_, &info, 0);
if (res)
throw GmeException(res);
auto info_finally = srb2::finally([&info] { gme_free_info(info); });
if (info->length == -1)
return std::nullopt;
// info lengths are in ms
return static_cast<float>(info->length) / 1000.f;
}
std::optional<float> Gme::loop_point_seconds() const {
SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr;
gme_err_t res = gme_track_info(instance_, &info, 0);
if (res)
throw GmeException(res);
auto info_finally = srb2::finally([&info] { gme_free_info(info); });
int loop_point_ms = info->intro_length;
if (loop_point_ms == -1)
return std::nullopt;
return loop_point_ms / 44100.f;
}
float Gme::position_seconds() const {
SRB2_ASSERT(instance_ != nullptr);
gme_info_t* info = nullptr;
gme_err_t res = gme_track_info(instance_, &info, 0);
if (res)
throw GmeException(res);
auto info_finally = srb2::finally([&info] { gme_free_info(info); });
int position = gme_tell(instance_);
// adjust position, since GME's counter keeps going past loop
if (info->length > 0)
position %= info->length;
else if (info->intro_length + info->loop_length > 0)
position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position;
else
position %= 150 * 1000; // 2.5 minutes
return position / 1000.f;
}
void Gme::_init_with_data() {
if (instance_) {
return;
}
if (memory_data_.size() >= std::numeric_limits<long>::max())
throw std::invalid_argument("Buffer is too large for gme");
if (memory_data_.size() == 0)
throw std::invalid_argument("Insufficient data from stream");
gme_err_t result =
gme_open_data(reinterpret_cast<const void*>(memory_data_.data()), memory_data_.size(), &instance_, 44100);
if (result)
throw GmeException(result);
// we no longer need the data, so there's no reason to keep the allocation
memory_data_ = std::vector<std::byte>();
result = gme_start_track(instance_, 0);
if (result)
throw GmeException(result);
}

74
src/audio/gme.hpp Normal file
View file

@ -0,0 +1,74 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_GME_HPP__
#define __SRB2_AUDIO_GME_HPP__
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <gme/gme.h>
#undef byte // BLARGG!! NO!!
#undef check // STOP IT!!!!
#include "../io/streams.hpp"
namespace srb2::audio {
class GmeException : public std::exception {
std::string msg_;
public:
explicit GmeException(gme_err_t msg) : msg_(msg == nullptr ? "" : msg) {}
virtual const char* what() const noexcept override { return msg_.c_str(); }
};
class Gme {
std::vector<std::byte> memory_data_;
Music_Emu* instance_;
public:
Gme();
Gme(const Gme&) = delete;
Gme(Gme&& rhs) noexcept;
Gme& operator=(const Gme&) = delete;
Gme& operator=(Gme&& rhs) noexcept;
explicit Gme(std::vector<std::byte>&& data);
explicit Gme(tcb::span<std::byte> data);
std::size_t get_samples(tcb::span<short> buffer);
void seek(int sample);
std::optional<float> duration_seconds() const;
std::optional<float> loop_point_seconds() const;
float position_seconds() const;
~Gme();
private:
void _init_with_data();
};
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Gme load_gme(I& stream) {
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Gme {std::move(data)};
}
} // namespace srb2::audio
#endif // __SRB2_AUDIO_GME_HPP__

73
src/audio/gme_player.cpp Normal file
View file

@ -0,0 +1,73 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "gme_player.hpp"
using namespace srb2;
using namespace srb2::audio;
template <size_t C>
GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_() {
}
template <size_t C>
GmePlayer<C>::GmePlayer(GmePlayer<C>&& rhs) noexcept = default;
template <size_t C>
GmePlayer<C>& GmePlayer<C>::operator=(GmePlayer<C>&& rhs) noexcept = default;
template <size_t C>
GmePlayer<C>::~GmePlayer() = default;
template <size_t C>
std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) {
buf_.clear();
buf_.resize(buffer.size() * 2);
std::size_t read = gme_.get_samples(tcb::make_span(buf_));
buf_.resize(read);
std::size_t new_samples = std::min((read / 2), buffer.size());
for (std::size_t i = 0; i < new_samples; i++) {
if constexpr (C == 1) {
buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f;
} else if constexpr (C == 2) {
buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f;
buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f;
}
}
return new_samples;
}
template <size_t C>
void GmePlayer<C>::seek(float position_seconds) {
gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f));
}
template <size_t C>
void GmePlayer<C>::reset() {
gme_.seek(0);
}
template <size_t C>
std::optional<float> GmePlayer<C>::duration_seconds() const {
return gme_.duration_seconds();
}
template <size_t C>
std::optional<float> GmePlayer<C>::loop_point_seconds() const {
return gme_.loop_point_seconds();
}
template <size_t C>
float GmePlayer<C>::position_seconds() const {
return gme_.position_seconds();
}
template class srb2::audio::GmePlayer<1>;
template class srb2::audio::GmePlayer<2>;

51
src/audio/gme_player.hpp Normal file
View file

@ -0,0 +1,51 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_GME_PLAYER_HPP__
#define __SRB2_AUDIO_GME_PLAYER_HPP__
#include <optional>
#include "gme.hpp"
#include "source.hpp"
namespace srb2::audio {
template <size_t C>
class GmePlayer : public Source<C> {
Gme gme_;
std::vector<short> buf_;
public:
GmePlayer(Gme&& gme);
GmePlayer(const GmePlayer<C>&) = delete;
GmePlayer(GmePlayer<C>&& gme) noexcept;
~GmePlayer();
GmePlayer& operator=(const GmePlayer<C>&) = delete;
GmePlayer& operator=(GmePlayer<C>&& rhs) noexcept;
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override;
void seek(float position_seconds);
std::optional<float> duration_seconds() const;
std::optional<float> loop_point_seconds() const;
float position_seconds() const;
void reset();
};
extern template class GmePlayer<1>;
extern template class GmePlayer<2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_GME_PLAYER_HPP__

62
src/audio/mixer.cpp Normal file
View file

@ -0,0 +1,62 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "mixer.hpp"
#include <algorithm>
using std::shared_ptr;
using std::size_t;
using srb2::audio::Mixer;
using srb2::audio::Sample;
using srb2::audio::Source;
namespace {
template <size_t C>
void default_init_sample_buffer(Sample<C>* buffer, size_t size) {
std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; });
}
template <size_t C>
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++) {
dst[i] += src[i];
}
}
} // namespace
template <size_t C>
size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) {
buffer_.resize(buffer.size());
default_init_sample_buffer<C>(buffer.data(), buffer.size());
for (auto& source : sources_) {
size_t read = source->generate(buffer_);
mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read);
}
// because we initialized the out-buffer, we always generate size samples
return buffer.size();
}
template <size_t C>
void Mixer<C>::add_source(const shared_ptr<Source<C>>& source) {
sources_.push_back(source);
}
template <size_t C>
Mixer<C>::~Mixer() = default;
template class srb2::audio::Mixer<1>;
template class srb2::audio::Mixer<2>;

41
src/audio/mixer.hpp Normal file
View file

@ -0,0 +1,41 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_MIXER_HPP__
#define __SRB2_AUDIO_MIXER_HPP__
#include <memory>
#include <vector>
#include <tcb/span.hpp>
#include "source.hpp"
namespace srb2::audio {
template <size_t C>
class Mixer : public Source<C> {
public:
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;
virtual ~Mixer();
void add_source(const std::shared_ptr<Source<C>>& source);
private:
std::vector<std::shared_ptr<Source<C>>> sources_;
std::vector<Sample<C>> buffer_;
};
extern template class Mixer<1>;
extern template class Mixer<2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_MIXER_HPP__

421
src/audio/music_player.cpp Normal file
View file

@ -0,0 +1,421 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "music_player.hpp"
#include <algorithm>
#include <cmath>
#include <exception>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
#include <gme/gme.h>
#include <stb_vorbis.h>
#undef byte // BLARGG!! NO!!
#undef check // STOP IT!!!!
#include "../cxxutil.hpp"
#include "../io/streams.hpp"
#include "gme_player.hpp"
#include "ogg_player.hpp"
#include "resample.hpp"
#include "xmp_player.hpp"
using std::array;
using std::byte;
using std::make_unique;
using std::size_t;
using std::vector;
using srb2::audio::MusicPlayer;
using srb2::audio::Resampler;
using srb2::audio::Sample;
using srb2::audio::Source;
using namespace srb2;
class MusicPlayer::Impl {
public:
Impl() = default;
Impl(tcb::span<std::byte> data) : Impl() { _load(data); }
size_t generate(tcb::span<Sample<2>> buffer) {
if (!resampler_)
return 0;
if (!playing_)
return 0;
size_t total_written = 0;
while (total_written < buffer.size()) {
const size_t generated = resampler_->generate(buffer.subspan(total_written));
// To avoid a branch preventing optimizations, we're always going to apply
// the fade gain, even if it would clamp anyway.
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_)) /
static_cast<double>(gain_samples_target_);
const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_;
buffer[total_written + i] *= fade_gain;
}
gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_);
if (gain_samples_ >= gain_samples_target_) {
fading_ = false;
gain_samples_ = gain_samples_target_;
gain_ = gain_target_;
}
total_written += generated;
if (generated == 0) {
playing_ = false;
break;
}
}
return total_written;
}
void _load(tcb::span<std::byte> data) {
ogg_inst_ = nullptr;
gme_inst_ = nullptr;
xmp_inst_ = nullptr;
resampler_ = std::nullopt;
try {
io::SpanStream stream {data};
audio::Ogg ogg = audio::load_ogg(stream);
ogg_inst_ = std::make_shared<audio::OggPlayer<2>>(std::move(ogg));
ogg_inst_->looping(looping_);
resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f);
} catch (const std::exception& ex) {
// it's probably not ogg
ogg_inst_ = nullptr;
resampler_ = std::nullopt;
}
if (!resampler_) {
try {
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) {
io::ZlibInputStream stream {io::SpanStream(data)};
audio::Gme gme = audio::load_gme(stream);
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
} else {
io::SpanStream stream {data};
audio::Gme gme = audio::load_gme(stream);
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
}
resampler_ = Resampler<2>(gme_inst_, 1.f);
} catch (const std::exception& ex) {
// it's probably not gme
gme_inst_ = nullptr;
resampler_ = std::nullopt;
}
}
if (!resampler_) {
try {
io::SpanStream stream {data};
audio::Xmp<2> xmp = audio::load_xmp<2>(stream);
xmp_inst_ = std::make_shared<XmpPlayer<2>>(std::move(xmp));
xmp_inst_->looping(looping_);
resampler_ = Resampler<2>(xmp_inst_, 1.f);
} catch (const std::exception& ex) {
// it's probably not xmp
xmp_inst_ = nullptr;
resampler_ = std::nullopt;
}
}
playing_ = false;
internal_gain(1.f);
}
void play(bool looping) {
if (ogg_inst_) {
ogg_inst_->looping(looping);
ogg_inst_->playing(true);
playing_ = true;
ogg_inst_->reset();
} else if (gme_inst_) {
playing_ = true;
gme_inst_->reset();
} else if (xmp_inst_) {
xmp_inst_->looping(looping);
playing_ = true;
xmp_inst_->reset();
}
}
void unpause() {
if (ogg_inst_) {
ogg_inst_->playing(true);
playing_ = true;
} else if (gme_inst_) {
playing_ = true;
} else if (xmp_inst_) {
playing_ = true;
}
}
void pause() {
if (ogg_inst_) {
ogg_inst_->playing(false);
playing_ = false;
} else if (gme_inst_) {
playing_ = false;
} else if (xmp_inst_) {
playing_ = false;
}
}
void stop() {
if (ogg_inst_) {
ogg_inst_->reset();
ogg_inst_->playing(false);
playing_ = false;
} else if (gme_inst_) {
gme_inst_->reset();
playing_ = false;
} else if (xmp_inst_) {
xmp_inst_->reset();
playing_ = false;
}
}
void seek(float position_seconds) {
if (ogg_inst_) {
ogg_inst_->seek(position_seconds);
return;
}
if (gme_inst_) {
gme_inst_->seek(position_seconds);
return;
}
if (xmp_inst_) {
xmp_inst_->seek(position_seconds);
return;
}
}
bool playing() const {
if (ogg_inst_)
return ogg_inst_->playing();
else if (gme_inst_)
return playing_;
else if (xmp_inst_)
return playing_;
return false;
}
std::optional<audio::MusicType> music_type() const {
if (ogg_inst_)
return audio::MusicType::kOgg;
else if (gme_inst_)
return audio::MusicType::kGme;
else if (xmp_inst_)
return audio::MusicType::kMod;
return std::nullopt;
}
std::optional<float> duration_seconds() const {
if (ogg_inst_)
return ogg_inst_->duration_seconds();
if (gme_inst_)
return gme_inst_->duration_seconds();
if (xmp_inst_)
return xmp_inst_->duration_seconds();
return std::nullopt;
}
std::optional<float> loop_point_seconds() const {
if (ogg_inst_)
return ogg_inst_->loop_point_seconds();
if (gme_inst_)
return gme_inst_->loop_point_seconds();
return std::nullopt;
}
std::optional<float> position_seconds() const {
if (ogg_inst_)
return ogg_inst_->position_seconds();
if (gme_inst_)
return gme_inst_->position_seconds();
return std::nullopt;
}
void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); }
void fade_from_to(float from, float to, float seconds) {
fading_ = true;
gain_ = from;
gain_target_ = to;
// Gain samples target must always be at least 1 to avoid a div-by-zero.
gain_samples_target_ = std::max(static_cast<uint64_t>(seconds * 44100.f), 1ULL);
gain_samples_ = 0;
}
bool fading() const { return fading_; }
void stop_fade() { internal_gain(gain_target_); }
void loop_point_seconds(float loop_point) {
if (ogg_inst_)
ogg_inst_->loop_point_seconds(loop_point);
}
void internal_gain(float gain) {
fading_ = false;
gain_ = gain;
gain_target_ = gain;
gain_samples_target_ = 1;
gain_samples_ = 0;
}
private:
std::shared_ptr<OggPlayer<2>> ogg_inst_;
std::shared_ptr<GmePlayer<2>> gme_inst_;
std::shared_ptr<XmpPlayer<2>> xmp_inst_;
std::optional<Resampler<2>> resampler_;
bool playing_ {false};
bool looping_ {false};
// fade control
float gain_target_ {1.f};
float gain_ {1.f};
bool fading_ {false};
uint64_t gain_samples_ {0};
uint64_t gain_samples_target_ {1};
};
// The special member functions MUST be declared in this unit, where Impl is complete.
MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>()) {
}
MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data)) {
}
MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default;
MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default;
MusicPlayer::~MusicPlayer() = default;
void MusicPlayer::play(bool looping) {
SRB2_ASSERT(impl_ != nullptr);
return impl_->play(looping);
}
void MusicPlayer::unpause() {
SRB2_ASSERT(impl_ != nullptr);
return impl_->unpause();
}
void MusicPlayer::pause() {
SRB2_ASSERT(impl_ != nullptr);
return impl_->pause();
}
void MusicPlayer::stop() {
SRB2_ASSERT(impl_ != nullptr);
return impl_->stop();
}
void MusicPlayer::seek(float position_seconds) {
SRB2_ASSERT(impl_ != nullptr);
return impl_->seek(position_seconds);
}
bool MusicPlayer::playing() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->playing();
}
size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer) {
SRB2_ASSERT(impl_ != nullptr);
return impl_->generate(buffer);
}
std::optional<audio::MusicType> MusicPlayer::music_type() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->music_type();
}
std::optional<float> MusicPlayer::duration_seconds() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->duration_seconds();
}
std::optional<float> MusicPlayer::loop_point_seconds() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->loop_point_seconds();
}
std::optional<float> MusicPlayer::position_seconds() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->position_seconds();
}
void MusicPlayer::fade_to(float gain, float seconds) {
SRB2_ASSERT(impl_ != nullptr);
impl_->fade_to(gain, seconds);
}
void MusicPlayer::fade_from_to(float from, float to, float seconds) {
SRB2_ASSERT(impl_ != nullptr);
impl_->fade_from_to(from, to, seconds);
}
void MusicPlayer::internal_gain(float gain) {
SRB2_ASSERT(impl_ != nullptr);
impl_->internal_gain(gain);
}
bool MusicPlayer::fading() const {
SRB2_ASSERT(impl_ != nullptr);
return impl_->fading();
}
void MusicPlayer::stop_fade() {
SRB2_ASSERT(impl_ != nullptr);
impl_->stop_fade();
}
void MusicPlayer::loop_point_seconds(float loop_point) {
SRB2_ASSERT(impl_ != nullptr);
impl_->loop_point_seconds(loop_point);
}

View file

@ -0,0 +1,69 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_MUSIC_PLAYER_HPP__
#define __SRB2_AUDIO_MUSIC_PLAYER_HPP__
#include <memory>
#include <optional>
#include <tcb/span.hpp>
#include "source.hpp"
struct stb_vorbis;
namespace srb2::audio {
enum class MusicType {
kOgg,
kGme,
kMod
};
class MusicPlayer : public Source<2> {
public:
MusicPlayer();
MusicPlayer(tcb::span<std::byte> data);
MusicPlayer(const MusicPlayer& rhs) = delete;
MusicPlayer(MusicPlayer&& rhs) noexcept;
MusicPlayer& operator=(const MusicPlayer& rhs) = delete;
MusicPlayer& operator=(MusicPlayer&& rhs) noexcept;
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final;
void play(bool looping);
void unpause();
void pause();
void stop();
void seek(float position_seconds);
void fade_to(float gain, float seconds);
void fade_from_to(float from, float to, float seconds);
void internal_gain(float gain);
void stop_fade();
void loop_point_seconds(float loop_point);
bool playing() const;
std::optional<MusicType> music_type() const;
std::optional<float> duration_seconds() const;
std::optional<float> loop_point_seconds() const;
std::optional<float> position_seconds() const;
bool fading() const;
virtual ~MusicPlayer() final;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_MUSIC_PLAYER_HPP__

194
src/audio/ogg.cpp Normal file
View file

@ -0,0 +1,194 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "ogg.hpp"
#include <limits>
#include "../cxxutil.hpp"
using namespace srb2;
using namespace srb2::audio;
StbVorbisException::StbVorbisException(int code) noexcept : code_(code) {
}
const char* StbVorbisException::what() const noexcept {
switch (code_) {
case VORBIS__no_error:
return "No error";
case VORBIS_need_more_data:
return "Need more data";
case VORBIS_invalid_api_mixing:
return "Invalid API mixing";
case VORBIS_outofmem:
return "Out of memory";
case VORBIS_feature_not_supported:
return "Feature not supported";
case VORBIS_too_many_channels:
return "Too many channels";
case VORBIS_file_open_failure:
return "File open failure";
case VORBIS_seek_without_length:
return "Seek without length";
case VORBIS_unexpected_eof:
return "Unexpected EOF";
case VORBIS_seek_invalid:
return "Seek invalid";
case VORBIS_invalid_setup:
return "Invalid setup";
case VORBIS_invalid_stream:
return "Invalid stream";
case VORBIS_missing_capture_pattern:
return "Missing capture pattern";
case VORBIS_invalid_stream_structure_version:
return "Invalid stream structure version";
case VORBIS_continued_packet_flag_invalid:
return "Continued packet flag invalid";
case VORBIS_incorrect_stream_serial_number:
return "Incorrect stream serial number";
case VORBIS_invalid_first_page:
return "Invalid first page";
case VORBIS_bad_packet_type:
return "Bad packet type";
case VORBIS_cant_find_last_page:
return "Can't find last page";
case VORBIS_seek_failed:
return "Seek failed";
case VORBIS_ogg_skeleton_not_supported:
return "OGG skeleton not supported";
default:
return "Unrecognized error code";
}
}
Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) {
}
Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr) {
_init_with_data();
}
Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
_init_with_data();
}
Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) {
std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_);
}
Ogg& Ogg::operator=(Ogg&& rhs) noexcept {
std::swap(memory_data_, rhs.memory_data_);
std::swap(instance_, rhs.instance_);
return *this;
}
Ogg::~Ogg() {
if (instance_) {
stb_vorbis_close(instance_);
instance_ = nullptr;
}
}
std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer) {
SRB2_ASSERT(instance_ != nullptr);
size_t read = stb_vorbis_get_samples_float_interleaved(
instance_, 1, reinterpret_cast<float*>(buffer.data()), buffer.size() * 1);
return read;
}
std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) {
SRB2_ASSERT(instance_ != nullptr);
size_t read = stb_vorbis_get_samples_float_interleaved(
instance_, 2, reinterpret_cast<float*>(buffer.data()), buffer.size() * 2);
stb_vorbis_info info = stb_vorbis_get_info(instance_);
if (info.channels == 1) {
for (auto& sample : buffer.subspan(0, read)) {
sample.amplitudes[1] = sample.amplitudes[0];
}
}
return read;
}
OggComment Ogg::comment() const {
SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_);
return OggComment {
std::string(c_comment.vendor),
std::vector<std::string>(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)};
}
std::size_t Ogg::sample_rate() const {
SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_info info = stb_vorbis_get_info(instance_);
return info.sample_rate;
}
void Ogg::seek(std::size_t sample) {
SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_seek(instance_, sample);
}
std::size_t Ogg::position() const {
SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_get_sample_offset(instance_);
}
float Ogg::position_seconds() const {
return position() / static_cast<float>(sample_rate());
}
std::size_t Ogg::duration_samples() const {
SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_stream_length_in_samples(instance_);
}
float Ogg::duration_seconds() const {
SRB2_ASSERT(instance_ != nullptr);
return stb_vorbis_stream_length_in_seconds(instance_);
}
std::size_t Ogg::channels() const {
SRB2_ASSERT(instance_ != nullptr);
stb_vorbis_info info = stb_vorbis_get_info(instance_);
return info.channels;
}
void Ogg::_init_with_data() {
if (instance_) {
return;
}
if (memory_data_.size() >= std::numeric_limits<int>::max())
throw std::logic_error("Buffer is too large for stb_vorbis");
if (memory_data_.size() == 0)
throw std::logic_error("Insufficient data from stream");
int vorbis_result;
instance_ = stb_vorbis_open_memory(
reinterpret_cast<const unsigned char*>(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL);
if (vorbis_result != VORBIS__no_error)
throw StbVorbisException(vorbis_result);
}

81
src/audio/ogg.hpp Normal file
View file

@ -0,0 +1,81 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_OGG_HPP__
#define __SRB2_AUDIO_OGG_HPP__
#include <exception>
#include <stdexcept>
#include <vector>
#include <stb_vorbis.h>
#include <tcb/span.hpp>
#include "../io/streams.hpp"
#include "source.hpp"
namespace srb2::audio {
class StbVorbisException final : public std::exception {
int code_;
public:
explicit StbVorbisException(int code) noexcept;
virtual const char* what() const noexcept;
};
struct OggComment {
std::string vendor;
std::vector<std::string> comments;
};
class Ogg final {
std::vector<std::byte> memory_data_;
stb_vorbis* instance_;
public:
Ogg() noexcept;
explicit Ogg(std::vector<std::byte> data);
explicit Ogg(tcb::span<std::byte> data);
Ogg(const Ogg&) = delete;
Ogg(Ogg&& rhs) noexcept;
Ogg& operator=(const Ogg&) = delete;
Ogg& operator=(Ogg&& rhs) noexcept;
~Ogg();
std::size_t get_samples(tcb::span<Sample<1>> buffer);
std::size_t get_samples(tcb::span<Sample<2>> buffer);
void seek(std::size_t sample);
std::size_t position() const;
float position_seconds() const;
OggComment comment() const;
std::size_t sample_rate() const;
std::size_t channels() const;
std::size_t duration_samples() const;
float duration_seconds() const;
private:
void _init_with_data();
};
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Ogg load_ogg(I& stream) {
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Ogg {std::move(data)};
}
} // namespace srb2::audio
#endif // __SRB2_AUDIO_OGG_HPP__

141
src/audio/ogg_player.cpp Normal file
View file

@ -0,0 +1,141 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "ogg_player.hpp"
#include <cmath>
#include <limits>
#include <optional>
#include <stdexcept>
#include <utility>
using namespace srb2;
using namespace srb2::audio;
namespace {
std::optional<std::size_t> find_loop_point(const Ogg& ogg) {
OggComment comment = ogg.comment();
std::size_t rate = ogg.sample_rate();
for (auto& comment : comment.comments) {
if (comment.find("LOOPPOINT=") == 0) {
std::string_view comment_view(comment);
comment_view.remove_prefix(10);
std::string copied {comment_view};
try {
int loop_point = std::stoi(copied);
return loop_point;
} catch (...) {
}
}
if (comment.find("LOOPMS=") == 0) {
std::string_view comment_view(comment);
comment_view.remove_prefix(7);
std::string copied {comment_view};
try {
int loop_ms = std::stoi(copied);
int loop_point = std::round(static_cast<double>(loop_ms) / (rate / 1000.));
return loop_point;
} catch (...) {
}
}
}
return std::nullopt;
}
} // namespace
template <size_t C>
OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept
: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg)) {
loop_point_ = find_loop_point(ogg_);
}
template <size_t C>
OggPlayer<C>::OggPlayer(OggPlayer&& rhs) noexcept = default;
template <size_t C>
OggPlayer<C>& OggPlayer<C>::operator=(OggPlayer&& rhs) noexcept = default;
template <size_t C>
OggPlayer<C>::~OggPlayer() = default;
template <size_t C>
std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
if (!playing_)
return 0;
std::size_t total = 0;
do {
std::size_t read = ogg_.get_samples(buffer.subspan(total));
total += read;
if (read == 0 && !looping_) {
playing_ = false;
break;
}
if (read == 0 && loop_point_) {
ogg_.seek(*loop_point_);
}
if (read == 0 && !loop_point_) {
ogg_.seek(0);
}
} while (total < buffer.size());
return total;
}
template <size_t C>
void OggPlayer<C>::seek(float position_seconds) {
ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate()));
}
template <size_t C>
void OggPlayer<C>::loop_point_seconds(float loop_point) {
std::size_t rate = sample_rate();
loop_point = static_cast<std::size_t>(std::round(loop_point * rate));
}
template <size_t C>
void OggPlayer<C>::reset() {
ogg_.seek(0);
}
template <size_t C>
std::size_t OggPlayer<C>::sample_rate() const {
return ogg_.sample_rate();
}
template <size_t C>
float OggPlayer<C>::duration_seconds() const {
return ogg_.duration_seconds();
}
template <size_t C>
std::optional<float> OggPlayer<C>::loop_point_seconds() const {
if (!loop_point_)
return std::nullopt;
return *loop_point_ / static_cast<float>(sample_rate());
}
template <size_t C>
float OggPlayer<C>::position_seconds() const {
return ogg_.position_seconds();
}
template class srb2::audio::OggPlayer<1>;
template class srb2::audio::OggPlayer<2>;

72
src/audio/ogg_player.hpp Normal file
View file

@ -0,0 +1,72 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_OGG_SOURCE_HPP__
#define __SRB2_AUDIO_OGG_SOURCE_HPP__
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include <stb_vorbis.h>
#include <tcb/span.hpp>
#include "../io/streams.hpp"
#include "ogg.hpp"
#include "source.hpp"
namespace srb2::audio {
template <size_t C>
class OggPlayer final : public Source<C> {
bool playing_;
bool looping_;
std::optional<std::size_t> loop_point_;
Ogg ogg_;
public:
OggPlayer(Ogg&& ogg) noexcept;
OggPlayer(const OggPlayer&) = delete;
OggPlayer(OggPlayer&& rhs) noexcept;
OggPlayer& operator=(const OggPlayer&) = delete;
OggPlayer& operator=(OggPlayer&& rhs) noexcept;
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;
bool looping() const { return looping_; }
void looping(bool looping) { looping_ = looping; }
bool playing() const { return playing_; }
void playing(bool playing) { playing_ = playing; }
void seek(float position_seconds);
void loop_point_seconds(float loop_point);
void reset();
std::size_t sample_rate() const;
float duration_seconds() const;
std::optional<float> loop_point_seconds() const;
float position_seconds() const;
~OggPlayer();
};
extern template class OggPlayer<1>;
extern template class OggPlayer<2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_OGG_SOURCE_HPP__

81
src/audio/resample.cpp Normal file
View file

@ -0,0 +1,81 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "resample.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
#include <utility>
#include <vector>
using std::shared_ptr;
using std::size_t;
using std::vector;
using namespace srb2::audio;
template <size_t C>
Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio)
: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio) {
}
template <size_t C>
Resampler<C>::Resampler(Resampler<C>&& r) = default;
template <size_t C>
Resampler<C>::~Resampler() = default;
template <size_t C>
Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default;
template <size_t C>
size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) {
if (!source_)
return 0;
if (ratio_ == 1.f) {
// fast path - generate directly from source
size_t source_read = source_->generate(buffer);
return source_read;
}
size_t written = 0;
while (written < buffer.size()) {
// do we need a refill?
if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1)) {
pos_ -= buf_.size();
last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back();
buf_.clear();
buf_.resize(512);
size_t source_read = source_->generate(buf_);
buf_.resize(source_read);
if (source_read == 0) {
break;
}
}
if (pos_ < 0) {
buffer[written] = (buf_[0] - last_) * pos_frac_ + last_;
advance(ratio_);
written++;
continue;
}
buffer[written] = (buf_[pos_ + 1] - buf_[pos_]) * pos_frac_ + buf_[pos_];
advance(ratio_);
written++;
}
return written;
}
template class srb2::audio::Resampler<1>;
template class srb2::audio::Resampler<2>;

63
src/audio/resample.hpp Normal file
View file

@ -0,0 +1,63 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_RESAMPLE_HPP__
#define __SRB2_AUDIO_RESAMPLE_HPP__
#include <cmath>
#include <memory>
#include <optional>
#include <variant>
#include <vector>
#include <tcb/span.hpp>
#include "sound_chunk.hpp"
#include "source.hpp"
namespace srb2::audio {
template <size_t C>
class Resampler : public Source<C> {
public:
Resampler(std::shared_ptr<Source<C>>&& source_, float ratio);
Resampler(const Resampler<C>& r) = delete;
Resampler(Resampler<C>&& r);
virtual ~Resampler();
virtual std::size_t generate(tcb::span<Sample<C>> buffer);
Resampler& operator=(const Resampler<C>& r) = delete;
Resampler& operator=(Resampler<C>&& r);
private:
std::shared_ptr<Source<C>> source_;
float ratio_ {1.f};
std::vector<Sample<C>> buf_;
Sample<C> last_;
int pos_ {0};
float pos_frac_ {0.f};
void advance(float samples) {
pos_frac_ += samples;
float integer;
std::modf(pos_frac_, &integer);
pos_ += integer;
pos_frac_ -= integer;
}
void refill();
};
extern template class Resampler<1>;
extern template class Resampler<2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_RESAMPLE_HPP__

78
src/audio/sample.hpp Normal file
View file

@ -0,0 +1,78 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_SAMPLE_HPP__
#define __SRB2_AUDIO_SAMPLE_HPP__
#include <cstddef>
namespace srb2::audio {
template <size_t C>
struct Sample {
std::array<float, C> amplitudes;
constexpr Sample& operator+=(const Sample& rhs) noexcept {
for (std::size_t i = 0; i < C; i++) {
amplitudes[i] += rhs.amplitudes[i];
}
return *this;
}
constexpr Sample& operator*=(float rhs) noexcept {
for (std::size_t i = 0; i < C; i++) {
amplitudes[i] *= rhs;
}
return *this;
}
};
template <size_t C>
constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
Sample<C> out;
for (std::size_t i = 0; i < C; i++) {
out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i];
}
return out;
}
template <size_t C>
constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
Sample<C> out;
for (std::size_t i = 0; i < C; i++) {
out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i];
}
return out;
}
template <size_t C>
constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept {
Sample<C> out;
for (std::size_t i = 0; i < C; i++) {
out.amplitudes[i] = lhs.amplitudes[i] * rhs;
}
return out;
}
template <class T>
static constexpr float sample_to_float(T sample) noexcept;
template <>
constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept {
return (sample / 128.f) - 1.f;
}
template <>
constexpr float sample_to_float<int16_t>(int16_t sample) noexcept {
return sample / 32768.f;
}
} // namespace srb2::audio
#endif // __SRB2_AUDIO_SAMPLE_HPP__

25
src/audio/sound_chunk.hpp Normal file
View file

@ -0,0 +1,25 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_SOUND_CHUNK_HPP__
#define __SRB2_AUDIO_SOUND_CHUNK_HPP__
#include <vector>
#include "source.hpp"
namespace srb2::audio {
struct SoundChunk {
std::vector<Sample<1>> samples;
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_SOUND_CHUNK_HPP__

View file

@ -0,0 +1,72 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "sound_effect_player.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
using std::shared_ptr;
using std::size_t;
using srb2::audio::Sample;
using srb2::audio::SoundEffectPlayer;
using srb2::audio::Source;
size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) {
if (!chunk_)
return 0;
if (position_ >= chunk_->samples.size()) {
return 0;
}
size_t written = 0;
for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++) {
float mono_sample = chunk_->samples[position_].amplitudes[0];
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
float left_scale = std::cos(sep_pan);
float right_scale = std::sin(sep_pan);
buffer[written] = {mono_sample * volume_ * left_scale, mono_sample * volume_ * right_scale};
written += 1;
}
return written;
}
void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep) {
this->update(volume, sep);
position_ = 0;
chunk_ = chunk;
}
void SoundEffectPlayer::update(float volume, float sep) {
volume_ = volume;
sep_ = sep;
}
void SoundEffectPlayer::reset() {
position_ = 0;
chunk_ = nullptr;
}
bool SoundEffectPlayer::finished() const {
if (!chunk_)
return true;
if (position_ >= chunk_->samples.size())
return true;
return false;
}
bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const {
return chunk_ == chunk;
}
SoundEffectPlayer::~SoundEffectPlayer() = default;

View file

@ -0,0 +1,46 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__
#define __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__
#include <cstddef>
#include <tcb/span.hpp>
#include "sound_chunk.hpp"
#include "source.hpp"
namespace srb2::audio {
class SoundEffectPlayer : public Source<2> {
public:
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final;
virtual ~SoundEffectPlayer() final;
void start(const SoundChunk* chunk, float volume, float sep);
void update(float volume, float sep);
void reset();
bool finished() const;
bool is_playing_chunk(const SoundChunk* chunk) const;
private:
float volume_;
float sep_;
std::size_t position_;
const SoundChunk* chunk_;
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__

36
src/audio/source.hpp Normal file
View file

@ -0,0 +1,36 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_SOURCE_HPP__
#define __SRB2_AUDIO_SOURCE_HPP__
#include <array>
#include <tcb/span.hpp>
#include "sample.hpp"
namespace srb2::audio {
template <size_t C>
class Source {
public:
virtual std::size_t generate(tcb::span<Sample<C>> buffer) = 0;
virtual ~Source() = default;
};
// This audio DSP is Stereo, FP32 system-endian, 44100 Hz internally.
// Conversions to other formats should be handled elsewhere.
constexpr const std::size_t kSampleRate = 44100;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_SOURCE_HPP__

264
src/audio/wav.cpp Normal file
View file

@ -0,0 +1,264 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "wav.hpp"
#include <algorithm>
#include <optional>
#include <stdexcept>
using namespace srb2;
using srb2::audio::Wav;
namespace {
constexpr const uint32_t kMagicRIFF = 0x46464952;
constexpr const uint32_t kMagicWAVE = 0x45564157;
constexpr const uint32_t kMagicFmt = 0x20746d66;
constexpr const uint32_t kMagicData = 0x61746164;
constexpr const uint16_t kFormatPcm = 1;
constexpr const std::size_t kRiffHeaderLength = 8;
struct RiffHeader {
uint32_t magic;
std::size_t filesize;
};
struct TagHeader {
uint32_t type;
std::size_t length;
};
struct FmtTag {
uint16_t format;
uint16_t channels;
uint32_t rate;
uint32_t bytes_per_second;
uint32_t bytes_per_sample;
uint16_t bit_width;
};
struct DataTag {};
RiffHeader parse_riff_header(io::SpanStream& stream) {
if (io::remaining(stream) < kRiffHeaderLength)
throw std::runtime_error("insufficient bytes remaining in stream");
RiffHeader ret;
ret.magic = io::read_uint32(stream);
ret.filesize = io::read_uint32(stream);
return ret;
}
TagHeader parse_tag_header(io::SpanStream& stream) {
if (io::remaining(stream) < 8)
throw std::runtime_error("insufficient bytes remaining in stream");
TagHeader header;
header.type = io::read_uint32(stream);
header.length = io::read_uint32(stream);
return header;
}
FmtTag parse_fmt_tag(io::SpanStream& stream) {
if (io::remaining(stream) < 16)
throw std::runtime_error("insufficient bytes in stream");
FmtTag tag;
tag.format = io::read_uint16(stream);
tag.channels = io::read_uint16(stream);
tag.rate = io::read_uint32(stream);
tag.bytes_per_second = io::read_uint32(stream);
tag.bytes_per_sample = io::read_uint16(stream);
tag.bit_width = io::read_uint16(stream);
return tag;
}
template <typename Visitor>
void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header) {
if (io::remaining(stream) < header.length)
throw std::runtime_error("insufficient bytes in stream");
const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0);
const io::StreamSize dest = start + header.length;
switch (header.type) {
case kMagicFmt:
{
FmtTag fmt_tag {parse_fmt_tag(stream)};
visitor(fmt_tag);
break;
}
case kMagicData:
{
DataTag data_tag;
visitor(data_tag);
break;
}
default:
// Unrecognized tags are ignored.
break;
}
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> samples;
samples.reserve(count);
for (std::size_t i = 0; i < count; i++) {
samples.push_back(io::read_uint8(stream));
}
return samples;
}
std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) {
std::vector<int16_t> samples;
samples.reserve(count);
for (std::size_t i = 0; i < count; i++) {
samples.push_back(io::read_int16(stream));
}
return samples;
}
template <typename... Ts>
struct OverloadVisitor : Ts... {
using Ts::operator()...;
};
template <typename... Ts>
OverloadVisitor(Ts...) -> OverloadVisitor<Ts...>;
} // namespace
Wav::Wav() = default;
Wav::Wav(tcb::span<std::byte> data) {
io::SpanStream stream {data};
auto [magic, filesize] = parse_riff_header(stream);
if (magic != kMagicRIFF) {
throw std::runtime_error("invalid RIFF magic");
}
if (io::remaining(stream) < 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;
uint32_t type = io::read_uint32(stream);
if (type != kMagicWAVE) {
throw std::runtime_error("RIFF in stream is not a WAVE");
}
std::optional<FmtTag> read_fmt;
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples;
while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) {
TagHeader tag_header {parse_tag_header(stream)};
if (io::remaining(stream) < tag_header.length) {
throw std::runtime_error("WAVE tag length exceeds stream length");
}
auto tag_visitor = OverloadVisitor {
[&](const FmtTag& fmt) {
if (read_fmt) {
throw std::runtime_error("WAVE has multiple 'fmt' tags");
}
if (fmt.format != kFormatPcm) {
throw std::runtime_error("Unsupported WAVE format (only PCM is supported)");
}
read_fmt = fmt;
},
[&](const DataTag& data) {
if (!read_fmt) {
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) {
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;
switch (read_fmt->bit_width) {
case 8:
interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count));
break;
case 16:
interleaved_samples = std::move(read_int16_samples_from_stream(stream, sample_count));
break;
default:
throw std::runtime_error("unsupported sample amplitude bit width");
}
}};
visit_tag(tag_visitor, stream, tag_header);
}
if (!read_fmt) {
throw std::runtime_error("WAVE did not have a fmt tag");
}
interleaved_samples_ = std::move(interleaved_samples);
channels_ = read_fmt->channels;
sample_rate_ = read_fmt->rate;
}
namespace {
template <typename T>
std::size_t read_samples(std::size_t channels,
std::size_t offset,
const std::vector<T>& samples,
tcb::span<audio::Sample<1>> buffer) noexcept {
const std::size_t offset_interleaved = offset * channels;
const std::size_t samples_size = samples.size();
const std::size_t buffer_size = buffer.size();
if (offset_interleaved >= samples_size) {
return 0;
}
const std::size_t remainder = (samples_size - offset_interleaved) / channels;
const std::size_t samples_to_read = std::min(buffer_size, remainder);
for (std::size_t i = 0; i < samples_to_read; i++) {
buffer[i].amplitudes[0] = 0.f;
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] /= static_cast<float>(channels);
}
return samples_to_read;
}
} // namespace
std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept {
auto samples_visitor = OverloadVisitor {
[&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); },
[&](const std::vector<int16_t>& samples) {
return read_samples<int16_t>(channels(), offset, samples, buffer);
}};
return std::visit(samples_visitor, interleaved_samples_);
}
std::size_t Wav::interleaved_length() const noexcept {
auto samples_visitor = OverloadVisitor {[](const std::vector<uint8_t>& samples) { return samples.size(); },
[](const std::vector<int16_t>& samples) { return samples.size(); }};
return std::visit(samples_visitor, interleaved_samples_);
}

51
src/audio/wav.hpp Normal file
View file

@ -0,0 +1,51 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_WAV_HPP__
#define __SRB2_AUDIO_WAV_HPP__
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <variant>
#include <vector>
#include <tcb/span.hpp>
#include "../io/streams.hpp"
#include "sample.hpp"
namespace srb2::audio {
class Wav final {
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_;
std::size_t channels_ = 1;
std::size_t sample_rate_ = 44100;
public:
Wav();
explicit Wav(tcb::span<std::byte> data);
std::size_t get_samples(std::size_t offset, tcb::span<Sample<1>> buffer) const noexcept;
std::size_t interleaved_length() const noexcept;
std::size_t length() const noexcept { return interleaved_length() / channels(); };
std::size_t channels() const noexcept { return channels_; };
std::size_t sample_rate() const noexcept { return sample_rate_; };
};
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Wav load_wav(I& stream) {
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Wav {data};
}
} // namespace srb2::audio
#endif // __SRB2_AUDIO_WAV_HPP__

45
src/audio/wav_player.cpp Normal file
View file

@ -0,0 +1,45 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "wav_player.hpp"
using namespace srb2;
using srb2::audio::WavPlayer;
WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) {
}
WavPlayer::WavPlayer(const WavPlayer& rhs) = default;
WavPlayer::WavPlayer(WavPlayer&& rhs) noexcept = default;
WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default;
WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default;
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 samples_read = 0;
while (samples_read < buffer.size()) {
const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read));
position_ += read_this_time;
samples_read += read_this_time;
if (position_ > wav_.length() && looping_) {
position_ = 0;
}
if (read_this_time == 0 && !looping_) {
break;
}
}
return samples_read;
}

49
src/audio/wav_player.hpp Normal file
View file

@ -0,0 +1,49 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_WAV_PLAYER_HPP__
#define __SRB2_AUDIO_WAV_PLAYER_HPP__
#include <cstddef>
#include <tcb/span.hpp>
#include "source.hpp"
#include "wav.hpp"
namespace srb2::audio {
class WavPlayer final : public Source<1> {
Wav wav_;
std::size_t position_;
bool looping_;
public:
WavPlayer();
WavPlayer(const WavPlayer& rhs);
WavPlayer(WavPlayer&& rhs) noexcept;
WavPlayer& operator=(const WavPlayer& rhs);
WavPlayer& operator=(WavPlayer&& rhs) noexcept;
WavPlayer(Wav&& wav) noexcept;
virtual std::size_t generate(tcb::span<Sample<1>> buffer) override;
bool looping() const { return looping_; }
void looping(bool looping) { looping_ = looping; }
std::size_t sample_rate() const { return wav_.sample_rate(); }
float duration_seconds() const { return wav_.length() / static_cast<float>(wav_.sample_rate()); }
void seek(float seconds) { position_ = seconds * wav_.sample_rate(); }
};
} // namespace srb2::audio
#endif // __SRB2_AUDIO_WAV_PLAYER_HPP__

167
src/audio/xmp.cpp Normal file
View file

@ -0,0 +1,167 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "xmp.hpp"
#include <limits>
#include "../cxxutil.hpp"
using namespace srb2;
using namespace srb2::audio;
XmpException::XmpException(int code) : code_(code) {
}
const char* XmpException::what() const noexcept {
switch (code_) {
case -XMP_ERROR_INTERNAL:
return "XMP_ERROR_INTERNAL";
case -XMP_ERROR_FORMAT:
return "XMP_ERROR_FORMAT";
case -XMP_ERROR_LOAD:
return "XMP_ERROR_LOAD";
case -XMP_ERROR_DEPACK:
return "XMP_ERROR_DEPACK";
case -XMP_ERROR_SYSTEM:
return "XMP_ERROR_SYSTEM";
case -XMP_ERROR_INVALID:
return "XMP_ERROR_INVALID";
case -XMP_ERROR_STATE:
return "XMP_ERROR_STATE";
default:
return "unknown";
}
}
template <size_t C>
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) {
}
template <size_t C>
Xmp<C>::Xmp(std::vector<std::byte> data)
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) {
_init();
}
template <size_t C>
Xmp<C>::Xmp(tcb::span<std::byte> data)
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) {
_init();
}
template <size_t C>
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
std::swap(data_, rhs.data_);
std::swap(instance_, rhs.instance_);
std::swap(module_loaded_, rhs.module_loaded_);
std::swap(looping_, rhs.looping_);
}
template <size_t C>
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept {
std::swap(data_, rhs.data_);
std::swap(instance_, rhs.instance_);
std::swap(module_loaded_, rhs.module_loaded_);
std::swap(looping_, rhs.looping_);
return *this;
};
template <size_t C>
Xmp<C>::~Xmp() {
if (instance_) {
xmp_free_context(instance_);
instance_ = nullptr;
}
}
template <size_t C>
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
int result = xmp_play_buffer(instance_, buffer.data(), buffer.size_bytes(), !looping_);
if (result == -XMP_END)
return 0;
if (result != 0)
throw XmpException(result);
return buffer.size();
}
template <size_t C>
void Xmp<C>::reset() {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
xmp_restart_module(instance_);
}
template <size_t C>
float Xmp<C>::duration_seconds() const {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
xmp_frame_info info;
xmp_get_frame_info(instance_, &info);
return static_cast<float>(info.total_time) / 1000.f;
}
template <size_t C>
void Xmp<C>::seek(int position_ms) {
SRB2_ASSERT(instance_ != nullptr);
SRB2_ASSERT(module_loaded_ == true);
int err = xmp_seek_time(instance_, position_ms);
if (err != 0)
throw XmpException(err);
}
template <size_t C>
void Xmp<C>::_init() {
if (instance_)
return;
if (data_.size() >= std::numeric_limits<long>::max())
throw std::logic_error("Buffer is too large for xmp");
if (data_.size() == 0)
throw std::logic_error("Insufficient data from stream");
instance_ = xmp_create_context();
if (instance_ == nullptr) {
throw std::bad_alloc();
}
int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size());
if (result != 0) {
xmp_free_context(instance_);
instance_ = nullptr;
throw XmpException(result);
}
module_loaded_ = true;
int flags = 0;
if constexpr (C == 1) {
flags |= XMP_FORMAT_MONO;
}
result = xmp_start_player(instance_, 44100, flags);
if (result != 0) {
xmp_release_module(instance_);
module_loaded_ = false;
xmp_free_context(instance_);
instance_ = nullptr;
throw XmpException(result);
}
}
template class srb2::audio::Xmp<1>;
template class srb2::audio::Xmp<2>;

78
src/audio/xmp.hpp Normal file
View file

@ -0,0 +1,78 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_XMP_HPP__
#define __SRB2_AUDIO_XMP_HPP__
#include <array>
#include <cstddef>
#include <exception>
#include <stdexcept>
#include <utility>
#include <vector>
#include <tcb/span.hpp>
#include <xmp.h>
#include "../io/streams.hpp"
namespace srb2::audio {
class XmpException : public std::exception {
int code_;
public:
XmpException(int code);
virtual const char* what() const noexcept override final;
};
template <size_t C>
class Xmp final {
std::vector<std::byte> data_;
xmp_context instance_;
bool module_loaded_;
bool looping_;
public:
Xmp();
explicit Xmp(std::vector<std::byte> data);
explicit Xmp(tcb::span<std::byte> data);
Xmp(const Xmp<C>&) = delete;
Xmp(Xmp<C>&& rhs) noexcept;
Xmp& operator=(const Xmp&) = delete;
Xmp& operator=(Xmp&& rhs) noexcept;
std::size_t play_buffer(tcb::span<std::array<int16_t, C>> buffer);
bool looping() const { return looping_; };
void looping(bool looping) { looping_ = looping; };
void reset();
float duration_seconds() const;
void seek(int position_ms);
~Xmp();
private:
void _init();
};
extern template class Xmp<1>;
extern template class Xmp<2>;
template <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
inline Xmp<C> load_xmp(I& stream) {
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
return Xmp<C> {std::move(data)};
}
} // namespace srb2::audio
#endif // __SRB2_AUDIO_XMP_HPP__

57
src/audio/xmp_player.cpp Normal file
View file

@ -0,0 +1,57 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "xmp_player.hpp"
#include <cmath>
using namespace srb2;
using namespace srb2::audio;
template <size_t C>
XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_() {
}
template <size_t C>
XmpPlayer<C>::XmpPlayer(XmpPlayer&& rhs) noexcept = default;
template <size_t C>
XmpPlayer<C>& XmpPlayer<C>::operator=(XmpPlayer<C>&& rhs) noexcept = default;
template <size_t C>
XmpPlayer<C>::~XmpPlayer() = default;
template <size_t C>
std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
buf_.resize(buffer.size());
std::size_t read = xmp_.play_buffer(tcb::make_span(buf_));
buf_.resize(read);
std::size_t ret = std::min(buffer.size(), buf_.size());
for (std::size_t i = 0; i < ret; i++) {
for (std::size_t j = 0; j < C; j++) {
buffer[i].amplitudes[j] = buf_[i][j] / 32768.f;
}
}
return ret;
}
template <size_t C>
float XmpPlayer<C>::duration_seconds() const {
return xmp_.duration_seconds();
}
template <size_t C>
void XmpPlayer<C>::seek(float position_seconds) {
xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f)));
}
template class srb2::audio::XmpPlayer<1>;
template class srb2::audio::XmpPlayer<2>;

48
src/audio/xmp_player.hpp Normal file
View file

@ -0,0 +1,48 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_AUDIO_XMP_PLAYER_HPP__
#define __SRB2_AUDIO_XMP_PLAYER_HPP__
#include "source.hpp"
#include "xmp.hpp"
namespace srb2::audio {
template <size_t C>
class XmpPlayer final : public Source<C> {
Xmp<C> xmp_;
std::vector<std::array<int16_t, C>> buf_;
public:
XmpPlayer(Xmp<C>&& xmp);
XmpPlayer(const XmpPlayer<C>&) = delete;
XmpPlayer(XmpPlayer<C>&& rhs) noexcept;
XmpPlayer<C>& operator=(const XmpPlayer<C>&) = delete;
XmpPlayer<C>& operator=(XmpPlayer<C>&& rhs) noexcept;
~XmpPlayer();
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;
bool looping() { return xmp_.looping(); };
void looping(bool looping) { xmp_.looping(looping); }
void reset() { xmp_.reset(); }
float duration_seconds() const;
void seek(float position_seconds);
};
extern template class XmpPlayer<1>;
extern template class XmpPlayer<2>;
} // namespace srb2::audio
#endif // __SRB2_AUDIO_XMP_PLAYER_HPP__

View file

@ -1,3 +1,12 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "streams.hpp"
template class srb2::io::ZlibInputStream<srb2::io::SpanStream>;

View file

@ -1,3 +1,12 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_IO_STREAMS_HPP__
#define __SRB2_IO_STREAMS_HPP__
@ -409,7 +418,7 @@ public:
switch (seek_from) {
case SeekFrom::kStart:
if (offset < 0 || offset >= static_cast<StreamOffset>(span_.size())) {
if (offset < 0) {
throw std::logic_error("start offset is out of bounds");
}
head = offset;
@ -421,7 +430,7 @@ public:
head = span_.size() - offset;
break;
case SeekFrom::kCurrent:
if (head_ + offset < 0 || head_ + offset >= span_.size()) {
if (head_ + offset < 0) {
throw std::logic_error("offset is out of bounds");
}
head = head_ + offset;
@ -480,7 +489,7 @@ public:
switch (seek_from) {
case SeekFrom::kStart:
if (offset < 0 || offset >= static_cast<StreamOffset>(vec_.size())) {
if (offset < 0) {
throw std::logic_error("start offset is out of bounds");
}
head = offset;
@ -492,7 +501,7 @@ public:
head = vec_.size() - offset;
break;
case SeekFrom::kCurrent:
if (head_ + offset < 0 || head_ + offset >= vec_.size()) {
if (head_ + offset < 0) {
throw std::logic_error("offset is out of bounds");
}
head = head_ + offset;

View file

@ -1,12 +1,12 @@
# Declare SDL2 interface sources
target_sources(SRB2SDL2 PRIVATE
mixer_sound.c
new_sound.cpp
ogl_sdl.c
i_threads.c
i_net.c
i_system.c
i_main.c
i_main.cpp
i_video.c
dosstr.c
endtxt.c
@ -57,9 +57,9 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Darwin)
endif()
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static SDL2_mixer::SDL2_mixer-static)
target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static)
else()
target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2 SDL2_mixer::SDL2_mixer)
target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2)
endif()
if("${CMAKE_SYSTEM_NAME}" MATCHES Linux)

View file

@ -23,6 +23,10 @@
#include "../m_misc.h"/* path shit */
#include "../i_system.h"
#include <exception>
#include <stdexcept>
#include <string>
#if defined (__GNUC__) || defined (__unix__)
#include <unistd.h>
#endif
@ -31,7 +35,9 @@
#include <errno.h>
#endif
extern "C" {
#include "time.h" // For log timestamps
}
#ifdef HAVE_SDL
@ -70,7 +76,9 @@ char logfilename[1024];
#endif
#if defined (_WIN32)
extern "C" {
#include "../win32/win_dbg.h"
}
typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID);
#endif
@ -151,20 +159,20 @@ static void InitLogging(void)
if (M_IsPathAbsolute(reldir))
{
left = snprintf(logfilename, sizeof logfilename,
"%s"PATHSEP, reldir);
"%s" PATHSEP, reldir);
}
else
#ifdef DEFAULTDIR
if (logdir)
{
left = snprintf(logfilename, sizeof logfilename,
"%s"PATHSEP DEFAULTDIR PATHSEP"%s"PATHSEP, logdir, reldir);
"%s" PATHSEP DEFAULTDIR PATHSEP "%s" PATHSEP, logdir, reldir);
}
else
#endif/*DEFAULTDIR*/
{
left = snprintf(logfilename, sizeof logfilename,
"."PATHSEP"%s"PATHSEP, reldir);
"." PATHSEP "%s" PATHSEP, reldir);
}
strftime(&logfilename[left], sizeof logfilename - left,
@ -208,6 +216,33 @@ ChDirToExe (void)
}
#endif
static void walk_exception_stack(std::string& accum, bool nested) {
if (nested)
accum.append("\n Caused by: Unknown exception");
else
accum.append("Uncaught exception: Unknown exception");
}
static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) {
if (nested)
accum.append("\n Caused by: ");
else
accum.append("Uncaught exception: ");
accum.append("(");
accum.append(typeid(ex).name());
accum.append(") ");
accum.append(ex.what());
try {
std::rethrow_if_nested(ex);
} catch (const std::exception& ex) {
walk_exception_stack(accum, ex, true);
} catch (...) {
walk_exception_stack(accum, true);
}
}
/** \brief The main function
@ -268,6 +303,8 @@ int main(int argc, char **argv)
MakeCodeWritable();
#endif
try {
// startup SRB2
CONS_Printf("Setting up Dr. Robotnik's Ring Racers...\n");
D_SRB2Main();
@ -279,6 +316,16 @@ int main(int argc, char **argv)
// never return
D_SRB2Loop();
} catch (const std::exception& ex) {
std::string exception;
walk_exception_stack(exception, ex, false);
I_Error("%s", exception.c_str());
} catch (...) {
std::string exception;
walk_exception_stack(exception, false);
I_Error("%s", exception.c_str());
}
#ifdef BUGTRAP
// This is safe even if BT didn't start.
ShutdownBugTrap();

665
src/sdl/new_sound.cpp Normal file
View file

@ -0,0 +1,665 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <cmath>
#include <memory>
#include <SDL.h>
#include "../audio/chunk_load.hpp"
#include "../audio/gain.hpp"
#include "../audio/mixer.hpp"
#include "../audio/music_player.hpp"
#include "../audio/sound_chunk.hpp"
#include "../audio/sound_effect_player.hpp"
#include "../cxxutil.hpp"
#include "../io/streams.hpp"
#include "../doomdef.h"
#include "../i_sound.h"
#include "../s_sound.h"
#include "../sounds.h"
#include "../w_wad.h"
#include "../z_zone.h"
using std::make_shared;
using std::make_unique;
using std::shared_ptr;
using std::unique_ptr;
using std::vector;
using srb2::audio::Gain;
using srb2::audio::Mixer;
using srb2::audio::MusicPlayer;
using srb2::audio::Sample;
using srb2::audio::SoundChunk;
using srb2::audio::SoundEffectPlayer;
using srb2::audio::Source;
using namespace srb2;
using namespace srb2::io;
// extern in i_sound.h
UINT8 sound_started = false;
static unique_ptr<Mixer<2>> master;
static shared_ptr<Mixer<2>> mixer_sound_effects;
static shared_ptr<Mixer<2>> mixer_music;
static shared_ptr<MusicPlayer> music_player;
static shared_ptr<Gain<2>> gain_sound_effects;
static shared_ptr<Gain<2>> gain_music;
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
static void (*music_fade_callback)();
void* I_GetSfx(sfxinfo_t* sfx) {
if (sfx->lumpnum == LUMPERROR)
sfx->lumpnum = S_GetSfxLumpNum(sfx);
sfx->length = W_LumpLength(sfx->lumpnum);
std::byte* lump = static_cast<std::byte*>(W_CacheLumpNum(sfx->lumpnum, PU_SOUND));
auto _ = srb2::finally([lump]() { Z_Free(lump); });
tcb::span<std::byte> data_span(lump, sfx->length);
std::optional<SoundChunk> chunk = srb2::audio::try_load_chunk(data_span);
if (!chunk)
return nullptr;
SoundChunk* heap_chunk = new SoundChunk {std::move(*chunk)};
return heap_chunk;
}
void I_FreeSfx(sfxinfo_t* sfx) {
if (sfx->data) {
SoundChunk* chunk = static_cast<SoundChunk*>(sfx->data);
auto _ = srb2::finally([chunk]() { delete chunk; });
// Stop any channels playing this chunk
for (auto& player : sound_effect_channels) {
if (player->is_playing_chunk(chunk)) {
player->reset();
}
}
}
sfx->data = nullptr;
sfx->lumpnum = LUMPERROR;
}
namespace {
class SdlAudioLockHandle {
public:
SdlAudioLockHandle() { SDL_LockAudio(); }
~SdlAudioLockHandle() { SDL_UnlockAudio(); }
};
void audio_callback(void* userdata, Uint8* buffer, int len) {
// The SDL Audio lock is implied to be held during callback.
try {
Sample<2>* float_buffer = reinterpret_cast<Sample<2>*>(buffer);
size_t float_len = len / 8;
for (size_t i = 0; i < float_len; i++) {
float_buffer[i] = Sample<2> {0.f, 0.f};
}
if (!master)
return;
master->generate(tcb::span {float_buffer, float_len});
for (size_t i = 0; i < float_len; i++) {
float_buffer[i] = {
std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f),
std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f),
};
}
} catch (...) {
}
return;
}
void initialize_sound() {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
return;
}
SDL_AudioSpec desired;
desired.format = AUDIO_F32SYS;
desired.channels = 2;
desired.samples = 1024;
desired.freq = 44100;
desired.callback = audio_callback;
if (SDL_OpenAudio(&desired, NULL) < 0) {
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return;
}
SDL_PauseAudio(SDL_FALSE);
{
SdlAudioLockHandle _;
master = make_unique<Mixer<2>>();
mixer_sound_effects = make_shared<Mixer<2>>();
mixer_music = make_shared<Mixer<2>>();
music_player = make_shared<MusicPlayer>();
gain_sound_effects = make_shared<Gain<2>>();
gain_music = make_shared<Gain<2>>();
gain_sound_effects->bind(mixer_sound_effects);
gain_music->bind(mixer_music);
master->add_source(gain_sound_effects);
master->add_source(gain_music);
mixer_music->add_source(music_player);
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++) {
shared_ptr<SoundEffectPlayer> player = make_shared<SoundEffectPlayer>();
sound_effect_channels.push_back(player);
mixer_sound_effects->add_source(player);
}
}
sound_started = true;
}
} // namespace
void I_StartupSound(void) {
if (!sound_started)
initialize_sound();
}
void I_ShutdownSound(void) {
SdlAudioLockHandle _;
for (auto& channel : sound_effect_channels) {
*channel = audio::SoundEffectPlayer();
}
}
void I_UpdateSound(void) {
// The SDL audio lock is re-entrant, so it is safe to lock twice
// for the "fade to stop music" callback later.
SdlAudioLockHandle _;
if (music_fade_callback && !music_player->fading()) {
auto old_callback = music_fade_callback;
music_fade_callback = nullptr;
(old_callback());
}
return;
}
//
// SFX I/O
//
INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel) {
(void) pitch;
(void) priority;
SdlAudioLockHandle _;
if (channel >= 0 && static_cast<size_t>(channel) >= sound_effect_channels.size())
return -1;
shared_ptr<SoundEffectPlayer> player_channel;
if (channel < 0) {
// find a free sfx channel
for (size_t i = 0; i < sound_effect_channels.size(); i++) {
if (sound_effect_channels[i]->finished()) {
player_channel = sound_effect_channels[i];
channel = i;
break;
}
}
} else {
player_channel = sound_effect_channels[channel];
}
if (!player_channel)
return -1;
SoundChunk* chunk = static_cast<SoundChunk*>(S_sfx[id].data);
if (chunk == nullptr)
return -1;
float vol_float = static_cast<float>(vol) / 255.f;
float sep_float = static_cast<float>(sep) / 127.f - 1.f;
player_channel->start(chunk, vol_float, sep_float);
return channel;
}
void I_StopSound(INT32 handle) {
SdlAudioLockHandle _;
if (sound_effect_channels.empty())
return;
if (handle < 0)
return;
size_t index = handle;
if (index >= sound_effect_channels.size())
return;
sound_effect_channels[index]->reset();
}
boolean I_SoundIsPlaying(INT32 handle) {
SdlAudioLockHandle _;
// Handle is channel index
if (sound_effect_channels.empty())
return 0;
if (handle < 0)
return 0;
size_t index = handle;
if (index >= sound_effect_channels.size())
return 0;
return sound_effect_channels[index]->finished() ? 0 : 1;
}
void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) {
(void) pitch;
SdlAudioLockHandle _;
if (sound_effect_channels.empty())
return;
if (handle < 0)
return;
size_t index = handle;
if (index >= sound_effect_channels.size())
return;
shared_ptr<SoundEffectPlayer>& channel = sound_effect_channels[index];
if (!channel->finished()) {
float vol_float = static_cast<float>(vol) / 255.f;
float sep_float = static_cast<float>(sep) / 127.f - 1.f;
channel->update(vol_float, sep_float);
}
}
void I_SetSfxVolume(int volume) {
SdlAudioLockHandle _;
float vol = static_cast<float>(volume) / 100.f;
if (gain_sound_effects) {
gain_sound_effects->gain(vol * vol * vol);
}
}
/// ------------------------
// MUSIC SYSTEM
/// ------------------------
void I_InitMusic(void) {
if (!sound_started)
initialize_sound();
SdlAudioLockHandle _;
*music_player = audio::MusicPlayer();
}
void I_ShutdownMusic(void) {
SdlAudioLockHandle _;
*music_player = audio::MusicPlayer();
}
/// ------------------------
// MUSIC PROPERTIES
/// ------------------------
musictype_t I_SongType(void) {
if (!music_player)
return MU_NONE;
SdlAudioLockHandle _;
std::optional<audio::MusicType> music_type = music_player->music_type();
if (music_type == std::nullopt) {
return MU_NONE;
}
switch (*music_type) {
case audio::MusicType::kOgg:
return MU_OGG;
case audio::MusicType::kGme:
return MU_GME;
case audio::MusicType::kMod:
return MU_MOD;
default:
return MU_NONE;
}
}
boolean I_SongPlaying(void) {
if (!music_player)
return false;
SdlAudioLockHandle _;
return music_player->music_type().has_value();
}
boolean I_SongPaused(void) {
if (!music_player)
return false;
SdlAudioLockHandle _;
return !music_player->playing();
}
/// ------------------------
// MUSIC EFFECTS
/// ------------------------
boolean I_SetSongSpeed(float speed) {
(void) speed;
return false;
}
/// ------------------------
// MUSIC SEEKING
/// ------------------------
UINT32 I_GetSongLength(void) {
if (!music_player)
return 0;
SdlAudioLockHandle _;
std::optional<float> duration = music_player->duration_seconds();
if (!duration)
return 0;
return static_cast<UINT32>(std::round(*duration * 1000.f));
}
boolean I_SetSongLoopPoint(UINT32 looppoint) {
if (!music_player)
return 0;
SdlAudioLockHandle _;
if (music_player->music_type() == audio::MusicType::kOgg) {
music_player->loop_point_seconds(looppoint / 1000.f);
return true;
}
return false;
}
UINT32 I_GetSongLoopPoint(void) {
if (!music_player)
return 0;
SdlAudioLockHandle _;
std::optional<float> loop_point_seconds = music_player->loop_point_seconds();
if (!loop_point_seconds)
return 0;
return static_cast<UINT32>(std::round(*loop_point_seconds * 1000.f));
}
boolean I_SetSongPosition(UINT32 position) {
if (!music_player)
return false;
SdlAudioLockHandle _;
music_player->seek(position / 1000.f);
return true;
}
UINT32 I_GetSongPosition(void) {
if (!music_player)
return 0;
SdlAudioLockHandle _;
std::optional<float> position_seconds = music_player->position_seconds();
if (!position_seconds)
return 0;
return static_cast<UINT32>(std::round(*position_seconds * 1000.f));
}
void I_UpdateSongLagThreshold(void) {
}
void I_UpdateSongLagConditions(void) {
}
/// ------------------------
// MUSIC PLAYBACK
/// ------------------------
namespace {
void print_walk_ex_stack(const std::exception& ex) {
CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what());
try {
std::rethrow_if_nested(ex);
} catch (const std::exception& ex) {
print_walk_ex_stack(ex);
}
}
void print_ex(const std::exception& ex) {
CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what());
try {
std::rethrow_if_nested(ex);
} catch (const std::exception& ex) {
print_walk_ex_stack(ex);
}
}
} // namespace
boolean I_LoadSong(char* data, size_t len) {
if (!music_player)
return false;
tcb::span<std::byte> data_span(reinterpret_cast<std::byte*>(data), len);
audio::MusicPlayer new_player;
try {
new_player = audio::MusicPlayer {data_span};
} catch (const std::exception& ex) {
print_ex(ex);
return false;
}
if (music_fade_callback && music_player->fading()) {
auto old_callback = music_fade_callback;
music_fade_callback = nullptr;
(old_callback)();
}
SdlAudioLockHandle _;
try {
*music_player = std::move(new_player);
} catch (const std::exception& ex) {
print_ex(ex);
return false;
}
return true;
}
void I_UnloadSong(void) {
if (!music_player)
return;
if (music_fade_callback && music_player->fading()) {
auto old_callback = music_fade_callback;
music_fade_callback = nullptr;
(old_callback)();
}
SdlAudioLockHandle _;
*music_player = audio::MusicPlayer();
}
boolean I_PlaySong(boolean looping) {
if (!music_player)
return false;
SdlAudioLockHandle _;
music_player->play(looping);
return true;
}
void I_StopSong(void) {
if (!music_player)
return;
SdlAudioLockHandle _;
music_player->stop();
}
void I_PauseSong(void) {
if (!music_player)
return;
SdlAudioLockHandle _;
music_player->pause();
}
void I_ResumeSong(void) {
if (!music_player)
return;
SdlAudioLockHandle _;
music_player->unpause();
}
void I_SetMusicVolume(int volume) {
float vol = static_cast<float>(volume) / 100.f;
if (gain_music) {
gain_music->gain(vol * vol * vol);
}
}
boolean I_SetSongTrack(int track) {
(void) track;
return false;
}
/// ------------------------
// MUSIC FADING
/// ------------------------
void I_SetInternalMusicVolume(UINT8 volume) {
if (!music_player)
return;
SdlAudioLockHandle _;
float gain = volume / 100.f;
music_player->internal_gain(gain);
}
void I_StopFadingSong(void) {
if (!music_player)
return;
SdlAudioLockHandle _;
music_player->stop_fade();
}
boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) {
if (!music_player)
return false;
SdlAudioLockHandle _;
float source_gain = source_volume / 100.f;
float target_gain = target_volume / 100.f;
float seconds = ms / 1000.f;
music_player->fade_from_to(source_gain, target_gain, seconds);
if (music_fade_callback)
music_fade_callback();
music_fade_callback = callback;
return true;
}
boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) {
if (!music_player)
return false;
SdlAudioLockHandle _;
float target_gain = target_volume / 100.f;
float seconds = ms / 1000.f;
music_player->fade_to(target_gain, seconds);
if (music_fade_callback)
music_fade_callback();
music_fade_callback = callback;
return true;
}
static void stop_song_cb(void) {
if (!music_player)
return;
SdlAudioLockHandle _;
music_player->stop();
}
boolean I_FadeOutStopSong(UINT32 ms) {
return I_FadeSong(0.f, ms, stop_song_cb);
}
boolean I_FadeInPlaySong(UINT32 ms, boolean looping) {
if (I_PlaySong(looping))
return I_FadeSongFromVolume(100, 0, ms, nullptr);
else
return false;
}

View file

@ -26,31 +26,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
)
endif()
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
CPMAddPackage(
NAME SDL2_mixer
VERSION 2.6.2
URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip"
EXCLUDE_FROM_ALL ON
OPTIONS
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
"SDL2MIXER_INSTALL OFF"
"SDL2MIXER_DEPS_SHARED OFF"
"SDL2MIXER_SAMPLES OFF"
"SDL2MIXER_VENDORED ON"
"SDL2MIXER_FLAC ON"
"SDL2MIXER_FLAC_LIBFLAC OFF"
"SDL2MIXER_FLAC_DRFLAC ON"
"SDL2MIXER_MOD OFF"
"SDL2MIXER_MP3 ON"
"SDL2MIXER_MP3_DRMP3 ON"
"SDL2MIXER_MIDI ON"
"SDL2MIXER_OPUS OFF"
"SDL2MIXER_VORBIS STB"
"SDL2MIXER_WAVE ON"
)
endif()
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
CPMAddPackage(
NAME ZLIB
@ -221,298 +196,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
)
endif()
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
CPMAddPackage(
NAME openmpt
VERSION 0.4.30
URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip"
DOWNLOAD_ONLY ON
)
if(openmpt_ADDED)
set(
openmpt_SOURCES
# minimp3
# -DMPT_WITH_MINIMP3
include/minimp3/minimp3.c
common/mptStringParse.cpp
common/mptLibrary.cpp
common/Logging.cpp
common/Profiler.cpp
common/version.cpp
common/mptCPU.cpp
common/ComponentManager.cpp
common/mptOS.cpp
common/serialization_utils.cpp
common/mptStringFormat.cpp
common/FileReader.cpp
common/mptWine.cpp
common/mptPathString.cpp
common/mptAlloc.cpp
common/mptUUID.cpp
common/mptTime.cpp
common/mptString.cpp
common/mptFileIO.cpp
common/mptStringBuffer.cpp
common/mptRandom.cpp
common/mptIO.cpp
common/misc_util.cpp
common/mptCRC.h
common/mptLibrary.h
common/mptIO.h
common/version.h
common/stdafx.h
common/ComponentManager.h
common/Endianness.h
common/mptStringFormat.h
common/mptMutex.h
common/mptUUID.h
common/mptExceptionText.h
common/BuildSettings.h
common/mptAlloc.h
common/mptTime.h
common/FileReaderFwd.h
common/Logging.h
common/mptException.h
common/mptWine.h
common/mptStringBuffer.h
common/misc_util.h
common/mptBaseMacros.h
common/mptMemory.h
common/mptFileIO.h
common/serialization_utils.h
common/mptSpan.h
common/mptThread.h
common/FlagSet.h
common/mptString.h
common/mptStringParse.h
common/mptBaseUtils.h
common/mptRandom.h
common/CompilerDetect.h
common/FileReader.h
common/mptAssert.h
common/mptPathString.h
common/Profiler.h
common/mptOS.h
common/mptBaseTypes.h
common/mptCPU.h
common/mptBufferIO.h
common/versionNumber.h
soundlib/WAVTools.cpp
soundlib/ITTools.cpp
soundlib/AudioCriticalSection.cpp
soundlib/Load_stm.cpp
soundlib/MixerLoops.cpp
soundlib/Load_dbm.cpp
soundlib/ModChannel.cpp
soundlib/Load_gdm.cpp
soundlib/Snd_fx.cpp
soundlib/Load_mid.cpp
soundlib/mod_specifications.cpp
soundlib/Snd_flt.cpp
soundlib/Load_psm.cpp
soundlib/Load_far.cpp
soundlib/patternContainer.cpp
soundlib/Load_med.cpp
soundlib/Load_dmf.cpp
soundlib/Paula.cpp
soundlib/modcommand.cpp
soundlib/Message.cpp
soundlib/SoundFilePlayConfig.cpp
soundlib/Load_uax.cpp
soundlib/plugins/PlugInterface.cpp
soundlib/plugins/LFOPlugin.cpp
soundlib/plugins/PluginManager.cpp
soundlib/plugins/DigiBoosterEcho.cpp
soundlib/plugins/dmo/DMOPlugin.cpp
soundlib/plugins/dmo/Flanger.cpp
soundlib/plugins/dmo/Distortion.cpp
soundlib/plugins/dmo/ParamEq.cpp
soundlib/plugins/dmo/Gargle.cpp
soundlib/plugins/dmo/I3DL2Reverb.cpp
soundlib/plugins/dmo/Compressor.cpp
soundlib/plugins/dmo/WavesReverb.cpp
soundlib/plugins/dmo/Echo.cpp
soundlib/plugins/dmo/Chorus.cpp
soundlib/Load_ams.cpp
soundlib/tuningbase.cpp
soundlib/ContainerUMX.cpp
soundlib/Load_ptm.cpp
soundlib/ContainerXPK.cpp
soundlib/SampleFormatMP3.cpp
soundlib/tuning.cpp
soundlib/Sndfile.cpp
soundlib/ContainerMMCMP.cpp
soundlib/Load_amf.cpp
soundlib/Load_669.cpp
soundlib/modsmp_ctrl.cpp
soundlib/Load_mtm.cpp
soundlib/OggStream.cpp
soundlib/Load_plm.cpp
soundlib/Tables.cpp
soundlib/Load_c67.cpp
soundlib/Load_mod.cpp
soundlib/Load_sfx.cpp
soundlib/Sndmix.cpp
soundlib/load_j2b.cpp
soundlib/ModSequence.cpp
soundlib/SampleFormatFLAC.cpp
soundlib/ModInstrument.cpp
soundlib/Load_mo3.cpp
soundlib/ModSample.cpp
soundlib/Dlsbank.cpp
soundlib/Load_itp.cpp
soundlib/UpgradeModule.cpp
soundlib/MIDIMacros.cpp
soundlib/ContainerPP20.cpp
soundlib/RowVisitor.cpp
soundlib/Load_imf.cpp
soundlib/SampleFormatVorbis.cpp
soundlib/Load_dsm.cpp
soundlib/Load_mt2.cpp
soundlib/MixerSettings.cpp
soundlib/S3MTools.cpp
soundlib/Load_xm.cpp
soundlib/MIDIEvents.cpp
soundlib/pattern.cpp
soundlib/Load_digi.cpp
soundlib/Load_s3m.cpp
soundlib/tuningCollection.cpp
soundlib/SampleIO.cpp
soundlib/Dither.cpp
soundlib/Load_mdl.cpp
soundlib/OPL.cpp
soundlib/WindowedFIR.cpp
soundlib/SampleFormats.cpp
soundlib/Load_wav.cpp
soundlib/Load_it.cpp
soundlib/UMXTools.cpp
soundlib/Load_stp.cpp
soundlib/Load_okt.cpp
soundlib/Load_ult.cpp
soundlib/MixFuncTable.cpp
soundlib/SampleFormatOpus.cpp
soundlib/Fastmix.cpp
soundlib/Tagging.cpp
soundlib/ITCompression.cpp
soundlib/Load_dtm.cpp
soundlib/MPEGFrame.cpp
soundlib/XMTools.cpp
soundlib/SampleFormatMediaFoundation.cpp
soundlib/InstrumentExtensions.cpp
soundlib/MixerInterface.h
soundlib/SoundFilePlayConfig.h
soundlib/ModSample.h
soundlib/MIDIEvents.h
soundlib/ModSampleCopy.h
soundlib/patternContainer.h
soundlib/ChunkReader.h
soundlib/ITCompression.h
soundlib/Dither.h
soundlib/S3MTools.h
soundlib/MPEGFrame.h
soundlib/WAVTools.h
soundlib/mod_specifications.h
soundlib/ITTools.h
soundlib/RowVisitor.h
soundlib/plugins/PluginMixBuffer.h
soundlib/plugins/PluginStructs.h
soundlib/plugins/LFOPlugin.h
soundlib/plugins/PlugInterface.h
soundlib/plugins/DigiBoosterEcho.h
soundlib/plugins/OpCodes.h
soundlib/plugins/dmo/Echo.h
soundlib/plugins/dmo/I3DL2Reverb.h
soundlib/plugins/dmo/WavesReverb.h
soundlib/plugins/dmo/ParamEq.h
soundlib/plugins/dmo/Gargle.h
soundlib/plugins/dmo/DMOPlugin.h
soundlib/plugins/dmo/Chorus.h
soundlib/plugins/dmo/Compressor.h
soundlib/plugins/dmo/Distortion.h
soundlib/plugins/dmo/Flanger.h
soundlib/plugins/PluginManager.h
soundlib/SampleIO.h
soundlib/Container.h
soundlib/ModSequence.h
soundlib/UMXTools.h
soundlib/Message.h
soundlib/modcommand.h
soundlib/XMTools.h
soundlib/Snd_defs.h
soundlib/MixFuncTable.h
soundlib/pattern.h
soundlib/modsmp_ctrl.h
soundlib/Tagging.h
soundlib/tuningcollection.h
soundlib/Mixer.h
soundlib/FloatMixer.h
soundlib/AudioCriticalSection.h
soundlib/Tables.h
soundlib/tuningbase.h
soundlib/WindowedFIR.h
soundlib/Sndfile.h
soundlib/Paula.h
soundlib/ModInstrument.h
soundlib/Dlsbank.h
soundlib/IntMixer.h
soundlib/OPL.h
soundlib/Resampler.h
soundlib/ModChannel.h
soundlib/MixerSettings.h
soundlib/AudioReadTarget.h
soundlib/MixerLoops.h
soundlib/tuning.h
soundlib/MIDIMacros.h
soundlib/OggStream.h
soundlib/Loaders.h
soundlib/BitReader.h
soundlib/opal.h
sounddsp/AGC.cpp
sounddsp/EQ.cpp
sounddsp/DSP.cpp
sounddsp/Reverb.cpp
sounddsp/Reverb.h
sounddsp/EQ.h
sounddsp/DSP.h
sounddsp/AGC.h
libopenmpt/libopenmpt_c.cpp
libopenmpt/libopenmpt_cxx.cpp
libopenmpt/libopenmpt_impl.cpp
libopenmpt/libopenmpt_ext_impl.cpp
)
list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/")
# -DLIBOPENMPT_BUILD
configure_file("openmpt_svn_version.h" "svn_version.h")
add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h)
if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang)
target_compile_options(openmpt PRIVATE "-g0")
endif()
if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC)
target_link_libraries(openmpt PRIVATE Rpcrt4)
endif()
target_compile_features(openmpt PRIVATE cxx_std_11)
target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD)
target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common")
target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src")
target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include")
target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}")
target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
# I wish this wasn't necessary, but it is
target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}")
endif()
endif()
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
CPMAddPackage(
NAME libgme
@ -605,4 +288,65 @@ if(DiscordRPC_ADDED)
endif()
endif()
CPMAddPackage(
NAME xmp-lite
VERSION 4.5.0
URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz"
EXCLUDE_FROM_ALL ON
DOWNLOAD_ONLY ON
)
if(xmp-lite_ADDED)
set(xmp_sources
virtual.c
format.c
period.c
player.c
read_event.c
misc.c
dataio.c
lfo.c
scan.c
control.c
filter.c
effects.c
mixer.c
mix_all.c
load_helpers.c
load.c
hio.c
smix.c
memio.c
win32.c
loaders/common.c
loaders/itsex.c
loaders/sample.c
loaders/xm_load.c
loaders/mod_load.c
loaders/s3m_load.c
loaders/it_load.c
)
list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/")
add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources})
target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS)
if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC")
if(WIN32)
# BUILDING_STATIC has to be public to work around a bug in xmp.h
# which adds __declspec(dllimport) even when statically linking
target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC)
else()
target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC)
endif()
else()
target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL)
endif()
target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src")
target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite")
add_library(xmp-lite::xmp-lite ALIAS xmp-lite)
endif()
add_subdirectory(tcbrindle_span)
add_subdirectory(stb_vorbis)

View file

@ -1,10 +0,0 @@
#pragma once
#define OPENMPT_VERSION_SVNVERSION "17963"
#define OPENMPT_VERSION_REVISION 17963
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.4.32"
#define OPENMPT_VERSION_DATE "2022-09-25T14:19:05.052596Z"
#define OPENMPT_VERSION_IS_PACKAGE 1

4
thirdparty/stb_vorbis/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,4 @@
# Update from https://github.com/nothings/stb
# This doesn't use CPM because stb_vorbis.c has a weird header setup
add_library(stb_vorbis STATIC stb_vorbis.c include/stb_vorbis.h)
target_include_directories(stb_vorbis PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")

View file

@ -0,0 +1,2 @@
#define STB_VORBIS_HEADER_ONLY
#include "../stb_vorbis.c"

5584
thirdparty/stb_vorbis/stb_vorbis.c vendored Normal file

File diff suppressed because it is too large Load diff