From cd1d12eec6ad42e8968375b3a07743ad1d0564f8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 20:50:59 +0200 Subject: [PATCH] Audio res documentation, API cleanup, bug fixes --- docs/images/jaudio.mermaid | 58 +++++ docs/images/jaudio_se_flow.svg | 1 + docs/images/jaudio_seq_flow.svg | 1 + docs/images/jaudio_stream_flow.svg | 1 + docs/jaudio.md | 4 +- mods/audio_mod/src/mod.cpp | 4 +- sdk/include/mods/svc/audio_res.h | 302 ++++++++++++++++++++-- src/dusk/mods/svc/audio_res/audio_res.cpp | 3 + src/dusk/mods/svc/audio_res/bst.cpp | 38 ++- src/dusk/mods/svc/audio_res/bst.hpp | 8 +- src/dusk/mods/svc/audio_res/opus.cpp | 2 +- src/dusk/mods/svc/audio_res/wave.cpp | 2 +- src/dusk/mods/svc/audio_res/wsys.cpp | 21 +- src/dusk/mods/svc/audio_res/wsys.hpp | 2 + 14 files changed, 410 insertions(+), 37 deletions(-) create mode 100644 docs/images/jaudio.mermaid create mode 100644 docs/images/jaudio_se_flow.svg create mode 100644 docs/images/jaudio_seq_flow.svg create mode 100644 docs/images/jaudio_stream_flow.svg diff --git a/docs/images/jaudio.mermaid b/docs/images/jaudio.mermaid new file mode 100644 index 0000000000..e5e3ee221e --- /dev/null +++ b/docs/images/jaudio.mermaid @@ -0,0 +1,58 @@ +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + subgraph BSC + bms_start[Look up BMS start] + end + Start-->|Sound ID|BSC + + BSC-->|BMS data| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|File path| ast[Stream /AudioRes/Stream/xxx.ast from disc] + + +flowchart TD + Start[Start Sound Effect] + subgraph BST + params[Look up sound parameters] + end + Start-->|Sound ID|BST + + BST-->|Resource ID| arc[Z2SoundSeqs.arc] + arc-->|BMS file| bms[BMS interpreter] + + subgraph IBNK + bnk[Look up instrument parameters] + end + + bms-->|Plays notes| IBNK + + subgraph WSYS + wsys[Look up wave sample] + end + + IBNK-->|Wave ID|WSYS diff --git a/docs/images/jaudio_se_flow.svg b/docs/images/jaudio_se_flow.svg new file mode 100644 index 0000000000..03d4bbb29b --- /dev/null +++ b/docs/images/jaudio_se_flow.svg @@ -0,0 +1 @@ +

Sound ID

Sound ID

BMS data

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BSC

Look up BMS start

BST

Look up sound parameters

Start Sound Effect

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_seq_flow.svg b/docs/images/jaudio_seq_flow.svg new file mode 100644 index 0000000000..f39ee7ca5b --- /dev/null +++ b/docs/images/jaudio_seq_flow.svg @@ -0,0 +1 @@ +

BMS file

Sound ID

Resource ID

Plays notes

Wave ID

WSYS

Look up wave sample

IBNK

Look up instrument parameters

BST

Look up sound parameters

Start Sound Effect

Z2SoundSeqs.arc

BMS interpreter

\ No newline at end of file diff --git a/docs/images/jaudio_stream_flow.svg b/docs/images/jaudio_stream_flow.svg new file mode 100644 index 0000000000..47bee926eb --- /dev/null +++ b/docs/images/jaudio_stream_flow.svg @@ -0,0 +1 @@ +

Sound ID

File path

BST

Look up sound parameters

Start Sound Effect

Stream /AudioRes/Stream/xxx.ast from disc

