mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2026-01-09 08:22:28 +00:00
Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into save_p-unglobal
This commit is contained in:
commit
5c1235407b
73 changed files with 10160 additions and 975 deletions
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -311,15 +310,6 @@ if("${SRB2_CONFIG_HWRENDER}")
|
|||
endif()
|
||||
endif()
|
||||
|
||||
# TODO: build this with the game
|
||||
if(${CMAKE_SYSTEM} MATCHES Windows AND ${CMAKE_C_COMPILER_ID} MATCHES "GNU" AND ${SRB2_SYSTEM_BITS} EQUAL 32)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libexchndl.a"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libmgwhelp.a"
|
||||
)
|
||||
target_include_directories(SRB2SDL2 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/include")
|
||||
endif()
|
||||
|
||||
if(${SRB2_CONFIG_USEASM})
|
||||
#SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm.
|
||||
if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
|
||||
|
|
@ -402,7 +392,7 @@ target_compile_options(SRB2SDL2 PRIVATE
|
|||
-Winline
|
||||
-Wformat-y2k
|
||||
-Wformat-security
|
||||
|
||||
|
||||
$<$<VERSION_LESS:$<C_COMPILER_VERSION>,2.9.5>:
|
||||
-Wno-div-by-zero
|
||||
-Wendif-labels
|
||||
|
|
@ -538,6 +528,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
37
src/audio/CMakeLists.txt
Normal 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
206
src/audio/chunk_load.cpp
Normal 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
27
src/audio/chunk_load.hpp
Normal 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
26
src/audio/expand_mono.cpp
Normal 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
27
src/audio/expand_mono.hpp
Normal 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
40
src/audio/filter.cpp
Normal 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
46
src/audio/filter.hpp
Normal 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
43
src/audio/gain.cpp
Normal 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
33
src/audio/gain.hpp
Normal 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
141
src/audio/gme.cpp
Normal 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
74
src/audio/gme.hpp
Normal 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
73
src/audio/gme_player.cpp
Normal 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
51
src/audio/gme_player.hpp
Normal 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
62
src/audio/mixer.cpp
Normal 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
41
src/audio/mixer.hpp
Normal 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
421
src/audio/music_player.cpp
Normal 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);
|
||||
}
|
||||
69
src/audio/music_player.hpp
Normal file
69
src/audio/music_player.hpp
Normal 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
194
src/audio/ogg.cpp
Normal 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
81
src/audio/ogg.hpp
Normal 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
141
src/audio/ogg_player.cpp
Normal 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
72
src/audio/ogg_player.hpp
Normal 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
81
src/audio/resample.cpp
Normal 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
63
src/audio/resample.hpp
Normal 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
78
src/audio/sample.hpp
Normal 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
25
src/audio/sound_chunk.hpp
Normal 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__
|
||||
72
src/audio/sound_effect_player.cpp
Normal file
72
src/audio/sound_effect_player.cpp
Normal 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;
|
||||
46
src/audio/sound_effect_player.hpp
Normal file
46
src/audio/sound_effect_player.hpp
Normal 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
36
src/audio/source.hpp
Normal 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
264
src/audio/wav.cpp
Normal 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
51
src/audio/wav.hpp
Normal 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
45
src/audio/wav_player.cpp
Normal 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
49
src/audio/wav_player.hpp
Normal 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
167
src/audio/xmp.cpp
Normal 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
78
src/audio/xmp.hpp
Normal 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
57
src/audio/xmp_player.cpp
Normal 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
48
src/audio/xmp_player.hpp
Normal 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__
|
||||
|
|
@ -138,7 +138,7 @@ char srb2home[256] = ".";
|
|||
char srb2path[256] = ".";
|
||||
boolean usehome = true;
|
||||
const char *pandf = "%s" PATHSEP "%s";
|
||||
static char addonsdir[MAX_WADPATH];
|
||||
char addonsdir[MAX_WADPATH];
|
||||
|
||||
//
|
||||
// EVENT HANDLING
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ extern char srb2home[256]; //Alam: My Home
|
|||
extern boolean usehome; //Alam: which path?
|
||||
extern const char *pandf; //Alam: how to path?
|
||||
extern char srb2path[256]; //Alam: SRB2's Home
|
||||
extern char addonsdir[MAX_WADPATH]; // Where addons are stored
|
||||
|
||||
// the infinite loop of D_SRB2Loop() called from win_main for windows version
|
||||
void D_SRB2Loop(void) FUNCNORETURN;
|
||||
|
|
|
|||
|
|
@ -310,13 +310,13 @@ closedir (DIR * dirp)
|
|||
}
|
||||
#endif
|
||||
|
||||
static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"},
|
||||
static CV_PossibleValue_t addons_cons_t[] = {{0, "Addons"},
|
||||
#if 1
|
||||
{1, "HOME"}, {2, "RINGRACERS"},
|
||||
{1, "HOME"}, {2, "IWAD"},
|
||||
#endif
|
||||
{3, "CUSTOM"}, {0, NULL}};
|
||||
|
||||
consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange);
|
||||
consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Addons", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange);
|
||||
consvar_t cv_addons_folder = CVAR_INIT ("addons_folder", "", CV_SAVE, NULL, NULL);
|
||||
|
||||
static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}};
|
||||
|
|
|
|||
|
|
@ -789,6 +789,8 @@ char sprnames[NUMSPRITES + 1][5] =
|
|||
"UFOA",
|
||||
"UFOS",
|
||||
|
||||
"UQMK",
|
||||
|
||||
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
||||
"VIEW",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1340,6 +1340,8 @@ typedef enum sprite
|
|||
SPR_UFOA,
|
||||
SPR_UFOS,
|
||||
|
||||
SPR_UQMK,
|
||||
|
||||
// First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later
|
||||
SPR_VIEW,
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
59
src/k_hud.c
59
src/k_hud.c
|
|
@ -785,6 +785,29 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item)
|
||||
{
|
||||
UINT8 offset;
|
||||
|
||||
item = K_ItemResultToType(item);
|
||||
|
||||
switch (item)
|
||||
{
|
||||
case KITEM_INVINCIBILITY:
|
||||
offset = 7;
|
||||
break;
|
||||
|
||||
case KITEM_ORBINAUT:
|
||||
offset = 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
return K_GetCachedItemPatch(item, offset);
|
||||
}
|
||||
|
||||
//}
|
||||
|
||||
INT32 ITEM_X, ITEM_Y; // Item Window
|
||||
|
|
@ -4930,39 +4953,6 @@ K_drawMiniPing (void)
|
|||
|
||||
static void K_drawDistributionDebugger(void)
|
||||
{
|
||||
patch_t *patches[NUMKARTRESULTS] = {
|
||||
kp_sadface[1],
|
||||
kp_sneaker[1],
|
||||
kp_rocketsneaker[1],
|
||||
kp_invincibility[7],
|
||||
kp_banana[1],
|
||||
kp_eggman[1],
|
||||
kp_orbinaut[4],
|
||||
kp_jawz[1],
|
||||
kp_mine[1],
|
||||
kp_landmine[1],
|
||||
kp_ballhog[1],
|
||||
kp_selfpropelledbomb[1],
|
||||
kp_grow[1],
|
||||
kp_shrink[1],
|
||||
kp_lightningshield[1],
|
||||
kp_bubbleshield[1],
|
||||
kp_flameshield[1],
|
||||
kp_hyudoro[1],
|
||||
kp_pogospring[1],
|
||||
kp_superring[1],
|
||||
kp_kitchensink[1],
|
||||
kp_droptarget[1],
|
||||
kp_gardentop[1],
|
||||
|
||||
kp_sneaker[1],
|
||||
kp_sneaker[1],
|
||||
kp_banana[1],
|
||||
kp_orbinaut[4],
|
||||
kp_orbinaut[4],
|
||||
kp_jawz[1]
|
||||
};
|
||||
|
||||
itemroulette_t rouletteData = {0};
|
||||
|
||||
const fixed_t scale = (FRACUNIT >> 1);
|
||||
|
|
@ -4991,7 +4981,8 @@ static void K_drawDistributionDebugger(void)
|
|||
y = -pad;
|
||||
}
|
||||
|
||||
V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, patches[item], NULL);
|
||||
V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP,
|
||||
K_GetSmallStaticCachedItemPatch(item), NULL);
|
||||
|
||||
// Display amount for multi-items
|
||||
amount = K_ItemResultToAmount(item);
|
||||
|
|
|
|||
|
|
@ -6219,19 +6219,18 @@ void K_DropHnextList(player_t *player, boolean keepshields)
|
|||
|
||||
SINT8 K_GetTotallyRandomResult(UINT8 useodds)
|
||||
{
|
||||
itemroulette_t rouletteData = {0};
|
||||
INT32 spawnchance[NUMKARTRESULTS];
|
||||
INT32 totalspawnchance = 0;
|
||||
INT32 i;
|
||||
|
||||
memset(spawnchance, 0, sizeof (spawnchance));
|
||||
|
||||
K_FillItemRouletteData(NULL, &rouletteData);
|
||||
|
||||
for (i = 1; i < NUMKARTRESULTS; i++)
|
||||
{
|
||||
// Avoid calling K_FillItemRouletteData since that
|
||||
// function resets PR_ITEM_ROULETTE.
|
||||
spawnchance[i] = (
|
||||
totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i)
|
||||
totalspawnchance += K_KartGetItemOdds(NULL, NULL, useodds, i)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1926,8 +1926,6 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch)
|
|||
INT16 map, start = M_GetFirstLevelInList(&i, levelsearch);
|
||||
UINT8 starti = i;
|
||||
|
||||
V_DrawFill(0, y, BASEVIDWIDTH, 54, 31);
|
||||
|
||||
if (levelsearch->cup && maxlevels > 0)
|
||||
{
|
||||
add = (cupgrid.previewanim / 82) % maxlevels;
|
||||
|
|
@ -2060,7 +2058,9 @@ void M_DrawCupSelect(void)
|
|||
|
||||
templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID];
|
||||
|
||||
V_DrawFill(0, 146 + (24*menutransition.tics), BASEVIDWIDTH, 54, 31);
|
||||
M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch);
|
||||
|
||||
M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch);
|
||||
}
|
||||
|
||||
|
|
@ -4510,15 +4510,14 @@ void M_DrawAddons(void)
|
|||
|
||||
// Challenges Menu
|
||||
|
||||
#define challengesbordercolor 27
|
||||
|
||||
static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili)
|
||||
{
|
||||
unlockable_t *ref = NULL;
|
||||
patch_t *pat = missingpat;
|
||||
UINT8 *colormap = NULL;
|
||||
fixed_t siz;
|
||||
UINT8 id, num, work;
|
||||
UINT8 id, num;
|
||||
UINT32 edgelength;
|
||||
|
||||
id = (i * CHALLENGEGRIDHEIGHT) + j;
|
||||
num = gamedata->challengegrid[id];
|
||||
|
|
@ -4526,19 +4525,19 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
|
|||
// Empty spots in the grid are always unconnected.
|
||||
if (num >= MAXUNLOCKABLES)
|
||||
{
|
||||
V_DrawFill(x, y, 16, 16, challengesbordercolor);
|
||||
goto drawborder;
|
||||
}
|
||||
|
||||
// Okay, this is what we want to draw.
|
||||
ref = &unlockables[num];
|
||||
|
||||
edgelength = (ref->majorunlock ? 30 : 14);
|
||||
|
||||
// ...unless we simply aren't unlocked yet.
|
||||
if ((gamedata->unlocked[num] == false)
|
||||
|| (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME))
|
||||
{
|
||||
work = (ref->majorunlock) ? 2 : 1;
|
||||
V_DrawFill(x, y, 16*work, 16*work,
|
||||
V_DrawFill(x+1, y+1, edgelength, edgelength,
|
||||
((challengesmenu.extradata[id] == CHE_HINT) ? 132 : 11));
|
||||
goto drawborder;
|
||||
}
|
||||
|
|
@ -4589,6 +4588,12 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
|
|||
siz = (SHORT(pat->width) << FRACBITS);
|
||||
siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz);
|
||||
|
||||
V_SetClipRect(
|
||||
(x+1) << FRACBITS, (y+1) << FRACBITS,
|
||||
edgelength << FRACBITS, edgelength << FRACBITS,
|
||||
0
|
||||
);
|
||||
|
||||
V_DrawFixedPatch(
|
||||
x*FRACUNIT, y*FRACUNIT,
|
||||
siz,
|
||||
|
|
@ -4596,19 +4601,11 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
|
|||
colormap
|
||||
);
|
||||
|
||||
V_ClearClipRect();
|
||||
|
||||
drawborder:
|
||||
if (!hili)
|
||||
{
|
||||
if (ref != NULL)
|
||||
{
|
||||
work = 16 * (ref->majorunlock ? 2 : 1);
|
||||
// Horizontal
|
||||
V_DrawFill(x, y , work, 1, challengesbordercolor);
|
||||
V_DrawFill(x, y + work-1, work, 1, challengesbordercolor);
|
||||
// Vertical
|
||||
V_DrawFill(x , y+1, 1, work-2, challengesbordercolor);
|
||||
V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -4628,23 +4625,40 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
|
|||
|
||||
if (challengesmenu.currentunlock >= MAXUNLOCKABLES)
|
||||
{
|
||||
V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, this is what we want to draw.
|
||||
ref = &unlockables[challengesmenu.currentunlock];
|
||||
|
||||
// Funny question mark?
|
||||
if (!gamedata->unlocked[challengesmenu.currentunlock])
|
||||
{
|
||||
// todo draw some sort of question mark?
|
||||
V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor);
|
||||
spritedef_t *sprdef = &sprites[SPR_UQMK];
|
||||
spriteframe_t *sprframe;
|
||||
patch_t *patch;
|
||||
UINT32 useframe;
|
||||
UINT32 addflags = 0;
|
||||
|
||||
if (!sprdef->numframes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
useframe = (challengesmenu.ticker / 2) % sprdef->numframes;
|
||||
|
||||
sprframe = &sprdef->spriteframes[useframe];
|
||||
patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
|
||||
|
||||
if (sprframe->flip & 1) // Only for first sprite
|
||||
{
|
||||
addflags ^= V_FLIP; // This sprite is left/right flipped!
|
||||
}
|
||||
|
||||
V_DrawFixedPatch(x*FRACUNIT, (y+6)*FRACUNIT, FRACUNIT, addflags, patch, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ref->type != SECRET_CUP)
|
||||
V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor);
|
||||
|
||||
switch (ref->type)
|
||||
{
|
||||
case SECRET_SKIN:
|
||||
|
|
@ -4812,7 +4826,7 @@ void M_DrawChallenges(void)
|
|||
INT16 offset;
|
||||
|
||||
{
|
||||
patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE);
|
||||
patch_t *bg = W_CachePatchName("BGUNLCK2", PU_CACHE);
|
||||
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
|
||||
}
|
||||
|
||||
|
|
@ -4847,15 +4861,10 @@ void M_DrawChallenges(void)
|
|||
i = gamedata->challengegridwidth-1;
|
||||
explodex = x - (i*16)/2;
|
||||
x += (i*16)/2;
|
||||
|
||||
V_DrawFill(0, currentMenu->y, explodex, (CHALLENGEGRIDHEIGHT*16), challengesbordercolor);
|
||||
V_DrawFill((x+16), currentMenu->y, BASEVIDWIDTH - (x+16), (CHALLENGEGRIDHEIGHT*16), challengesbordercolor);
|
||||
}
|
||||
|
||||
selectx = explodex + (challengesmenu.hilix*16);
|
||||
|
||||
V_DrawFill(0, (currentMenu->y)-1 , BASEVIDWIDTH, 1, challengesbordercolor);
|
||||
V_DrawFill(0, (currentMenu->y) + (CHALLENGEGRIDHEIGHT*16), BASEVIDWIDTH, 1, challengesbordercolor);
|
||||
while (i >= 0 && x >= -32)
|
||||
{
|
||||
y = currentMenu->y-16;
|
||||
|
|
@ -4910,7 +4919,6 @@ challengedesc:
|
|||
// Name bar
|
||||
{
|
||||
y = 120;
|
||||
V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE));
|
||||
|
||||
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
|
||||
{
|
||||
|
|
@ -4940,6 +4948,7 @@ challengedesc:
|
|||
i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
|
||||
|
||||
if (challengesmenu.unlockcondition != NULL
|
||||
&& challengesmenu.currentunlock < MAXUNLOCKABLES
|
||||
&& ((gamedata->unlocked[challengesmenu.currentunlock] == true)
|
||||
|| ((challengesmenu.extradata != NULL)
|
||||
&& (challengesmenu.extradata[i] & CHE_HINT))
|
||||
|
|
|
|||
|
|
@ -1219,18 +1219,33 @@ static boolean M_MenuConfirmPressed(UINT8 pid)
|
|||
return M_MenuButtonPressed(pid, MBT_A);
|
||||
}
|
||||
|
||||
/*static boolean M_MenuConfirmHeld(UINT8 pid)
|
||||
{
|
||||
return M_MenuButtonHeld(pid, MBT_A);
|
||||
}*/
|
||||
|
||||
// Returns true if we press the Cancel button
|
||||
static boolean M_MenuBackPressed(UINT8 pid)
|
||||
{
|
||||
return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X));
|
||||
}
|
||||
|
||||
/*static boolean M_MenuBackHeld(UINT8 pid)
|
||||
{
|
||||
return (M_MenuButtonHeld(pid, MBT_B) || M_MenuButtonHeld(pid, MBT_X));
|
||||
}*/
|
||||
|
||||
// Retrurns true if we press the tertiary option button (C)
|
||||
static boolean M_MenuExtraPressed(UINT8 pid)
|
||||
{
|
||||
return M_MenuButtonPressed(pid, MBT_C);
|
||||
}
|
||||
|
||||
static boolean M_MenuExtraHeld(UINT8 pid)
|
||||
{
|
||||
return M_MenuButtonHeld(pid, MBT_C);
|
||||
}
|
||||
|
||||
|
||||
// Updates the x coordinate of the keybord so prevent it from going in weird places
|
||||
static void M_UpdateKeyboardX(void)
|
||||
|
|
@ -6842,7 +6857,7 @@ void M_Addons(INT32 choice)
|
|||
|
||||
#if 1
|
||||
if (cv_addons_option.value == 0)
|
||||
pathname = usehome ? srb2home : srb2path;
|
||||
pathname = addonsdir;
|
||||
else if (cv_addons_option.value == 1)
|
||||
pathname = srb2home;
|
||||
else if (cv_addons_option.value == 2)
|
||||
|
|
@ -7369,6 +7384,7 @@ void M_Challenges(INT32 choice)
|
|||
|
||||
void M_ChallengesTick(void)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
UINT8 i, newunlock = MAXUNLOCKABLES;
|
||||
boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES);
|
||||
|
||||
|
|
@ -7412,8 +7428,9 @@ void M_ChallengesTick(void)
|
|||
else
|
||||
{
|
||||
// Unlock sequence.
|
||||
tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME;
|
||||
|
||||
if (++challengesmenu.unlockanim >= MAXUNLOCKTIME)
|
||||
if (++challengesmenu.unlockanim >= nexttime)
|
||||
{
|
||||
challengesmenu.requestnew = true;
|
||||
}
|
||||
|
|
|
|||
307
src/k_roulette.c
307
src/k_roulette.c
|
|
@ -417,6 +417,171 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers
|
|||
return pdis;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------
|
||||
static boolean K_DenyShieldOdds(kartitems_t item)
|
||||
|
||||
Checks if this type of shield already exists in
|
||||
another player's inventory.
|
||||
|
||||
Input Arguments:-
|
||||
item - The item type of the shield.
|
||||
|
||||
Return:-
|
||||
Whether this item is a shield and may not be awarded
|
||||
at this time.
|
||||
--------------------------------------------------*/
|
||||
static boolean K_DenyShieldOdds(kartitems_t item)
|
||||
{
|
||||
INT32 shieldType = K_GetShieldFromItem(item);
|
||||
|
||||
if ((gametyperules & GTR_CIRCUIT) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (shieldType)
|
||||
{
|
||||
case KSHIELD_NONE:
|
||||
/* Marble Garden Top is not REALLY
|
||||
a Sonic 3 shield */
|
||||
case KSHIELD_TOP:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (playeringame[i] == false || players[i].spectator == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shieldType == K_GetShieldFromItem(players[i].itemtype))
|
||||
{
|
||||
// Don't allow more than one of each shield type at a time
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------
|
||||
static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position)
|
||||
|
||||
Adjust odds of SPB according to distances of first and
|
||||
second place players.
|
||||
|
||||
Input Arguments:-
|
||||
roulette - The roulette data that we intend to
|
||||
insert this item into.
|
||||
position - Position of player to consider for these
|
||||
odds.
|
||||
|
||||
Return:-
|
||||
New item odds.
|
||||
--------------------------------------------------*/
|
||||
static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position)
|
||||
{
|
||||
I_Assert(roulette != NULL);
|
||||
|
||||
if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done
|
||||
|| position == 1) // No SPB for 1st ever
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST);
|
||||
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
|
||||
const fixed_t maxOdds = 20 << FRACBITS;
|
||||
fixed_t multiplier = FixedDiv(dist, distRange);
|
||||
|
||||
if (multiplier < 0)
|
||||
{
|
||||
multiplier = 0;
|
||||
}
|
||||
|
||||
if (multiplier > FRACUNIT)
|
||||
{
|
||||
multiplier = FRACUNIT;
|
||||
}
|
||||
|
||||
return FixedMul(maxOdds, multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
boolean powerItem;
|
||||
boolean cooldownOnStart;
|
||||
boolean notNearEnd;
|
||||
|
||||
// gameplay state
|
||||
boolean rival; // player is a bot Rival
|
||||
} itemconditions_t;
|
||||
|
||||
/*--------------------------------------------------
|
||||
static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette)
|
||||
|
||||
Adjust item odds to certain group conditions.
|
||||
|
||||
Input Arguments:-
|
||||
newOdds - The item odds to adjust.
|
||||
conditions - The conditions state.
|
||||
roulette - The roulette data that we intend to
|
||||
insert this item into.
|
||||
|
||||
Return:-
|
||||
New item odds.
|
||||
--------------------------------------------------*/
|
||||
static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette)
|
||||
{
|
||||
// None if this applies outside of Race modes (for now?)
|
||||
if ((gametyperules & GTR_CIRCUIT) == 0)
|
||||
{
|
||||
return newOdds;
|
||||
}
|
||||
|
||||
if ((conditions->cooldownOnStart == true) && (leveltime < (30*TICRATE) + starttime))
|
||||
{
|
||||
// This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items)
|
||||
newOdds = 0;
|
||||
}
|
||||
else if ((conditions->notNearEnd == true) && (roulette != NULL && roulette->baseDist < ENDDIST))
|
||||
{
|
||||
// This item should not appear at the end of a race. (Usually trap items that lose their effectiveness)
|
||||
newOdds = 0;
|
||||
}
|
||||
else if (conditions->powerItem == true)
|
||||
{
|
||||
// This item is a "power item". This activates "frantic item" toggle related functionality.
|
||||
if (franticitems == true)
|
||||
{
|
||||
// First, power items multiply their odds by 2 if frantic items are on; easy-peasy.
|
||||
newOdds *= 2;
|
||||
}
|
||||
|
||||
if (conditions->rival == true)
|
||||
{
|
||||
// The Rival bot gets frantic-like items, also :p
|
||||
newOdds *= 2;
|
||||
}
|
||||
|
||||
if (roulette != NULL)
|
||||
{
|
||||
newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing));
|
||||
}
|
||||
}
|
||||
|
||||
return newOdds;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------
|
||||
INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item)
|
||||
|
||||
|
|
@ -425,19 +590,16 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers
|
|||
INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item)
|
||||
{
|
||||
boolean bot = false;
|
||||
boolean rival = false;
|
||||
UINT8 position = 0;
|
||||
|
||||
INT32 shieldType = KSHIELD_NONE;
|
||||
|
||||
boolean powerItem = false;
|
||||
boolean cooldownOnStart = false;
|
||||
boolean notNearEnd = false;
|
||||
itemconditions_t conditions = {
|
||||
.powerItem = false,
|
||||
.cooldownOnStart = false,
|
||||
.notNearEnd = false,
|
||||
.rival = false,
|
||||
};
|
||||
|
||||
fixed_t newOdds = 0;
|
||||
size_t i;
|
||||
|
||||
I_Assert(roulette != NULL);
|
||||
|
||||
I_Assert(item > KITEM_NONE); // too many off by one scenarioes.
|
||||
I_Assert(item < NUMKARTRESULTS);
|
||||
|
|
@ -445,7 +607,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
if (player != NULL)
|
||||
{
|
||||
bot = player->bot;
|
||||
rival = (bot == true && player->botvars.rival == true);
|
||||
conditions.rival = (bot == true && player->botvars.rival == true);
|
||||
position = player->position;
|
||||
}
|
||||
|
||||
|
|
@ -478,33 +640,9 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
*/
|
||||
(void)bot;
|
||||
|
||||
shieldType = K_GetShieldFromItem(item);
|
||||
switch (shieldType)
|
||||
if (K_DenyShieldOdds(item))
|
||||
{
|
||||
case KSHIELD_NONE:
|
||||
/* Marble Garden Top is not REALLY
|
||||
a Sonic 3 shield */
|
||||
case KSHIELD_TOP:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (playeringame[i] == false || players[i].spectator == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shieldType == K_GetShieldFromItem(players[i].itemtype))
|
||||
{
|
||||
// Don't allow more than one of each shield type at a time
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (gametype == GT_BATTLE)
|
||||
|
|
@ -531,7 +669,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
case KITEM_EGGMAN:
|
||||
case KITEM_SUPERRING:
|
||||
{
|
||||
notNearEnd = true;
|
||||
conditions.notNearEnd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -545,15 +683,15 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
case KRITEM_QUADORBINAUT:
|
||||
case KRITEM_DUALJAWZ:
|
||||
{
|
||||
powerItem = true;
|
||||
conditions.powerItem = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case KITEM_HYUDORO:
|
||||
case KRITEM_TRIPLEBANANA:
|
||||
{
|
||||
powerItem = true;
|
||||
notNearEnd = true;
|
||||
conditions.powerItem = true;
|
||||
conditions.notNearEnd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -563,59 +701,34 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
case KITEM_BUBBLESHIELD:
|
||||
case KITEM_FLAMESHIELD:
|
||||
{
|
||||
cooldownOnStart = true;
|
||||
powerItem = true;
|
||||
conditions.cooldownOnStart = true;
|
||||
conditions.powerItem = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case KITEM_SPB:
|
||||
{
|
||||
cooldownOnStart = true;
|
||||
notNearEnd = true;
|
||||
conditions.cooldownOnStart = true;
|
||||
conditions.notNearEnd = true;
|
||||
|
||||
if ((gametyperules & GTR_CIRCUIT) == 0)
|
||||
if (roulette != NULL &&
|
||||
(gametyperules & GTR_CIRCUIT) &&
|
||||
specialstageinfo.valid == false)
|
||||
{
|
||||
// Needs to be a race.
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (specialstageinfo.valid == false)
|
||||
{
|
||||
if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done
|
||||
|| position == 1) // No SPB for 1st ever
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST);
|
||||
const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST;
|
||||
const fixed_t maxOdds = 20 << FRACBITS;
|
||||
fixed_t multiplier = FixedDiv(dist, distRange);
|
||||
|
||||
if (multiplier < 0)
|
||||
{
|
||||
multiplier = 0;
|
||||
}
|
||||
|
||||
if (multiplier > FRACUNIT)
|
||||
{
|
||||
multiplier = FRACUNIT;
|
||||
}
|
||||
|
||||
newOdds = FixedMul(maxOdds, multiplier);
|
||||
}
|
||||
newOdds = K_AdjustSPBOdds(roulette, position);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case KITEM_SHRINK:
|
||||
{
|
||||
cooldownOnStart = true;
|
||||
powerItem = true;
|
||||
notNearEnd = true;
|
||||
conditions.cooldownOnStart = true;
|
||||
conditions.powerItem = true;
|
||||
conditions.notNearEnd = true;
|
||||
|
||||
if (roulette->playing - 1 <= roulette->exiting)
|
||||
if (roulette != NULL &&
|
||||
(gametyperules & GTR_CIRCUIT) &&
|
||||
roulette->playing - 1 <= roulette->exiting)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -624,10 +737,10 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
|
||||
case KITEM_LIGHTNINGSHIELD:
|
||||
{
|
||||
cooldownOnStart = true;
|
||||
powerItem = true;
|
||||
conditions.cooldownOnStart = true;
|
||||
conditions.powerItem = true;
|
||||
|
||||
if (spbplace != -1)
|
||||
if ((gametyperules & GTR_CIRCUIT) && spbplace != -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -646,35 +759,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette,
|
|||
return newOdds;
|
||||
}
|
||||
|
||||
if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime))
|
||||
{
|
||||
// This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items)
|
||||
newOdds = 0;
|
||||
}
|
||||
else if ((notNearEnd == true) && (roulette->baseDist < ENDDIST))
|
||||
{
|
||||
// This item should not appear at the end of a race. (Usually trap items that lose their effectiveness)
|
||||
newOdds = 0;
|
||||
}
|
||||
else if (powerItem == true)
|
||||
{
|
||||
// This item is a "power item". This activates "frantic item" toggle related functionality.
|
||||
if (franticitems == true)
|
||||
{
|
||||
// First, power items multiply their odds by 2 if frantic items are on; easy-peasy.
|
||||
newOdds *= 2;
|
||||
}
|
||||
|
||||
if (rival == true)
|
||||
{
|
||||
// The Rival bot gets frantic-like items, also :p
|
||||
newOdds *= 2;
|
||||
}
|
||||
|
||||
newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing));
|
||||
}
|
||||
|
||||
newOdds = FixedInt(FixedRound(newOdds));
|
||||
newOdds = FixedInt(FixedRound(K_AdjustItemOddsToConditions(newOdds, &conditions, roulette)));
|
||||
return newOdds;
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/m_cond.c
14
src/m_cond.c
|
|
@ -97,21 +97,27 @@ void M_PopulateChallengeGrid(void)
|
|||
{
|
||||
// Getting the number of 2-highs you can fit into two adjacent columns.
|
||||
UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2);
|
||||
majorpad = (nummajorunlocks+1)/majorpad;
|
||||
numempty = nummajorunlocks%majorpad;
|
||||
majorpad = (nummajorunlocks+(majorpad-1))/majorpad;
|
||||
|
||||
gamedata->challengegridwidth = majorpad*2;
|
||||
numempty *= 4;
|
||||
|
||||
#if (CHALLENGEGRIDHEIGHT % 2)
|
||||
// One empty per column.
|
||||
numempty = gamedata->challengegridwidth;
|
||||
// One extra empty per column.
|
||||
numempty += gamedata->challengegridwidth;
|
||||
#endif
|
||||
|
||||
//CONS_Printf("%d major unlocks means width of %d, numempty of %d\n", nummajorunlocks, gamedata->challengegridwidth, numempty);
|
||||
}
|
||||
|
||||
if (numunlocks > numempty)
|
||||
{
|
||||
// Getting the number of extra columns to store normal unlocks
|
||||
gamedata->challengegridwidth += ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT;
|
||||
UINT16 temp = ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT;
|
||||
gamedata->challengegridwidth += temp;
|
||||
majorcompact = 1;
|
||||
//CONS_Printf("%d normal unlocks means %d extra entries, additional width of %d\n", numunlocks, (numunlocks - numempty), temp);
|
||||
}
|
||||
else if (challengegridloops)
|
||||
{
|
||||
|
|
|
|||
34
src/p_user.c
34
src/p_user.c
|
|
@ -1792,6 +1792,38 @@ static void P_DoBubbleBreath(player_t *player)
|
|||
}
|
||||
}
|
||||
|
||||
static inline boolean P_IsMomentumAngleLocked(player_t *player)
|
||||
{
|
||||
// This timer is used for the animation too and the
|
||||
// animation should continue for a bit after the physics
|
||||
// stop.
|
||||
|
||||
if (player->stairjank > 8)
|
||||
{
|
||||
const angle_t th = K_MomentumAngle(player->mo);
|
||||
const angle_t d = AngleDelta(th, player->mo->angle);
|
||||
|
||||
// A larger difference between momentum and facing
|
||||
// angles awards back control.
|
||||
// <45 deg: 3/4 tics
|
||||
// >45 deg: 2/4 tics
|
||||
// >90 deg: 1/4 tics
|
||||
// >135 deg: 0/4 tics
|
||||
|
||||
if ((leveltime & 3) > (d / ANGLE_45))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (K_IsRidingFloatingTop(player))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//#define OLD_MOVEMENT_CODE 1
|
||||
static void P_3dMovement(player_t *player)
|
||||
{
|
||||
|
|
@ -1812,7 +1844,7 @@ static void P_3dMovement(player_t *player)
|
|||
// Get the old momentum; this will be needed at the end of the function! -SH
|
||||
oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
|
||||
|
||||
if ((player->stairjank > 8 && leveltime & 3) || K_IsRidingFloatingTop(player))
|
||||
if (P_IsMomentumAngleLocked(player))
|
||||
{
|
||||
movepushangle = K_MomentumAngle(player->mo);
|
||||
}
|
||||
|
|
|
|||
135
src/s_sound.c
135
src/s_sound.c
|
|
@ -1365,6 +1365,28 @@ musicdef_t *musicdefstart = NULL;
|
|||
struct cursongcredit cursongcredit; // Currently displayed song credit info
|
||||
int musicdef_volume;
|
||||
|
||||
//
|
||||
// S_FindMusicDef
|
||||
//
|
||||
// Find music def by 6 char name
|
||||
//
|
||||
static musicdef_t *S_FindMusicDef(const char *name)
|
||||
{
|
||||
musicdef_t *def = musicdefstart;
|
||||
|
||||
while (def)
|
||||
{
|
||||
if (!stricmp(def->name, name))
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
def = def->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static boolean
|
||||
MusicDefError
|
||||
(
|
||||
|
|
@ -1412,21 +1434,10 @@ ReadMusicDefFields
|
|||
}
|
||||
else
|
||||
{
|
||||
musicdef_t **tail = &musicdefstart;
|
||||
|
||||
// Search if this is a replacement
|
||||
while (*tail)
|
||||
{
|
||||
if (!stricmp((*tail)->name, value))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
tail = &(*tail)->next;
|
||||
}
|
||||
def = S_FindMusicDef(value);
|
||||
|
||||
// Nothing found, add to the end.
|
||||
if (!(*tail))
|
||||
if (!def)
|
||||
{
|
||||
def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
|
||||
|
||||
|
|
@ -1434,10 +1445,11 @@ ReadMusicDefFields
|
|||
strlwr(def->name);
|
||||
def->volume = DEFAULT_MUSICDEF_VOLUME;
|
||||
|
||||
(*tail) = def;
|
||||
def->next = musicdefstart;
|
||||
musicdefstart = def;
|
||||
}
|
||||
|
||||
(*defp) = (*tail);
|
||||
(*defp) = def;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1611,7 +1623,11 @@ void S_InitMusicDefs(void)
|
|||
//
|
||||
void S_ShowMusicCredit(void)
|
||||
{
|
||||
musicdef_t *def = musicdefstart;
|
||||
musicdef_t *def = S_FindMusicDef(music_name);
|
||||
|
||||
char credittext[128] = "";
|
||||
char *work = NULL;
|
||||
size_t len = 128, worklen;
|
||||
|
||||
if (!cv_songcredits.value || demo.rewinding)
|
||||
return;
|
||||
|
|
@ -1619,58 +1635,45 @@ void S_ShowMusicCredit(void)
|
|||
if (!def) // No definitions
|
||||
return;
|
||||
|
||||
while (def)
|
||||
if (!def->title)
|
||||
{
|
||||
if (!stricmp(def->name, music_name))
|
||||
{
|
||||
char credittext[128] = "";
|
||||
char *work = NULL;
|
||||
size_t len = 128, worklen;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!def->title)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
work = va("\x1F %s", def->title);
|
||||
worklen = strlen(work);
|
||||
if (worklen <= len)
|
||||
{
|
||||
strncat(credittext, work, len);
|
||||
len -= worklen;
|
||||
work = va("\x1F %s", def->title);
|
||||
worklen = strlen(work);
|
||||
if (worklen <= len)
|
||||
{
|
||||
strncat(credittext, work, len);
|
||||
len -= worklen;
|
||||
|
||||
#define MUSICCREDITAPPEND(field)\
|
||||
if (field)\
|
||||
{\
|
||||
work = va(" - %s", field);\
|
||||
worklen = strlen(work);\
|
||||
if (worklen <= len)\
|
||||
{\
|
||||
strncat(credittext, work, len);\
|
||||
len -= worklen;\
|
||||
}\
|
||||
}
|
||||
|
||||
MUSICCREDITAPPEND(def->author);
|
||||
MUSICCREDITAPPEND(def->source);
|
||||
|
||||
#undef MUSICCREDITAPPEND
|
||||
}
|
||||
|
||||
if (credittext[0] == '\0')
|
||||
return;
|
||||
|
||||
cursongcredit.def = def;
|
||||
Z_Free(cursongcredit.text);
|
||||
cursongcredit.text = Z_StrDup(credittext);
|
||||
cursongcredit.anim = 5*TICRATE;
|
||||
cursongcredit.x = cursongcredit.old_x = 0;
|
||||
cursongcredit.trans = NUMTRANSMAPS;
|
||||
return;
|
||||
if (field)\
|
||||
{\
|
||||
work = va(" - %s", field);\
|
||||
worklen = strlen(work);\
|
||||
if (worklen <= len)\
|
||||
{\
|
||||
strncat(credittext, work, len);\
|
||||
len -= worklen;\
|
||||
}\
|
||||
}
|
||||
|
||||
def = def->next;
|
||||
MUSICCREDITAPPEND(def->author);
|
||||
MUSICCREDITAPPEND(def->source);
|
||||
|
||||
#undef MUSICCREDITAPPEND
|
||||
}
|
||||
|
||||
if (credittext[0] == '\0')
|
||||
return;
|
||||
|
||||
cursongcredit.def = def;
|
||||
Z_Free(cursongcredit.text);
|
||||
cursongcredit.text = Z_StrDup(credittext);
|
||||
cursongcredit.anim = 5*TICRATE;
|
||||
cursongcredit.x = cursongcredit.old_x = 0;
|
||||
cursongcredit.trans = NUMTRANSMAPS;
|
||||
}
|
||||
|
||||
/// ------------------------
|
||||
|
|
@ -2242,13 +2245,11 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
|
|||
musicdef_volume = DEFAULT_MUSICDEF_VOLUME;
|
||||
|
||||
{
|
||||
musicdef_t *def;
|
||||
for (def = musicdefstart; def; def = def->next)
|
||||
musicdef_t *def = S_FindMusicDef(music_name);
|
||||
|
||||
if (def)
|
||||
{
|
||||
if (strcasecmp(def->name, music_name) == 0)
|
||||
{
|
||||
musicdef_volume = def->volume;
|
||||
}
|
||||
musicdef_volume = def->volume;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -287,8 +287,8 @@ static void CustomApplicationMain (int argc, char **argv)
|
|||
[self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()];
|
||||
#endif
|
||||
|
||||
if (!getenv("SRB2WADDIR"))
|
||||
setenv("SRB2WADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1);
|
||||
if (!getenv("RINGRACERSWADDIR"))
|
||||
setenv("RINGRACERSWADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1);
|
||||
|
||||
/* Hand off to main application code */
|
||||
status = SDL_main (gArgc, gArgv);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -66,11 +72,9 @@ char logfilename[1024];
|
|||
#endif
|
||||
|
||||
#if defined (_WIN32)
|
||||
#include <exchndl.h>
|
||||
#endif
|
||||
|
||||
#if defined (_WIN32)
|
||||
extern "C" {
|
||||
#include "../win32/win_dbg.h"
|
||||
}
|
||||
typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID);
|
||||
#endif
|
||||
|
||||
|
|
@ -151,20 +155,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,
|
||||
|
|
@ -179,7 +183,7 @@ static void InitLogging(void)
|
|||
logstream = fopen(logfilename, "w");
|
||||
#ifdef DEFAULTDIR
|
||||
if (logdir)
|
||||
link = va("%s/"DEFAULTDIR"/latest-log.txt", logdir);
|
||||
link = va("%s/" DEFAULTDIR "/latest-log.txt", logdir);
|
||||
else
|
||||
#endif/*DEFAULTDIR*/
|
||||
link = "latest-log.txt";
|
||||
|
|
@ -194,6 +198,20 @@ static void InitLogging(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void init_exchndl()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
HMODULE exchndl_module = LoadLibraryA("exchndl.dll");
|
||||
if (exchndl_module != NULL)
|
||||
{
|
||||
using PFN_ExcHndlInit = void(*)(void);
|
||||
PFN_ExcHndlInit pfnExcHndlInit = reinterpret_cast<PFN_ExcHndlInit>(
|
||||
GetProcAddress(exchndl_module, "ExcHndlInit"));
|
||||
if (pfnExcHndlInit != NULL)
|
||||
(pfnExcHndlInit)();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static void
|
||||
|
|
@ -208,6 +226,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
|
||||
|
||||
|
|
@ -259,7 +304,7 @@ int main(int argc, char **argv)
|
|||
)
|
||||
#endif
|
||||
{
|
||||
ExcHndlInit();
|
||||
init_exchndl();
|
||||
}
|
||||
}
|
||||
#ifndef __MINGW32__
|
||||
|
|
@ -268,6 +313,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 +326,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();
|
||||
|
|
@ -989,7 +989,7 @@ INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev)
|
|||
SDL_Joystick *joystick = NULL;
|
||||
|
||||
joystick = SDL_GameControllerGetJoystick(dev);
|
||||
|
||||
|
||||
if (joystick)
|
||||
{
|
||||
return SDL_JoystickInstanceID(joystick);
|
||||
|
|
@ -2242,9 +2242,9 @@ static const char *locateWad(void)
|
|||
const char *envstr;
|
||||
const char *WadPath;
|
||||
|
||||
I_OutputMsg("SRB2WADDIR");
|
||||
// does SRB2WADDIR exist?
|
||||
if (((envstr = I_GetEnv("SRB2WADDIR")) != NULL) && isWadPathOk(envstr))
|
||||
I_OutputMsg("RINGRACERSWADDIR");
|
||||
// does RINGRACERSWADDIR exist?
|
||||
if (((envstr = I_GetEnv("RINGRACERSWADDIR")) != NULL) && isWadPathOk(envstr))
|
||||
return envstr;
|
||||
|
||||
#ifndef NOCWD
|
||||
|
|
|
|||
665
src/sdl/new_sound.cpp
Normal file
665
src/sdl/new_sound.cpp
Normal 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;
|
||||
}
|
||||
601
thirdparty/CMakeLists.txt
vendored
601
thirdparty/CMakeLists.txt
vendored
|
|
@ -9,600 +9,17 @@ else()
|
|||
set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON)
|
||||
endif()
|
||||
|
||||
|
||||
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
|
||||
CPMAddPackage(
|
||||
NAME SDL2
|
||||
VERSION 2.24.2
|
||||
URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_TEST OFF"
|
||||
"SDL2_DISABLE_SDL2MAIN ON"
|
||||
"SDL2_DISABLE_INSTALL ON"
|
||||
)
|
||||
include("cpm-sdl2.cmake")
|
||||
include("cpm-zlib.cmake")
|
||||
include("cpm-png.cmake")
|
||||
include("cpm-curl.cmake")
|
||||
include("cpm-libgme.cmake")
|
||||
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
|
||||
VERSION 1.2.13
|
||||
URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
|
||||
EXCLUDE_FROM_ALL
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
if(ZLIB_ADDED)
|
||||
set(ZLIB_SRCS
|
||||
crc32.h
|
||||
deflate.h
|
||||
gzguts.h
|
||||
inffast.h
|
||||
inffixed.h
|
||||
inflate.h
|
||||
inftrees.h
|
||||
trees.h
|
||||
zutil.h
|
||||
|
||||
adler32.c
|
||||
compress.c
|
||||
crc32.c
|
||||
deflate.c
|
||||
gzclose.c
|
||||
gzlib.c
|
||||
gzread.c
|
||||
gzwrite.c
|
||||
inflate.c
|
||||
infback.c
|
||||
inftrees.c
|
||||
inffast.c
|
||||
trees.c
|
||||
uncompr.c
|
||||
zutil.c
|
||||
)
|
||||
list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
|
||||
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
|
||||
|
||||
add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
|
||||
set_target_properties(ZLIB PROPERTIES
|
||||
VERSION 1.2.13
|
||||
OUTPUT_NAME "z"
|
||||
)
|
||||
target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
|
||||
target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
|
||||
if(MSVC)
|
||||
target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
|
||||
endif()
|
||||
add_library(ZLIB::ZLIB ALIAS ZLIB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
|
||||
CPMAddPackage(
|
||||
NAME png
|
||||
VERSION 1.6.38
|
||||
URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
|
||||
# png cmake build is broken on msys/mingw32
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
if(png_ADDED)
|
||||
# Since png's cmake build is broken, we're going to create a target manually
|
||||
set(
|
||||
PNG_SOURCES
|
||||
|
||||
png.h
|
||||
pngconf.h
|
||||
|
||||
pngpriv.h
|
||||
pngdebug.h
|
||||
pnginfo.h
|
||||
pngstruct.h
|
||||
|
||||
png.c
|
||||
pngerror.c
|
||||
pngget.c
|
||||
pngmem.c
|
||||
pngpread.c
|
||||
pngread.c
|
||||
pngrio.c
|
||||
pngrtran.c
|
||||
pngrutil.c
|
||||
pngset.c
|
||||
pngtrans.c
|
||||
pngwio.c
|
||||
pngwrite.c
|
||||
pngwtran.c
|
||||
pngwutil.c
|
||||
)
|
||||
list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
|
||||
DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
|
||||
VERBATIM
|
||||
)
|
||||
list(
|
||||
APPEND PNG_SOURCES
|
||||
"${png_BINARY_DIR}/include/png.h"
|
||||
"${png_BINARY_DIR}/include/pngconf.h"
|
||||
"${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
)
|
||||
add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
|
||||
|
||||
# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
|
||||
target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
|
||||
|
||||
# The png includes need to be available to consumers
|
||||
target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
|
||||
|
||||
# ... and these also need to be present only for png build
|
||||
target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
|
||||
target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
|
||||
target_include_directories(png PRIVATE "${png_BINARY_DIR}")
|
||||
|
||||
target_link_libraries(png PRIVATE ZLIB::ZLIB)
|
||||
add_library(PNG::PNG ALIAS png)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
|
||||
set(
|
||||
internal_curl_options
|
||||
|
||||
"BUILD_CURL_EXE OFF"
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"CURL_DISABLE_TESTS ON"
|
||||
"HTTP_ONLY ON"
|
||||
"CURL_DISABLE_CRYPTO_AUTH ON"
|
||||
"CURL_DISABLE_NTLM ON"
|
||||
"ENABLE_MANUAL OFF"
|
||||
"ENABLE_THREADED_RESOLVER OFF"
|
||||
"CURL_USE_LIBPSL OFF"
|
||||
"CURL_USE_LIBSSH2 OFF"
|
||||
"USE_LIBIDN2 OFF"
|
||||
"CURL_ENABLE_EXPORT_TARGET OFF"
|
||||
)
|
||||
if(${CMAKE_SYSTEM} MATCHES Windows)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
|
||||
list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM} MATCHES Darwin)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
|
||||
list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM} MATCHES Linux)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME curl
|
||||
VERSION 7.86.0
|
||||
URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS ${internal_curl_options}
|
||||
)
|
||||
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
|
||||
VERSION 0.6.3
|
||||
URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"ENABLE_UBSAN OFF"
|
||||
"GME_YM2612_EMU MAME"
|
||||
)
|
||||
target_compile_features(gme PRIVATE cxx_std_11)
|
||||
target_link_libraries(gme PRIVATE ZLIB::ZLIB)
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME RapidJSON
|
||||
VERSION 1.1.0
|
||||
URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
DOWNLOAD_ONLY ON
|
||||
)
|
||||
if(RapidJSON_ADDED)
|
||||
add_library(RapidJSON INTERFACE)
|
||||
add_library(RapidJSON::RapidJSON ALIAS RapidJSON)
|
||||
target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include")
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME DiscordRPC
|
||||
VERSION 3.4.0
|
||||
URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
DOWNLOAD_ONLY ON
|
||||
)
|
||||
|
||||
if(DiscordRPC_ADDED)
|
||||
set(DiscordRPC_SOURCES
|
||||
include/discord_rpc.h
|
||||
include/discord_register.h
|
||||
|
||||
src/discord_rpc.cpp
|
||||
src/rpc_connection.h
|
||||
src/rpc_connection.cpp
|
||||
src/serialization.h
|
||||
src/serialization.cpp
|
||||
src/connection.h
|
||||
src/backoff.h
|
||||
src/msg_queue.h
|
||||
)
|
||||
list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/")
|
||||
|
||||
# Discord RPC is always statically linked because it's tiny.
|
||||
add_library(discord-rpc STATIC ${DiscordRPC_SOURCES})
|
||||
add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc)
|
||||
|
||||
target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include")
|
||||
target_compile_features(discord-rpc PUBLIC cxx_std_11)
|
||||
target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON)
|
||||
|
||||
# Platform-specific connection and register impls
|
||||
if(WIN32)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp"
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp"
|
||||
)
|
||||
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp"
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m"
|
||||
)
|
||||
target_link_libraries(discord-rpc PUBLIC "-framework AppKit")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
include("cpm-rapidjson.cmake")
|
||||
include("cpm-discordrpc.cmake")
|
||||
include("cpm-xmp-lite.cmake")
|
||||
|
||||
add_subdirectory(tcbrindle_span)
|
||||
add_subdirectory(stb_vorbis)
|
||||
|
|
|
|||
35
thirdparty/cpm-curl.cmake
vendored
Normal file
35
thirdparty/cpm-curl.cmake
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
set(
|
||||
internal_curl_options
|
||||
|
||||
"BUILD_CURL_EXE OFF"
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"CURL_DISABLE_TESTS ON"
|
||||
"HTTP_ONLY ON"
|
||||
"CURL_DISABLE_CRYPTO_AUTH ON"
|
||||
"CURL_DISABLE_NTLM ON"
|
||||
"ENABLE_MANUAL OFF"
|
||||
"ENABLE_THREADED_RESOLVER OFF"
|
||||
"CURL_USE_LIBPSL OFF"
|
||||
"CURL_USE_LIBSSH2 OFF"
|
||||
"USE_LIBIDN2 OFF"
|
||||
"CURL_ENABLE_EXPORT_TARGET OFF"
|
||||
)
|
||||
if(${CMAKE_SYSTEM} MATCHES Windows)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
|
||||
list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM} MATCHES Darwin)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
|
||||
list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM} MATCHES Linux)
|
||||
list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME curl
|
||||
VERSION 7.86.0
|
||||
URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS ${internal_curl_options}
|
||||
)
|
||||
63
thirdparty/cpm-discordrpc.cmake
vendored
Normal file
63
thirdparty/cpm-discordrpc.cmake
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
CPMAddPackage(
|
||||
NAME DiscordRPC
|
||||
VERSION 3.4.0
|
||||
URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
DOWNLOAD_ONLY ON
|
||||
)
|
||||
|
||||
if(DiscordRPC_ADDED)
|
||||
set(DiscordRPC_SOURCES
|
||||
include/discord_rpc.h
|
||||
include/discord_register.h
|
||||
|
||||
src/discord_rpc.cpp
|
||||
src/rpc_connection.h
|
||||
src/rpc_connection.cpp
|
||||
src/serialization.h
|
||||
src/serialization.cpp
|
||||
src/connection.h
|
||||
src/backoff.h
|
||||
src/msg_queue.h
|
||||
)
|
||||
list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/")
|
||||
|
||||
# Discord RPC is always statically linked because it's tiny.
|
||||
add_library(discord-rpc STATIC ${DiscordRPC_SOURCES})
|
||||
add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc)
|
||||
|
||||
target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include")
|
||||
target_compile_features(discord-rpc PUBLIC cxx_std_11)
|
||||
target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON)
|
||||
|
||||
# Platform-specific connection and register impls
|
||||
if(WIN32)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp"
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp"
|
||||
)
|
||||
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp"
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m"
|
||||
)
|
||||
target_link_libraries(discord-rpc PUBLIC "-framework AppKit")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX)
|
||||
target_sources(discord-rpc PRIVATE
|
||||
"${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
15
thirdparty/cpm-libgme.cmake
vendored
Normal file
15
thirdparty/cpm-libgme.cmake
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
CPMAddPackage(
|
||||
NAME libgme
|
||||
VERSION 0.6.3
|
||||
URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"ENABLE_UBSAN OFF"
|
||||
"GME_YM2612_EMU MAME"
|
||||
)
|
||||
|
||||
if(libgme_ADDED)
|
||||
target_compile_features(gme PRIVATE cxx_std_11)
|
||||
target_link_libraries(gme PRIVATE ZLIB::ZLIB)
|
||||
endif()
|
||||
73
thirdparty/cpm-png.cmake
vendored
Normal file
73
thirdparty/cpm-png.cmake
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
CPMAddPackage(
|
||||
NAME png
|
||||
VERSION 1.6.38
|
||||
URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
|
||||
# png cmake build is broken on msys/mingw32
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
if(png_ADDED)
|
||||
# Since png's cmake build is broken, we're going to create a target manually
|
||||
set(
|
||||
PNG_SOURCES
|
||||
|
||||
png.h
|
||||
pngconf.h
|
||||
|
||||
pngpriv.h
|
||||
pngdebug.h
|
||||
pnginfo.h
|
||||
pngstruct.h
|
||||
|
||||
png.c
|
||||
pngerror.c
|
||||
pngget.c
|
||||
pngmem.c
|
||||
pngpread.c
|
||||
pngread.c
|
||||
pngrio.c
|
||||
pngrtran.c
|
||||
pngrutil.c
|
||||
pngset.c
|
||||
pngtrans.c
|
||||
pngwio.c
|
||||
pngwrite.c
|
||||
pngwtran.c
|
||||
pngwutil.c
|
||||
)
|
||||
list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
|
||||
DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
|
||||
VERBATIM
|
||||
)
|
||||
list(
|
||||
APPEND PNG_SOURCES
|
||||
"${png_BINARY_DIR}/include/png.h"
|
||||
"${png_BINARY_DIR}/include/pngconf.h"
|
||||
"${png_BINARY_DIR}/include/pnglibconf.h"
|
||||
)
|
||||
add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
|
||||
|
||||
# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
|
||||
target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
|
||||
|
||||
# The png includes need to be available to consumers
|
||||
target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
|
||||
|
||||
# ... and these also need to be present only for png build
|
||||
target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
|
||||
target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
|
||||
target_include_directories(png PRIVATE "${png_BINARY_DIR}")
|
||||
|
||||
target_link_libraries(png PRIVATE ZLIB::ZLIB)
|
||||
add_library(PNG::PNG ALIAS png)
|
||||
endif()
|
||||
13
thirdparty/cpm-rapidjson.cmake
vendored
Normal file
13
thirdparty/cpm-rapidjson.cmake
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
CPMAddPackage(
|
||||
NAME RapidJSON
|
||||
VERSION 1.1.0
|
||||
URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
DOWNLOAD_ONLY ON
|
||||
)
|
||||
|
||||
if(RapidJSON_ADDED)
|
||||
add_library(RapidJSON INTERFACE)
|
||||
add_library(RapidJSON::RapidJSON ALIAS RapidJSON)
|
||||
target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include")
|
||||
endif()
|
||||
13
thirdparty/cpm-sdl2.cmake
vendored
Normal file
13
thirdparty/cpm-sdl2.cmake
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
CPMAddPackage(
|
||||
NAME SDL2
|
||||
VERSION 2.24.2
|
||||
URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
|
||||
EXCLUDE_FROM_ALL ON
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
|
||||
"SDL_TEST OFF"
|
||||
"SDL2_DISABLE_SDL2MAIN ON"
|
||||
"SDL2_DISABLE_INSTALL ON"
|
||||
)
|
||||
60
thirdparty/cpm-xmp-lite.cmake
vendored
Normal file
60
thirdparty/cpm-xmp-lite.cmake
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
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()
|
||||
54
thirdparty/cpm-zlib.cmake
vendored
Normal file
54
thirdparty/cpm-zlib.cmake
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
CPMAddPackage(
|
||||
NAME ZLIB
|
||||
VERSION 1.2.13
|
||||
URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
|
||||
EXCLUDE_FROM_ALL
|
||||
DOWNLOAD_ONLY YES
|
||||
)
|
||||
|
||||
if(ZLIB_ADDED)
|
||||
set(ZLIB_SRCS
|
||||
crc32.h
|
||||
deflate.h
|
||||
gzguts.h
|
||||
inffast.h
|
||||
inffixed.h
|
||||
inflate.h
|
||||
inftrees.h
|
||||
trees.h
|
||||
zutil.h
|
||||
|
||||
adler32.c
|
||||
compress.c
|
||||
crc32.c
|
||||
deflate.c
|
||||
gzclose.c
|
||||
gzlib.c
|
||||
gzread.c
|
||||
gzwrite.c
|
||||
inflate.c
|
||||
infback.c
|
||||
inftrees.c
|
||||
inffast.c
|
||||
trees.c
|
||||
uncompr.c
|
||||
zutil.c
|
||||
)
|
||||
list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
|
||||
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
|
||||
configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
|
||||
|
||||
add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
|
||||
set_target_properties(ZLIB PROPERTIES
|
||||
VERSION 1.2.13
|
||||
OUTPUT_NAME "z"
|
||||
)
|
||||
target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
|
||||
target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
|
||||
if(MSVC)
|
||||
target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
|
||||
endif()
|
||||
add_library(ZLIB::ZLIB ALIAS ZLIB)
|
||||
endif()
|
||||
10
thirdparty/openmpt_svn_version.h
vendored
10
thirdparty/openmpt_svn_version.h
vendored
|
|
@ -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
4
thirdparty/stb_vorbis/CMakeLists.txt
vendored
Normal 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")
|
||||
2
thirdparty/stb_vorbis/include/stb_vorbis.h
vendored
Normal file
2
thirdparty/stb_vorbis/include/stb_vorbis.h
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#define STB_VORBIS_HEADER_ONLY
|
||||
#include "../stb_vorbis.c"
|
||||
5584
thirdparty/stb_vorbis/stb_vorbis.c
vendored
Normal file
5584
thirdparty/stb_vorbis/stb_vorbis.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue