mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-08 18:13:02 -04:00
Move wsys replacements into their own .cpp file
This is gonna be a big PR
This commit is contained in:
@@ -1487,6 +1487,8 @@ set(DUSK_FILES
|
||||
src/dusk/mods/manifest.hpp
|
||||
src/dusk/mods/svc/audio_res/audio_res.hpp
|
||||
src/dusk/mods/svc/audio_res/audio_res.cpp
|
||||
src/dusk/mods/svc/audio_res/wsys.cpp
|
||||
src/dusk/mods/svc/audio_res/wsys.hpp
|
||||
src/dusk/mods/svc/audio_res/wave.cpp
|
||||
src/dusk/mods/svc/audio_res/opus.cpp
|
||||
src/dusk/mods/svc/camera.cpp
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "JSystem/JAudio2/JASBank.h"
|
||||
|
||||
#include "dusk/mods/svc/audio_res/audio_res.hpp"
|
||||
#include "dusk/mods/svc/audio_res/wsys.hpp"
|
||||
#include "JSystem/JAudio2/JASAiCtrl.h"
|
||||
#include "JSystem/JAudio2/JASBasicInst.h"
|
||||
#include "JSystem/JAudio2/JASBasicWaveBank.h"
|
||||
#include "JSystem/JAudio2/JASChannel.h"
|
||||
|
||||
using namespace dusk::mods::svc::audio_res;
|
||||
using namespace dusk::mods::svc::audio_res::wsys;
|
||||
|
||||
// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
|
||||
JASChannel* JASBank::noteOn(JASBank const* bank, int program, u8 key, u8 velocity, u16 priority,
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
#include "mods/svc/audio_res.h"
|
||||
|
||||
#include "../registry.hpp"
|
||||
#include "../slot_map.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "wsys.hpp"
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
|
||||
@@ -16,246 +13,24 @@ namespace {
|
||||
|
||||
using namespace dusk::helpers::cast;
|
||||
|
||||
bool audio_replacements_dirty = false;
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res");
|
||||
|
||||
SlotMap<RuntimeWaveReplacementSlot> s_waveReplacements;
|
||||
|
||||
constexpr ContainerLoadFunction s_containerLoadFunctions[] = {
|
||||
load_wav,
|
||||
#if DUSK_OPUS
|
||||
load_opus,
|
||||
#endif
|
||||
};
|
||||
|
||||
bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) {
|
||||
u32 samples_per_block;
|
||||
u32 bytes_per_block;
|
||||
switch (raw.format) {
|
||||
case Adpcm4:
|
||||
samples_per_block = 16;
|
||||
bytes_per_block = 9;
|
||||
break;
|
||||
case Pcm16:
|
||||
samples_per_block = 1;
|
||||
bytes_per_block = 2;
|
||||
break;
|
||||
default:
|
||||
Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actual_size % bytes_per_block != 0) {
|
||||
Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
sample_count = (actual_size / bytes_per_block) * samples_per_block;
|
||||
return true;
|
||||
}
|
||||
|
||||
ModResult load_raw(
|
||||
LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
u32 sample_count = 0;
|
||||
|
||||
if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) {
|
||||
Log.error(
|
||||
"[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified",
|
||||
mod.metadata.id, slot.bundle_path);
|
||||
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (slot.format == Pcm16) {
|
||||
SampleDataPcm16 pcm16;
|
||||
pcm16.data.resize(sample_count);
|
||||
memcpy(pcm16.data.data(), file_contents.data(), file_contents.size());
|
||||
slot.data = std::make_shared<SampleDataPcm16>(std::move(pcm16));
|
||||
} else {
|
||||
slot.data = std::make_shared<SampleDataU8>(std::move(file_contents));
|
||||
}
|
||||
|
||||
slot.sample_count = sample_count;
|
||||
slot.sample_rate = raw.sample_rate;
|
||||
slot.format = raw.format;
|
||||
slot.sample_value_penult = raw.sample_value_penult;
|
||||
slot.sample_value_last = raw.sample_value_last;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an audio file stored in some sort of container format.
|
||||
* File type is determined by looking at contents, not extension.
|
||||
*/
|
||||
ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
|
||||
for (auto const loader : s_containerLoadFunctions) {
|
||||
auto const result = loader(mod, slot, file_contents);
|
||||
if (result == MOD_OK) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
if (result != MOD_UNSUPPORTED) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) {
|
||||
JASWaveInfo info;
|
||||
info.mWaveFormat = static_cast<u8>(slot.format);
|
||||
info.mBaseKey = slot.base_key;
|
||||
info.mLoopFlag = slot.loop ? 0xFF : 0;
|
||||
info.mSampleRate = slot.sample_rate;
|
||||
info.mOffsetStart = 0; // Unused but just init it ig.
|
||||
info.mOffsetLength = bounded_cast(slot.data->size());
|
||||
info.mLoopStartSample = slot.loop_start_sample;
|
||||
info.mLoopEndSample = bounded_cast(slot.loop_end_sample);
|
||||
info.mSampleCount = bounded_cast(slot.sample_count);
|
||||
info.mpLast = slot.sample_value_last;
|
||||
info.mpPenult = slot.sample_value_penult;
|
||||
// mpLoaded is initialized to point to a 1, so we're good there.
|
||||
return info;
|
||||
}
|
||||
|
||||
ModResult insert_replace_wave(
|
||||
ModContext* ctx,
|
||||
AudioWaveBank bank,
|
||||
u16 wave_id,
|
||||
char const* file_name,
|
||||
AudioWaveInfo const* wave_info,
|
||||
AudioWaveHandle* out_handle) {
|
||||
if (out_handle != nullptr) {
|
||||
*out_handle = 0;
|
||||
}
|
||||
|
||||
auto mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
RuntimeWaveReplacementSlot slot{};
|
||||
slot.bundle_path = file_name;
|
||||
slot.bank = bank;
|
||||
slot.wave_id = wave_id;
|
||||
|
||||
ModResult result;
|
||||
if (wave_info && wave_info->raw_wave) {
|
||||
result = load_raw(*mod, slot, *wave_info->raw_wave);
|
||||
} else {
|
||||
result = load_container(*mod, slot);
|
||||
}
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (wave_info != nullptr) {
|
||||
slot.base_key = wave_info->base_key;
|
||||
slot.loop = wave_info->loop;
|
||||
slot.loop_start_sample = wave_info->loop_start_sample;
|
||||
slot.loop_end_sample = wave_info->loop_end_sample;
|
||||
} else {
|
||||
slot.base_key = AUDIO_RES_DEFAULT_KEY;
|
||||
slot.loop = false;
|
||||
slot.loop_start_sample = 0;
|
||||
slot.loop_end_sample = slot.sample_count;
|
||||
}
|
||||
|
||||
audio_replacements_dirty = true;
|
||||
|
||||
const auto handle = s_waveReplacements.emplace(*mod, std::move(slot));
|
||||
if (out_handle) {
|
||||
*out_handle = handle;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) {
|
||||
auto const result = s_waveReplacements.erase_owned(handle, mod);
|
||||
audio_replacements_dirty |= result;
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) {
|
||||
auto* mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!wave_remove(*mod, handle)) {
|
||||
Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
constexpr AudioResService s_audioResService{
|
||||
.header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR),
|
||||
.replace_wave = &insert_replace_wave,
|
||||
.remove_wave = &remove_wave,
|
||||
.replace_wave = &wsys::insert_replace_wave,
|
||||
.remove_wave = &wsys::remove_wave,
|
||||
};
|
||||
|
||||
void frame_end() {
|
||||
wsys::frame_end();
|
||||
}
|
||||
|
||||
void mod_detached(LoadedMod& mod) {
|
||||
wsys::remove_mod(mod);
|
||||
}
|
||||
|
||||
void sync_audio_replacements() {
|
||||
audio_replacements_dirty = false;
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> new_map;
|
||||
|
||||
for (auto const& mod : ModLoader::instance().active_mods()) {
|
||||
for (auto const& slot : s_waveReplacements.take_all(mod)) {
|
||||
auto const wave_info = wave_info_from_slot(slot.value);
|
||||
new_map.emplace(
|
||||
AudioWaveKey(slot.value.bank, slot.value.wave_id),
|
||||
AudioWaveReplacementValue(wave_info, slot.value.data));
|
||||
}
|
||||
}
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
|
||||
std::lock_guard lock(s_replacements_mutex);
|
||||
// Note: new_map will contain the old contents, and is dropped *outside* the lock.
|
||||
// As to avoid holding the lock any longer than necessary.
|
||||
std::exchange(s_replacements, std::move(new_map));
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
wsys::sync_audio_replacements();
|
||||
}
|
||||
|
||||
void replacements_remove_mod(LoadedMod& mod) {
|
||||
s_waveReplacements.erase_all(mod);
|
||||
|
||||
audio_replacements_dirty = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
std::mutex s_replacements_mutex;
|
||||
|
||||
AudioWaveReplacementValue::~AudioWaveReplacementValue() = default;
|
||||
const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const {
|
||||
return &wave_info;
|
||||
}
|
||||
|
||||
void const* AudioWaveReplacementValue::getAramBaseAddress() const {
|
||||
return data->get_data().data();
|
||||
}
|
||||
|
||||
intptr_t AudioWaveReplacementValue::getWavePtr() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<JASSampleDataReference> AudioWaveReplacementValue::getSampleReference() const {
|
||||
return std::make_unique<SampleReference>(data);
|
||||
}
|
||||
|
||||
void SampleDataPcm16::be_swap() {
|
||||
for (auto& sample : data) {
|
||||
::be_swap(sample);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace audio_res
|
||||
@@ -265,14 +40,9 @@ constinit const ServiceModule g_audioResModule{
|
||||
.majorVersion = AUDIO_RES_SERVICE_MAJOR,
|
||||
.minorVersion = AUDIO_RES_SERVICE_MINOR,
|
||||
.service = &audio_res::s_audioResService,
|
||||
.modDetached = audio_res::replacements_remove_mod,
|
||||
.modDetached = audio_res::mod_detached,
|
||||
.lifecycleApplied = audio_res::sync_audio_replacements,
|
||||
.frameEnd =
|
||||
[] {
|
||||
if (audio_res::audio_replacements_dirty) {
|
||||
audio_res::sync_audio_replacements();
|
||||
}
|
||||
}
|
||||
.frameEnd = audio_res::frame_end
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,106 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "JSystem/JAudio2/JASChannel.h"
|
||||
#include "JSystem/JAudio2/JASWaveInfo.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "mods/svc/audio_res.h"
|
||||
|
||||
namespace dusk::mods {
|
||||
struct LoadedMod;
|
||||
}
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
|
||||
struct SampleDataBuf {
|
||||
virtual ~SampleDataBuf() = default;
|
||||
[[nodiscard]] virtual std::span<std::byte const> get_data() const = 0;
|
||||
[[nodiscard]] size_t size() const {
|
||||
return get_data().size();
|
||||
}
|
||||
};
|
||||
|
||||
// Making a separate type for this rather than just using a vector<u8>
|
||||
// because I don't want aliasing or alignment to eat my face.
|
||||
struct SampleDataPcm16 : SampleDataBuf {
|
||||
std::vector<s16> data;
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
|
||||
void be_swap();
|
||||
};
|
||||
|
||||
struct SampleDataU8 : SampleDataBuf {
|
||||
std::vector<u8> data;
|
||||
|
||||
explicit SampleDataU8(std::vector<u8> data) : data(std::move(data)) {}
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
};
|
||||
|
||||
struct SampleReference : JASSampleDataReference {
|
||||
explicit SampleReference(std::shared_ptr<SampleDataBuf> data) : data(std::move(data)) {}
|
||||
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
struct AudioWaveKey {
|
||||
AudioWaveBank bank;
|
||||
u32 wave_id;
|
||||
|
||||
AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {};
|
||||
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const AudioWaveKey& k) {
|
||||
return H::combine(std::move(h), k.bank, k.wave_id);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const AudioWaveKey& other) const {
|
||||
return other.bank == bank && other.wave_id == wave_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct AudioWaveReplacementValue : JASWaveHandle {
|
||||
AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr<SampleDataBuf> data) : wave_info(wave_info), data(std::move(data)) {};
|
||||
~AudioWaveReplacementValue() override;
|
||||
[[nodiscard]] const JASWaveInfo* getWaveInfo() const override;
|
||||
[[nodiscard]] intptr_t getWavePtr() const override;
|
||||
[[nodiscard]] void const* getAramBaseAddress() const override;
|
||||
[[nodiscard]] std::unique_ptr<JASSampleDataReference> getSampleReference() const override;
|
||||
|
||||
JASWaveInfo wave_info;
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
extern absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
extern std::mutex s_replacements_mutex;
|
||||
|
||||
struct RuntimeWaveReplacementSlot {
|
||||
std::string bundle_path;
|
||||
AudioWaveBank bank;
|
||||
u16 wave_id;
|
||||
|
||||
u8 base_key;
|
||||
bool loop;
|
||||
u32 loop_start_sample;
|
||||
u32 loop_end_sample;
|
||||
|
||||
AudioWaveFormat format;
|
||||
f32 sample_rate;
|
||||
u32 sample_count;
|
||||
s16 sample_value_last;
|
||||
s16 sample_value_penult;
|
||||
|
||||
std::shared_ptr<audio_res::SampleDataBuf> data;
|
||||
};
|
||||
|
||||
using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#if DUSK_OPUS
|
||||
|
||||
#include "audio_res.hpp"
|
||||
#include "wsys.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "helpers/cast.hpp"
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::opus");
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::wsys::opus");
|
||||
|
||||
struct OpusHandle {
|
||||
OggOpusFile* file;
|
||||
@@ -24,7 +24,7 @@ struct OpusHandle {
|
||||
|
||||
}
|
||||
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
namespace dusk::mods::svc::audio_res::wsys {
|
||||
ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData) {
|
||||
OpusHead head;
|
||||
const int result = op_test(&head, fileData.data(), fileData.size());
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "audio_res.hpp"
|
||||
#include "wsys.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "helpers/alignment.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::wav");
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::wsys::wav");
|
||||
|
||||
struct ChunkHeader {
|
||||
char magic[4];
|
||||
@@ -37,7 +37,7 @@ bool check_four_cc(char const (&field)[4], char const (&expected)[5]) {
|
||||
|
||||
}
|
||||
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
namespace dusk::mods::svc::audio_res::wsys {
|
||||
|
||||
ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData) {
|
||||
using namespace helpers::alignment;
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "wsys.hpp"
|
||||
#include "../registry.hpp"
|
||||
#include "../slot_map.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "helpers/alignment.hpp"
|
||||
#include "helpers/cast.hpp"
|
||||
|
||||
namespace dusk::mods::svc::audio_res::wsys {
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace dusk::helpers::cast;
|
||||
|
||||
bool wave_replacements_dirty = false;
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res");
|
||||
|
||||
SlotMap<RuntimeWaveReplacementSlot> s_waveReplacements;
|
||||
|
||||
constexpr ContainerLoadFunction s_containerLoadFunctions[] = {
|
||||
load_wav,
|
||||
#if DUSK_OPUS
|
||||
load_opus,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) {
|
||||
u32 samples_per_block;
|
||||
u32 bytes_per_block;
|
||||
switch (raw.format) {
|
||||
case Adpcm4:
|
||||
samples_per_block = 16;
|
||||
bytes_per_block = 9;
|
||||
break;
|
||||
case Pcm16:
|
||||
samples_per_block = 1;
|
||||
bytes_per_block = 2;
|
||||
break;
|
||||
default:
|
||||
Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actual_size % bytes_per_block != 0) {
|
||||
Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
sample_count = (actual_size / bytes_per_block) * samples_per_block;
|
||||
return true;
|
||||
}
|
||||
|
||||
ModResult load_raw(
|
||||
LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
u32 sample_count = 0;
|
||||
|
||||
if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) {
|
||||
Log.error(
|
||||
"[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified",
|
||||
mod.metadata.id, slot.bundle_path);
|
||||
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (slot.format == Pcm16) {
|
||||
SampleDataPcm16 pcm16;
|
||||
pcm16.data.resize(sample_count);
|
||||
memcpy(pcm16.data.data(), file_contents.data(), file_contents.size());
|
||||
slot.data = std::make_shared<SampleDataPcm16>(std::move(pcm16));
|
||||
} else {
|
||||
slot.data = std::make_shared<SampleDataU8>(std::move(file_contents));
|
||||
}
|
||||
|
||||
slot.sample_count = sample_count;
|
||||
slot.sample_rate = raw.sample_rate;
|
||||
slot.format = raw.format;
|
||||
slot.sample_value_penult = raw.sample_value_penult;
|
||||
slot.sample_value_last = raw.sample_value_last;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an audio file stored in some sort of container format.
|
||||
* File type is determined by looking at contents, not extension.
|
||||
*/
|
||||
ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
|
||||
for (auto const loader : s_containerLoadFunctions) {
|
||||
auto const result = loader(mod, slot, file_contents);
|
||||
if (result == MOD_OK) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
if (result != MOD_UNSUPPORTED) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) {
|
||||
JASWaveInfo info;
|
||||
info.mWaveFormat = static_cast<u8>(slot.format);
|
||||
info.mBaseKey = slot.base_key;
|
||||
info.mLoopFlag = slot.loop ? 0xFF : 0;
|
||||
info.mSampleRate = slot.sample_rate;
|
||||
info.mOffsetStart = 0; // Unused but just init it ig.
|
||||
info.mOffsetLength = bounded_cast(slot.data->size());
|
||||
info.mLoopStartSample = slot.loop_start_sample;
|
||||
info.mLoopEndSample = bounded_cast(slot.loop_end_sample);
|
||||
info.mSampleCount = bounded_cast(slot.sample_count);
|
||||
info.mpLast = slot.sample_value_last;
|
||||
info.mpPenult = slot.sample_value_penult;
|
||||
// mpLoaded is initialized to point to a 1, so we're good there.
|
||||
return info;
|
||||
}
|
||||
|
||||
bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) {
|
||||
auto const result = s_waveReplacements.erase_owned(handle, mod);
|
||||
wave_replacements_dirty |= result;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
std::mutex s_replacements_mutex;
|
||||
|
||||
ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) {
|
||||
auto* mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!wave_remove(*mod, handle)) {
|
||||
Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult insert_replace_wave(
|
||||
ModContext* ctx,
|
||||
AudioWaveBank bank,
|
||||
u16 wave_id,
|
||||
char const* file_name,
|
||||
AudioWaveInfo const* wave_info,
|
||||
AudioWaveHandle* out_handle) {
|
||||
if (out_handle != nullptr) {
|
||||
*out_handle = 0;
|
||||
}
|
||||
|
||||
auto mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
RuntimeWaveReplacementSlot slot{};
|
||||
slot.bundle_path = file_name;
|
||||
slot.bank = bank;
|
||||
slot.wave_id = wave_id;
|
||||
|
||||
ModResult result;
|
||||
if (wave_info && wave_info->raw_wave) {
|
||||
result = load_raw(*mod, slot, *wave_info->raw_wave);
|
||||
} else {
|
||||
result = load_container(*mod, slot);
|
||||
}
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (wave_info != nullptr) {
|
||||
slot.base_key = wave_info->base_key;
|
||||
slot.loop = wave_info->loop;
|
||||
slot.loop_start_sample = wave_info->loop_start_sample;
|
||||
slot.loop_end_sample = wave_info->loop_end_sample;
|
||||
} else {
|
||||
slot.base_key = AUDIO_RES_DEFAULT_KEY;
|
||||
slot.loop = false;
|
||||
slot.loop_start_sample = 0;
|
||||
slot.loop_end_sample = slot.sample_count;
|
||||
}
|
||||
|
||||
wave_replacements_dirty = true;
|
||||
|
||||
const auto handle = s_waveReplacements.emplace(*mod, std::move(slot));
|
||||
if (out_handle) {
|
||||
*out_handle = handle;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void remove_mod(LoadedMod& mod) {
|
||||
s_waveReplacements.erase_all(mod);
|
||||
|
||||
wave_replacements_dirty = true;
|
||||
}
|
||||
|
||||
void sync_audio_replacements() {
|
||||
wave_replacements_dirty = false;
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> new_map;
|
||||
|
||||
for (auto const& mod : ModLoader::instance().active_mods()) {
|
||||
for (auto const& slot : s_waveReplacements.take_all(mod)) {
|
||||
auto const wave_info = wave_info_from_slot(slot.value);
|
||||
new_map.emplace(
|
||||
AudioWaveKey(slot.value.bank, slot.value.wave_id),
|
||||
AudioWaveReplacementValue(wave_info, slot.value.data));
|
||||
}
|
||||
}
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
|
||||
std::lock_guard lock(s_replacements_mutex);
|
||||
// Note: new_map will contain the old contents, and is dropped *outside* the lock.
|
||||
// As to avoid holding the lock any longer than necessary.
|
||||
std::exchange(s_replacements, std::move(new_map));
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
}
|
||||
|
||||
void frame_end() {
|
||||
if (wave_replacements_dirty) {
|
||||
sync_audio_replacements();
|
||||
}
|
||||
}
|
||||
|
||||
AudioWaveReplacementValue::~AudioWaveReplacementValue() = default;
|
||||
const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const {
|
||||
return &wave_info;
|
||||
}
|
||||
|
||||
void const* AudioWaveReplacementValue::getAramBaseAddress() const {
|
||||
return data->get_data().data();
|
||||
}
|
||||
|
||||
intptr_t AudioWaveReplacementValue::getWavePtr() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<JASSampleDataReference> AudioWaveReplacementValue::getSampleReference() const {
|
||||
return std::make_unique<SampleReference>(data);
|
||||
}
|
||||
|
||||
void SampleDataPcm16::be_swap() {
|
||||
for (auto& sample : data) {
|
||||
::be_swap(sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "JSystem/JAudio2/JASWaveInfo.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "mods/svc/audio_res.h"
|
||||
|
||||
namespace dusk::mods {
|
||||
struct LoadedMod;
|
||||
}
|
||||
|
||||
namespace dusk::mods::svc::audio_res::wsys {
|
||||
|
||||
struct SampleDataBuf {
|
||||
virtual ~SampleDataBuf() = default;
|
||||
[[nodiscard]] virtual std::span<std::byte const> get_data() const = 0;
|
||||
[[nodiscard]] size_t size() const {
|
||||
return get_data().size();
|
||||
}
|
||||
};
|
||||
|
||||
// Making a separate type for this rather than just using a vector<u8>
|
||||
// because I don't want aliasing or alignment to eat my face.
|
||||
struct SampleDataPcm16 : SampleDataBuf {
|
||||
std::vector<s16> data;
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
|
||||
void be_swap();
|
||||
};
|
||||
|
||||
struct SampleDataU8 : SampleDataBuf {
|
||||
std::vector<u8> data;
|
||||
|
||||
explicit SampleDataU8(std::vector<u8> data) : data(std::move(data)) {}
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
};
|
||||
|
||||
struct SampleReference : JASSampleDataReference {
|
||||
explicit SampleReference(std::shared_ptr<SampleDataBuf> data) : data(std::move(data)) {}
|
||||
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
struct AudioWaveKey {
|
||||
AudioWaveBank bank;
|
||||
u32 wave_id;
|
||||
|
||||
AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {};
|
||||
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const AudioWaveKey& k) {
|
||||
return H::combine(std::move(h), k.bank, k.wave_id);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const AudioWaveKey& other) const {
|
||||
return other.bank == bank && other.wave_id == wave_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct AudioWaveReplacementValue : JASWaveHandle {
|
||||
AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr<SampleDataBuf> data) : wave_info(wave_info), data(std::move(data)) {};
|
||||
~AudioWaveReplacementValue() override;
|
||||
[[nodiscard]] const JASWaveInfo* getWaveInfo() const override;
|
||||
[[nodiscard]] intptr_t getWavePtr() const override;
|
||||
[[nodiscard]] void const* getAramBaseAddress() const override;
|
||||
[[nodiscard]] std::unique_ptr<JASSampleDataReference> getSampleReference() const override;
|
||||
|
||||
JASWaveInfo wave_info;
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
extern absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
extern std::mutex s_replacements_mutex;
|
||||
|
||||
struct RuntimeWaveReplacementSlot {
|
||||
std::string bundle_path;
|
||||
AudioWaveBank bank;
|
||||
u16 wave_id;
|
||||
|
||||
u8 base_key;
|
||||
bool loop;
|
||||
u32 loop_start_sample;
|
||||
u32 loop_end_sample;
|
||||
|
||||
AudioWaveFormat format;
|
||||
f32 sample_rate;
|
||||
u32 sample_count;
|
||||
s16 sample_value_last;
|
||||
s16 sample_value_penult;
|
||||
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
void frame_end();
|
||||
void remove_mod(LoadedMod& mod);
|
||||
void sync_audio_replacements();
|
||||
|
||||
ModResult insert_replace_wave(
|
||||
ModContext* ctx,
|
||||
AudioWaveBank bank,
|
||||
u16 wave_id,
|
||||
char const* file_name,
|
||||
AudioWaveInfo const* wave_info,
|
||||
AudioWaveHandle* out_handle);
|
||||
|
||||
ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user