\ No newline at end of file diff --git a/docs/jaudio.md b/docs/jaudio.md index 70c726a548..84a0d9dcfa 100644 --- a/docs/jaudio.md +++ b/docs/jaudio.md @@ -26,7 +26,7 @@ All audio data is stored in `/Audiores` on the disc. Files are as follows: ### `/Audiores/Seqs/Z2SoundSeqs.arc` -Contains the BMS instructions for all BMS-based music and sound effects. Not all data is kept in memory at once. +Contains the BMS instructions for all BMS-based music. Not all data is kept in memory at once. ### `/Audiores/Stream/*.ast` @@ -230,7 +230,7 @@ on scene changes in TP's game code. ``` u8 mPriority; u8 mVolume; // Converted to float: mVolume * (1.0/127.0) -u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (00), left (01), or right (10). +u16 mStreamPanParameters; // Bitpacked, two bits per channel determining whether a channel is center (01), left (10), or right (11). char* mStreamFilePath[] : u32; // File path to the .ast on disc. ``` diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp index aa3da367ec..6319310eea 100644 --- a/mods/audio_mod/src/mod.cpp +++ b/mods/audio_mod/src/mod.cpp @@ -13,14 +13,14 @@ MOD_EXPORT ModResult mod_initialize(ModError*) { AudioWaveHandle handle; svc_audio_res->replace_wave( mod_ctx, - SoundEffects, + AUDIO_WAVE_BANK_SOUND_EFFECTS, 4238, "res/go.opus", nullptr, &handle); svc_audio_res->replace_wave( mod_ctx, - SoundEffects, + AUDIO_WAVE_BANK_SOUND_EFFECTS, 4237, "res/go.opus", nullptr, diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index a5a0b7b71e..ae57e3a75c 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -6,43 +6,117 @@ #define AUDIO_RES_SERVICE_MAJOR 1u #define AUDIO_RES_SERVICE_MINOR 0u +/* + * Defines APIs for replacing and adding audio resources. + * + * TP's audio engine (JAudioV2) is extremely complex. For more of an overview through its + * functionality, please see docs/jaudio.md in the repo. + */ + /* * WSYS structs */ -#define AUDIO_RES_DEFAULT_KEY 0x3C; - -typedef enum AudioWaveBank : uint8_t { - SoundEffects = 0, - MusicSamples = 1, -} AudioWaveBank; - -typedef enum AudioWaveFormat : uint8_t { - Adpcm4 = 0, - Adpcm2 = 1, - Pcm8 = 2, - Pcm16 = 3, -} AudioWaveFormat; - -typedef uint64_t AudioWaveHandle; -typedef uint64_t AudioSoundTableHandle; +/** + * "Default" key for wave definitions. Key like musical key, not map keys or whatever. + */ +#define AUDIO_RES_DEFAULT_KEY 0x3C /** + * Defines TP's raw wave banks, which can be modified. + */ +typedef enum AudioWaveBank : uint8_t { + AUDIO_WAVE_BANK_SOUND_EFFECTS = 0, + AUDIO_WAVE_BANK_MUSIC_SAMPLES = 1, +} AudioWaveBank; + +/** + * Defines formats for raw sample data the DSP can play back. + */ +typedef enum AudioWaveFormat : uint8_t { + /** + * 16 samples per 9 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM4 = 0, + + /** + * 16 samples per 5 bytes custom Nintendo ADPCM. + */ + AUDIO_WAVE_FORMAT_ADPCM2 = 1, + + /** + * 1-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM8 = 2, + + /** + * 2-byte-per-sample simple PCM. + */ + AUDIO_WAVE_FORMAT_PCM16 = 3, +} AudioWaveFormat; + +/** + * Handle to reference wave replacements/additions by mods. + */ +typedef uint64_t AudioWaveHandle; + +/** + * Data needed to load a raw audio data file. * + * @see AudioWaveInfo */ typedef struct AudioRawWave { + /** + * Format of the raw sample data. + */ AudioWaveFormat format; + + /** + * Sample rate, in Hertz. + */ float sample_rate; + + /** + * Last audio sample before ADPCM loop start point. (unused if not looping) + */ int16_t sample_value_last; + + /** + * Second-to-last audio sample before ADPCM loop start point. (unused if not looping) + */ int16_t sample_value_penult; } AudioRawWave; +/** + * Metadata needed to play a wave. + */ typedef struct AudioWaveInfo { + /** + * Base key (musical key). Set to AUDIO_RES_DEFAULT_KEY if you don't care. + */ uint8_t base_key; + + /** + * If true, the sample loops. + */ bool loop; + + /** + * Sample number where loop resumes when end is reached. + * Does nothing if not looping. + */ uint32_t loop_start_sample; + + /** + * End of the wave. Stops playback if not looping. + * Automatically clamped to the sample count from the file, if greater. + */ uint32_t loop_end_sample; + /** + * If provided, specifies that the passed file is "raw" and only contains sample data. + * This must be used if you want to ship ADPCM samples, as other containers do not support that. + */ AudioRawWave const* raw_wave; } AudioWaveInfo; @@ -50,6 +124,9 @@ typedef struct AudioWaveInfo { * BST structs */ +/** + * Game defined categories (groups) for sound effects. + */ typedef enum SoundEffectCategory : uint8_t { SE_CATEGORY_SYSTEM_SE, SE_CATEGORY_PLAYER_VOICE, @@ -63,44 +140,131 @@ typedef enum SoundEffectCategory : uint8_t { SE_CATEGORY_ENV_SE } SoundEffectCategory; +/** + * Data that can be defined for a sound effect in the sound table. + */ typedef struct AudioSoundTableEffectInfo { + /** + * Priority relative to other sound effects. Can affect culling and such if + * many effects are playing. + */ uint8_t priority; + + /** + * Volume multiplier for this sound effect. Clamped to range 0-2. + */ float volume; + + /** + * Pitch multiplier for this sound effect. + */ float pitch; + /** + * If true, the effect is always treated as max priority, regardless of factors like distance. + */ bool always_max_priority; + /** + * Don't calculate volume changes by distance. + */ bool ignore_distance_volume; + + /** + * Don't FX Mix (reverb) changes by distance. + */ bool ignore_distance_fx_mix; + + /** + * Don't calculate panning (left/right balance). + */ bool ignore_pan; + + /** + * Don't calculate dolby (front/back balance). + */ bool ignore_dolby; + /** + * 0-15 value controlling volume randomization strength. + */ uint8_t random_volume; + + /** + * 0-15 value controlling pitch randomization strength. + */ uint8_t random_pitch; + /** + * 0-15 value controlling Doppler effect strength. + */ uint8_t doppler_power; + /** + * Value from 0-15 selecting "volume distance" class. This effectively selects a fixed curve for + * parameters like volume by distance. + */ uint8_t volume_dist_class; + + /** + * Limit minimum volume of sound (after distance drop-off) to 0.2. + */ bool clamp_min_volume; + + /** + * Treat this sound as "far away" or "culled" at "max distance." + * Affects things like automatic stopping. + */ bool cull_at_max_distance; } AudioSoundTableEffectInfo; +/** + * Stereo panning parameters for stream channels. + */ typedef enum StreamPan : uint8_t { STREAM_PAN_CENTER, STREAM_PAN_LEFT, STREAM_PAN_RIGHT, } StreamPan; +/** + * Maximum amount of channels a streamed track can have. + */ #define STREAM_MAX_CHILDREN 6 +/** + * Data that can be defined for a stream in the sound table. + */ typedef struct AudioSoundTableStreamInfo { + /** + * Unsure if used. + */ uint8_t priority; + + /** + * Volume multiplier for this stream. Clamped to range 0-2. + */ float volume; + + /** + * For each channel in the loaded .ast file, specifies the panning position for said channel. + */ StreamPan pan_parameters[STREAM_MAX_CHILDREN]; - char const* file_path; + + /** + * Whether this stream automatically stops on scene change. + */ bool stop_on_scene_change; } AudioSoundTableStreamInfo; +/** + * Handle to reference sound table replacements/additions by mods. + */ +typedef uint64_t AudioSoundTableHandle; + +/** + * Defines APIs for replacing and adding audio resources. + */ typedef struct AudioResService { ServiceHeader header; @@ -108,6 +272,32 @@ typedef struct AudioResService { * WSYS API */ + /** + * Default wave info if none is provided: default key, no loop, not raw audio. + */ + AudioWaveInfo const* default_wave_info; + + /** + * Replace an existing audio wave in the game. This replacement will follow mod order + * prioritization. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param wave_id ID of the wave to replace. This does *not* directly correlate to sound table + * entries or JAISound values in any way! + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This + * does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable + * defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used + * with @ref remove_wave. + */ ModResult (*replace_wave)( ModContext* ctx, AudioWaveBank bank, @@ -116,6 +306,22 @@ typedef struct AudioResService { AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + /** + * Add a new audio wave to the game. The service allocates the placed ID for you. + * + * The sound effect will remain permanently resident in memory. + * + * Sound effects can be provided as raw samples (if raw_wave data is provided in wave_info), + * WAVE (.wav) file, or OGG Opus (.opus, if Dusklight is compiled with support). Container + * formats are detected based on header, not based on file name. + * + * @param ctx Pointer to your mod's context. + * @param bank Which wave bank to replace a sound effect in. + * @param file_name Path of the audio file to load in the mod's data, e.g. res/foo.opus. This does *not* need to be an overlay file! + * @param wave_info Optional: metadata for the new wave. If not provided will use reasonable defaults (@ref default_wave_info). + * @param out_handle Optional: pointer receives the handle for the addition. This can be used with @ref remove_wave. + * @param out_wave_id Receives the allocated ID in the wave bank. + */ ModResult (*add_wave)( ModContext* ctx, AudioWaveBank bank, @@ -124,12 +330,33 @@ typedef struct AudioResService { AudioWaveHandle* out_handle, uint16_t* out_wave_id); + /** + * Remove a wave addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); /* * BST API */ + /** + * Default sound effect info if none is provided: default volume, pitch, medium priority. + */ + AudioSoundTableEffectInfo const* default_effect_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param effect_id ID of the effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ ModResult (*replace_sound_table_effect)( ModContext* ctx, SoundEffectCategory category_id, @@ -137,6 +364,15 @@ typedef struct AudioResService { AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle); + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param category_id Category of the sound effect. + * @param info Optional: new parameters for the sound. Falls back to @ref default_effect_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_effect_id Receives the allocated ID of the sound effect. + */ ModResult (*add_sound_table_effect)( ModContext* ctx, SoundEffectCategory category_id, @@ -144,18 +380,50 @@ typedef struct AudioResService { AudioSoundTableHandle* out_handle, uint16_t* out_effect_id); + /** + * Default sound effect info if none is provided: default volume, medium priority, stereo channels, *no* stop on scene change. + */ + AudioSoundTableStreamInfo const* default_stream_info; + + /** + * Replace a sound effect's parameters in the sound table. This replacement will follow mod order + * prioritization. + * + * @param ctx Pointer to your mod's context. + * @param stream_id ID of the stream. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + */ ModResult (*replace_sound_table_stream)( ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle); + /** + * Add a sound effect to the sound table. The service allocates the placed ID for you. + * + * @param ctx Pointer to your mod's context. + * @param file_path Path of the .ast on disc. If you're providing one yourself, use an overlay! + * @param info Optional: new parameters for the stream. Falls back to @ref default_stream_info if not provided. + * @param out_handle Optional: pointer receives the handle for the replacement. This can be used with @ref remove_sound_table. + * @param out_stream_id Receives the allocated ID of the stream. + */ ModResult (*add_sound_table_stream)( ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id); + /** + * Remove a sound table addition/replacement previously created by this mod. + * + * @param ctx Pointer to your mod's context. + * @param handle The handle identifying which wave to remove. + */ ModResult (*remove_sound_table)(ModContext* ctx, AudioSoundTableHandle handle); } AudioResService; diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 7550b360cc..1c8f359c75 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -16,11 +16,14 @@ using namespace dusk::helpers::cast; constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .default_wave_info = &wsys::default_wave_info, .replace_wave = &wsys::insert_replace_wave, .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, + .default_effect_info = &bst::default_effect_info, .replace_sound_table_effect = &bst::replace_sound_table_effect, .add_sound_table_effect = &bst::add_sound_table_effect, + .default_stream_info = &bst::default_stream_info, .replace_sound_table_stream = &bst::replace_sound_table_stream, .add_sound_table_stream = &bst::add_sound_table_stream, .remove_sound_table = &bst::remove_sound_table, diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp index e47ad2d216..9dcf635c2a 100644 --- a/src/dusk/mods/svc/audio_res/bst.cpp +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -35,6 +35,7 @@ bool validate_category(SoundEffectCategory const category) { } uint8_t volume_to_item(float volume) { + volume = std::clamp(volume, 0.0f, 2.0f); return static_cast(volume * 127); } @@ -90,8 +91,8 @@ u8 SoundEffectReplacementSlot::get_type_id() const { } StreamReplacementSlot::StreamReplacementSlot( - bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info) - : SoundTableReplacementSlot(mod_defined, id), file_path(info.file_path) { + bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info) + : SoundTableReplacementSlot(mod_defined, id), file_path(file_path) { stop_on_scene_change = info.stop_on_scene_change; item.mPriority = info.priority; item.mVolume = volume_to_item(info.volume); @@ -216,10 +217,14 @@ static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCate } auto mod = mod_from_context(ctx); - if (mod == nullptr || info == nullptr || !validate_category(category_id)) { + if (mod == nullptr || !validate_category(category_id)) { return MOD_INVALID_ARGUMENT; } + if (info == nullptr) { + info = &default_effect_info; + } + auto slot = std::make_shared(mod_defined, effect_id, category_id, *info); sound_replacements_dirty = true; @@ -236,6 +241,8 @@ ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory catego return insert_sound_table_effect_core(ctx, category_id, effect_id, false, info, out_handle); } +AudioSoundTableEffectInfo const default_effect_info(128, 1, 1); + ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_effect_id) { @@ -263,6 +270,7 @@ static ModResult insert_sound_table_stream_core( ModContext* ctx, uint16_t stream_id, bool mod_defined, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { if (out_handle != nullptr) { @@ -270,11 +278,15 @@ static ModResult insert_sound_table_stream_core( } auto mod = mod_from_context(ctx); - if (mod == nullptr || info == nullptr) { + if (mod == nullptr || file_path == nullptr) { return MOD_INVALID_ARGUMENT; } - auto slot = std::make_shared(mod_defined, stream_id, *info); + if (info == nullptr) { + info = &default_stream_info; + } + + auto slot = std::make_shared(mod_defined, stream_id, file_path, *info); sound_replacements_dirty = true; auto const handle = sound_replacements.emplace(*mod, std::move(slot)); @@ -285,12 +297,22 @@ static ModResult insert_sound_table_stream_core( return MOD_OK; } +AudioSoundTableStreamInfo const default_stream_info( + 128, + 1, + { + STREAM_PAN_LEFT, + STREAM_PAN_RIGHT + }); + ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { - return insert_sound_table_stream_core(ctx, stream_id, false, info, out_handle); + return insert_sound_table_stream_core(ctx, stream_id, false, file_path, info, out_handle); } -ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo const* info, +ModResult add_sound_table_stream(ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id) { if (out_stream_id == nullptr) { return MOD_INVALID_ARGUMENT; @@ -298,7 +320,7 @@ ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo cons auto const stream_id = stream_id_allocator.alloc(); - auto const result = insert_sound_table_stream_core(ctx, stream_id, true, info, out_handle); + auto const result = insert_sound_table_stream_core(ctx, stream_id, true, file_path, info, out_handle); if (result != MOD_OK) { stream_id_allocator.free(stream_id); } diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp index de13534f20..67b6c1aa18 100644 --- a/src/dusk/mods/svc/audio_res/bst.hpp +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -39,7 +39,7 @@ struct StreamReplacementSlot final : SoundTableReplacementSlot { std::string file_path; bool stop_on_scene_change; - StreamReplacementSlot(bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info); + StreamReplacementSlot(bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info); ~StreamReplacementSlot() override = default; @@ -68,6 +68,8 @@ void frame_end(); void remove_mod(LoadedMod const& mod); void sync_audio_replacements(); +extern AudioSoundTableEffectInfo const default_effect_info; + ModResult replace_sound_table_effect( ModContext* ctx, SoundEffectCategory category_id, @@ -82,14 +84,18 @@ ModResult add_sound_table_effect( AudioSoundTableHandle* out_handle, uint16_t* out_effect_id); +extern AudioSoundTableStreamInfo const default_stream_info; + ModResult replace_sound_table_stream( ModContext* ctx, uint16_t stream_id, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle); ModResult add_sound_table_stream( ModContext* ctx, + char const* file_path, AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id); diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp index aa4e6bea99..f885a6e5c6 100644 --- a/src/dusk/mods/svc/audio_res/opus.cpp +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -84,7 +84,7 @@ ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std: slot.data = std::make_shared(std::move(pcm_buffer)); slot.sample_rate = 48000; // Opus always decodes at 48 kHz. slot.sample_count = length; - slot.format = Pcm16; + slot.format = AUDIO_WAVE_FORMAT_PCM16; return MOD_OK; } diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index ae97a8f81b..5835fbcc6f 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -135,7 +135,7 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: pcmData.be_swap(); - slot.format = Pcm16; + slot.format = AUDIO_WAVE_FORMAT_PCM16; slot.sample_count = pcmData.size() / sizeof(u16); slot.data = std::make_unique(std::move(pcmData)); diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index d27c759520..7899bb117c 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -29,18 +29,18 @@ PlainIdAllocator sound_effect_id_allocator(5'000); PlainIdAllocator music_sample_id_allocator(1'000); PlainIdAllocator& id_allocator_for_bank(AudioWaveBank const bank) { - return bank == SoundEffects ? sound_effect_id_allocator : music_sample_id_allocator; + return bank == AUDIO_WAVE_BANK_SOUND_EFFECTS ? sound_effect_id_allocator : music_sample_id_allocator; } 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: + case AUDIO_WAVE_FORMAT_ADPCM4: samples_per_block = 16; bytes_per_block = 9; break; - case Pcm16: + case AUDIO_WAVE_FORMAT_PCM16: samples_per_block = 1; bytes_per_block = 2; break; @@ -71,7 +71,7 @@ ModResult load_raw( return MOD_INVALID_ARGUMENT; } - if (slot.format == Pcm16) { + if (slot.format == AUDIO_WAVE_FORMAT_PCM16) { SampleDataPcm16 pcm16; pcm16.data.resize(sample_count); memcpy(pcm16.data.data(), file_contents.data(), file_contents.size()); @@ -179,6 +179,15 @@ ModResult insert_replace_wave_core( slot.loop = wave_info->loop; slot.loop_start_sample = wave_info->loop_start_sample; slot.loop_end_sample = wave_info->loop_end_sample; + + if (slot.loop_end_sample > slot.sample_count) { + slot.loop_end_sample = slot.sample_count; + } + + if (slot.loop_start_sample >= slot.loop_end_sample) { + Log.error("[{}] wave has start >= end", mod->metadata.id); + return MOD_INVALID_ARGUMENT; + } } else { slot.base_key = AUDIO_RES_DEFAULT_KEY; slot.loop = false; @@ -202,6 +211,8 @@ ModResult insert_replace_wave_core( absl::flat_hash_map s_replacements; std::mutex s_replacements_mutex; +AudioWaveInfo const default_wave_info(AUDIO_RES_DEFAULT_KEY, false, 0, std::numeric_limits::max(), nullptr); + ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { auto* mod = mod_from_context(ctx); if (mod == nullptr || handle == 0) { @@ -239,7 +250,7 @@ ModResult insert_add_wave( *out_wave_id = 0; - if (bank != SoundEffects && bank != MusicSamples) { + if (bank != AUDIO_WAVE_BANK_SOUND_EFFECTS && bank != AUDIO_WAVE_BANK_MUSIC_SAMPLES) { return MOD_INVALID_ARGUMENT; } diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index 16d144e4b3..79b66b70e1 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -108,6 +108,8 @@ void frame_end(); void remove_mod(LoadedMod& mod); void sync_audio_replacements(); +extern AudioWaveInfo const default_wave_info; + ModResult insert_replace_wave( ModContext* ctx, AudioWaveBank bank,