From 079c969bb4502f90b494ae3dda8a72744474b54f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:15:15 +0200 Subject: [PATCH 01/36] Some JAudio markdown docs, WSYS filled out. --- docs/imhex/jaudio/wsys.hexpat | 228 ++++++++++++++++++++++++++++++++++ docs/jaudio.md | 90 ++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 docs/imhex/jaudio/wsys.hexpat create mode 100644 docs/jaudio.md diff --git a/docs/imhex/jaudio/wsys.hexpat b/docs/imhex/jaudio/wsys.hexpat new file mode 100644 index 0000000000..88027ade04 --- /dev/null +++ b/docs/imhex/jaudio/wsys.hexpat @@ -0,0 +1,228 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/* +WSYS file +The WSYS file contains roughly two sections: WBCT and WINF. +WBCT contains the mapping of Wave ID -> wave archives. +WINF are the wave archives themselves and their metadata. +*/ + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +/** + * Format that audio data is in.. + */ +enum WaveFormat : u8 { + /** + * 16-samples-per-9-bytes custom Nintendo ADPCM. + */ + ADPCM4 = 0, + + /** + * 16-samples-per-5-bytes custom Nintendo ADPCM. + */ + ADPCM2 = 1, + + /** + * 8-bit-per-sample PCM. + */ + PCM8 = 2, + + /** + * 16-bit-per-sample PCM. + */ + PCM16 = 3, +}; + +/** + * Defines playback info for a single audio "wave". + * This data is stored per archive, making it duplicated (except for mAWOffsetStart) + */ +struct TWave { + padding[1]; // unknown + WaveFormat mWaveFormat; + + /** + * Key (as in like, the musical term) this sample is in. + */ + u8 mBaseKey; + padding[1]; + + /** + * Sample rate of the audio in Hz. + */ + float mSampleRate; + + /** + * Position where the sample data starts. + * This is in the .aw file for the archive containing this TWave. + */ + u32 mAWOffsetStart; + + /** + * Byte length of the sample data. + */ + u32 mAWLength; + + /** + * Indicates whether the sample should loop or not. All bits appear set if so. + */ + u32 mLoopFlags; + + /** + * Audio sample at which the loop starts. + */ + u32 mLoopStartSample; + + /** + * Audio sample at which the loop ends (and goes back to mLoopStartSample). + */ + u32 mLoopEndSample; + + /** + * Total sample count in this wave. + */ + u32 mSampleCount; + + /** + * Last sample for continuing ADPCM decode after loop. + */ + s16 mpLast; + + /** + * Penult sample for continuing ADPCM decode after loop. + */ + s16 mpPenult; +}; + +/** + * A single wave archive on disk. + * These are paired 1:1 with TCtrlScene objects. + */ +struct TWaveArchive { + /** + * Filename of the raw sample data on disc. Relative to /Audiores/Waves/ + */ + char mFileName[0x70]; + + /** + * Amount of waves in this archive. + * Matches the count in the paired TCtrl object. + */ + u32 mWaveCount; + + /** + * Offsets to the wave metadata (not sample data) in the WSYS. + * These are paired 1:1 to the TCtrlWaves, and the TCtrlWave contains the actual + * "Wave ID" used by the game for lookups. + */ + Offset waveOffsets[mWaveCount]; +}; + +/** + * Header containing data for wave archives and their metadata. + */ +struct TWaveArchiveBank { + type::Magic<"WINF"> mMagic; + + /** + * Amount of archives in this wave bank. + * Matches the value in TCtrlGroup. + */ + u32 mArchiveCounts; + Offset mArchiveOffsets[mArchiveCounts]; +}; + +/** + * Definition for a single wave in a control group. + */ +struct TCtrlWave { + /** + * Group ID matches the index of the control group this item is referenced by. + */ + u16 mGroupId; + + /** + * Wave ID used by the game to look this wave up. + */ + u16 mWaveId; +}; + +/** + * Contains the actual data for a TCtrlScene. + * Why is this separate? Who knows. + */ +struct TCtrl { + // Other versions of this struct with different magic (C-EX and C-ST) also exist in the file. + // They aren't pointed to so we don't need to worry about them. + type::Magic<"C-DF"> mMagic; + + /** + * Amount of waves in this group. + * Matches the value in TWaveArchive. + */ + u32 waveCount; + Offset mWaveOffsets[waveCount]; +}; + +/** + * A single scene or "group" of waves that are loaded at once. + */ +struct TCtrlScene { + type::Magic<"SCNE"> mMagic; + + padding[8]; // unknown + TCtrl* mCtrlOffset : u32 [[inline]]; +}; + +/** + * Contains the "control" section of the WSYS. + */ +struct TCtrlGroup { + type::Magic<"WBCT"> mMagic; + + padding[4]; // unknown + u32 mGroupCount; + + Offset mCtrlSceneOffsets[mGroupCount]; +}; + +struct THeader { + type::Magic<"WSYS"> mMagic; + + /** + * Size of WSYS in bytes. + */ + u32 mSize; + + /** + * ID of wave bank. + * This matches the value passed to the BAA load command. + * The game does not use this value itself. + */ + u32 mId; + + /** + * Total amount of waves in this wave bank. + * (not groups! Waves!) + */ + u32 mWaveTableSize; + + TWaveArchiveBank* archiveBankOffset : u32; + TCtrlGroup* ctrlGroupOffset : u32; +}; + +THeader header @ 0x0; + +std::assert( + header.archiveBankOffset.mArchiveCounts == header.ctrlGroupOffset.mGroupCount, + "Control group and archive count does not match!"); \ No newline at end of file diff --git a/docs/jaudio.md b/docs/jaudio.md new file mode 100644 index 0000000000..a42df217b2 --- /dev/null +++ b/docs/jaudio.md @@ -0,0 +1,90 @@ +# JAudio + +**JAudio** is the name for the audio engine used by Twilight Princess (along with many other Nintendo games from the era). TP uses exclusively JAudio v2, while other games use v1 or a mix of v1 and v2 infrastructure. This document will primarily focus on TP's use case, but best efforts will be made to document where behavior is TP-specific. + +## Common concepts + +Audio is almost exclusively + +### `JAISoundID` + +A "sound", be that a _sound effect_ or music, is referenced in-code through the `JAISoundID` type. This is a `u32` that the game uses to look up the relevant sound. The actual value is bitpacked (BE) from the following fields: + +| Type | Field | Description | +|-------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `u8` | Section ID | Section of the Sound Table this sound effect is located in. 0 for sound effects, 1 for sequenced music, 2 for streamed music.[^sectionids] | +| `u8` | Group ID | ID of group further used to organize inside the Sound Table's Section. Sound effects are grouped into things like "SYSTEM" and "ENEMY", other sections leave this at 0. | +| `u16` | "Wave ID" | Index inside the group to look up the sound at.
**Note** that the term "wave" in code is extremely confusing[^waveterm]; it does **not** refer to audio samples ("waves") in the wave banks directly. | + +[^sectionids]: JAudio itself can seemingly work outside this convention, however it is enforced by some TP-specific game code. + +[^waveterm]: Possibly vestigial from JAudio v1, where I believe there were less layers of indirection. + +## Disc files + +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. + +### `/Audiores/Stream/*.ast` + +Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. + +### `/Audiores/Waves/*.aw` + +Contains audio sample data for sequenced music and sound effects. These files are pure meat, no bone: they are loaded directly into ARAM and all metadata is stored in the BAA WSYS sections. + +Each file contains audio samples for one "scene", with factors like the current level determining what "scenes" are made resident in memory. There is tons of duplicate data between scenes, presumably to increase simplicity and loading performance. + +### `/Audiores/Z2Sound.baa` + +Contains all remaining metadata for the audio system. This is effectively a container for a bunch of different sub-sections. The file starts with a bunch of "commands" that indicate where other data in the file is. Each command has a 4-character identifier and depending on the command will be followed by some extra arguments before the next command. + +The commands used by TP's BAA file are as follows (note that the decompiled code has support for more load commands, which are unused): + +| Command/Argument | Value | Description | +|------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| Command | `AA_<` | Start of BAA commands. Must be at the start of the file and only appear once. | +| Command | `>_AA` | End of BAA commands. | +| Command | `ws `[^spaces] | Wave bank/WSYS data. Defines where audio samples are located on disc. | +| Argument | u32 | Wave bank ID, max 255. In TP, 0 is sound effects, 1 is music samples. | +| Argument | u32 | File offset for start of `WSYS` data | +| Argument | u32 | Bit field selecting which groups (= `.aw` files) to load immediately. TP leaves this at zero.[^32ws] | +| Command | `bnk `[^spaces] | Instrument bank/IBNK | +| Argument | u32 | Target wave bank ID | +| Argument | u32 | File offset for start of `IBNK` data | +| Command | `bsc `[^spaces] | Sound effect sequence collection. | +| Argument | u32 | File offset for start of `SC` data. | +| Argument | u32 | File offset for end of `SC` data. | +| Command | `bst `[^spaces] | Sound table. Defines parameters for music and sound effects. | +| Argument | u32 | File offset for start of `BST `[^spaces] data. | +| Argument | u32 | File offset for end of `BST `[^spaces] data. | +| Command | `bstn` | Sound name table. Defines names of all music and sound effects. Present on disc, but loading is disabled on release versions of the game. | +| Argument | u32 | File offset for start of `BSTN` data. | +| Argument | u32 | File offset for end of `BSTN` data. | +| Command | `bfca` | Unknown, something related to initialization of DSP FX data. | +| Argument | u32 | File offset for start of `RARC` data. | + +[^spaces]: Padded with spaces. + +[^32ws]: Both of TP's wave banks have far more than 32 groups, and seemingly this mechanism would not be able to deal with that. + +Following is data descriptions for the remaining data in the `.BAA`, as pointed to by the above commands. + +### `WSYS` / Wave banks + +`WSYS` / Wave bank data defines where a set of audio samples can be found on disc. Each wave bank is made of multiple "groups", where each group corresponds to one `.aw` file on disc. Each group has a set of "wave IDs" it contains, along with the metadata (e.g. sample rate) and data offset in the `.aw` file. + +Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk. + +For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) + +*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* + +## Further reading & credits + +* https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP) +* XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/) +* The decompiled source code, duh. \ No newline at end of file From 226a1fc4827eaad96e244fe369704a83b7ef2308 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:18:37 +0200 Subject: [PATCH 02/36] read_unaligned helper "Things C++ should've had built-in 2 decades ago" --- include/helpers/alignment.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 include/helpers/alignment.hpp diff --git a/include/helpers/alignment.hpp b/include/helpers/alignment.hpp new file mode 100644 index 0000000000..7ce0ead69a --- /dev/null +++ b/include/helpers/alignment.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace dusk::helpers::alignment { + +/** + * Read data from an address that may not be aligned properly. + * @tparam T Type of data to read. + * @param ptr Address to read from. + * @return The copied value. + */ +template requires std::is_trivially_copyable_v +[[nodiscard]] constexpr T read_unaligned(u8 const* ptr) { + T copy; + memcpy(©, ptr, sizeof(T)); + return copy; +} + +} From a6c4419da6f559cace4a0ee8a363f5f341cc9873 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:26:15 +0200 Subject: [PATCH 03/36] cast.hpp helper I was tired of manually bounds-checking my integer casts. --- files.cmake | 1 + include/helpers/cast.hpp | 48 ++++++++++++++++++++++++++++++++++++++++ src/helpers/cast.cpp | 14 ++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 include/helpers/cast.hpp create mode 100644 src/helpers/cast.cpp diff --git a/files.cmake b/files.cmake index c8c92c9b75..51b2bdf95e 100644 --- a/files.cmake +++ b/files.cmake @@ -1581,6 +1581,7 @@ set(DUSK_FILES src/helpers/endian.cpp src/helpers/offset_ptr.cpp src/helpers/string.cpp + src/helpers/cast.cpp ) set(DUSK_HTTP_BACKEND_FILES diff --git a/include/helpers/cast.hpp b/include/helpers/cast.hpp new file mode 100644 index 0000000000..206bc1e3f3 --- /dev/null +++ b/include/helpers/cast.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +/** + * Helper functions for performing casts. + */ +namespace dusk::helpers::cast { + /** + * Implementation details of dusk::helpers::cast. + */ + namespace _impl { + [[noreturn]] void overrun_high(); + [[noreturn]] void overrun_low(); + } + + template + concept IntCastable = std::is_integral_v && std::is_trivially_copyable_v; + + /** + * Helper type that allows easily casting between integer types, + * that safely aborts if an overflow were to occur. + * Usage: + * @code + * size_t foobar = 20; + * int real = bounded_cast(foobar); + * @endcode + */ + template + struct bounded_cast { + Source src; + + template + [[nodiscard]] constexpr operator Target() const { + if (std::cmp_greater(src, std::numeric_limits::max())) [[unlikely]] { + _impl::overrun_high(); + } + + if (std::cmp_less(src, std::numeric_limits::min())) [[unlikely]] { + _impl::overrun_low(); + } + + return static_cast(src); + } + }; +} diff --git a/src/helpers/cast.cpp b/src/helpers/cast.cpp new file mode 100644 index 0000000000..71eb3160de --- /dev/null +++ b/src/helpers/cast.cpp @@ -0,0 +1,14 @@ +#include "helpers/cast.hpp" +#include "global.h" + +namespace dusk::helpers::cast::_impl { + +void overrun_high() { + CRASH("bounded cast overran!"); +} + +void overrun_low() { + CRASH("bounded cast overran!"); +} + +} // namespace dusk::helpers::cast::_impl From 6ded3ac467204c524ceb2a29ffadd1cbe7245608 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:32:02 +0200 Subject: [PATCH 04/36] JAudio naming/docs pass --- .../JSystem/JAudio2/JASBasicWaveBank.h | 14 +++--- .../include/JSystem/JAudio2/JASWSParser.h | 9 ++-- .../JSystem/JAudio2/JASWaveArcLoader.h | 2 +- .../include/JSystem/JAudio2/JASWaveInfo.h | 4 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 44 ++++++++++--------- libs/JSystem/src/JAudio2/JASAramStream.cpp | 2 +- libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp | 30 ++++++------- libs/JSystem/src/JAudio2/JASChannel.cpp | 4 +- .../JSystem/src/JAudio2/JASSimpleWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASWSParser.cpp | 8 ++-- libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp | 8 ++-- 11 files changed, 66 insertions(+), 61 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index f6a10fa0c7..5a84750e3b 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -12,21 +12,21 @@ struct JASBasicWaveBank : public JASWaveBank { struct TWaveHandle : public JASWaveHandle { TWaveHandle() { mHeap = NULL; } virtual intptr_t getWavePtr() const; - virtual const JASWaveInfo* getWaveInfo() const { return &field_0x4; } + virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; } bool compareHeap(JASHeap* heap) const { return mHeap == heap;} - /* 0x04 */ JASWaveInfo field_0x4; + /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; }; struct TGroupWaveInfo { TGroupWaveInfo() { - field_0x0 = 0xffff; - field_0x4 = -1; + waveId = 0xffff; + mOffsetStart = -1; } - /* 0x00 */ u16 field_0x0; - /* 0x04 */ int field_0x4; + /* 0x00 */ u16 waveId; + /* 0x04 */ int mOffsetStart; }; struct TWaveGroup : JASWaveArc { @@ -56,7 +56,7 @@ struct JASBasicWaveBank : public JASWaveBank { JASWaveArc* getWaveArc(u32 param_0) { return getWaveGroup(param_0); } u32 getArcCount() const { return mGroupCount; } - /* 0x04 */ OSMutex field_0x4; + /* 0x04 */ OSMutex mWaveTableMutex; /* 0x1C */ TWaveHandle* mWaveTable; /* 0x20 */ TWaveGroup* mWaveGroupArray; /* 0x24 */ u16 mHandleCount; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h index 9fe201308c..da9af7c37c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWSParser.h @@ -26,7 +26,9 @@ public: }; struct TCtrlWave { - /* 0x0 */ BE(u32) _00; + // Wave ID in lower half. + // Upper bits appear to be group ID, which is unused by the game code. + /* 0x0 */ BE(u32) mWaveAndGroupId; }; struct TWave { @@ -57,13 +59,14 @@ public: }; struct TCtrl { - /* 0x0 */ u8 _00[4]; + /* 0x0 */ BE(u32) mMagic; // 'C-DF'. /* 0x4 */ BE(u32) mWaveCount; /* 0x8 */ TOffset mCtrlWaveOffsets[0]; }; struct TCtrlScene { - /* 0x0 */ u8 _00[0xC]; + /* 0x0 */ BE(u32) mMagic; // 'SCNE' + /* 0x4 */ u8 _00[0x8]; /* 0xC */ TOffset mCtrlOffset; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h index 0ad4e044c8..7caa344c8f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveArcLoader.h @@ -66,7 +66,7 @@ struct JASWaveArc : JASDisposer { }; /* 0x04 */ mutable JASHeap mHeap; - /* 0x48 */ u32 _48; + /* 0x48 */ u32 mCurrentlyLoaded; /* 0x4C */ volatile s32 mStatus; /* 0x50 */ int mEntryNum; /* 0x54 */ u32 mFileLength; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 04bd541204..9d1c16d775 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -17,7 +17,7 @@ struct JASWaveArc; struct JASWaveInfo { JASWaveInfo() { mBaseKey = 0x3c; - field_0x20 = &one; + mpLoaded = &one; } /* 0x00 */ u8 mWaveFormat; @@ -31,7 +31,7 @@ struct JASWaveInfo { /* 0x18 */ int mSampleCount; /* 0x1C */ s16 mpLast; /* 0x1E */ s16 mpPenult; - /* 0x20 */ const u32* field_0x20; + /* 0x20 */ const u32* mpLoaded; static DUSK_GAME_DATA u32 one; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index fbd3427649..3571e386b6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -24,23 +24,25 @@ struct JAUSoundTableItem { template struct JAUSoundTable_ { JAUSoundTable_() { - field_0x0 = NULL; - field_0x4 = 0; + mData = NULL; + mRoot = 0; } void reset() { - field_0x0 = NULL; - field_0x4 = NULL; + mData = NULL; + mRoot = NULL; } void init(const void* param_0) { - field_0x0 = param_0; + mData = param_0; // magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?) // I also do not know how it is different between JAUSoundTable and JAUSoundNameTable - if (*(BE(u32)*)field_0x0 + 0xbdad0000 != Root::magicNumber()) { - field_0x0 = NULL; + // Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber(). + // Idk why the operations here are all weird but I can't use objdiff right now. + if (*(BE(u32)*)mData + 0xbdad0000 != Root::magicNumber()) { + mData = NULL; } else { - field_0x4 = (Root*)((u8*)field_0x0 + *((BE(u32)*)field_0x0 + 3)); + mRoot = (Root*)((u8*)mData + *((BE(u32)*)mData + 3)); } } @@ -48,14 +50,14 @@ struct JAUSoundTable_ { if (index < 0) { return NULL; } - if ((u32)index >= field_0x4->mSectionNumber) { + if ((u32)index >= mRoot->mSectionNumber) { return NULL; } - u32 offset = field_0x4->mSectionOffsets[index]; + u32 offset = mRoot->mSectionOffsets[index]; if (offset == 0) { return NULL; } - return (Section*)((u8*)field_0x0 + offset); + return (Section*)((u8*)mData + offset); } Group* getGroup(Section* param_1, int index) const { @@ -71,11 +73,11 @@ struct JAUSoundTable_ { if (offset == 0) { return NULL; } - return (Group*)((u8*)field_0x0 + offset); + return (Group*)((u8*)mData + offset); } - const void* field_0x0; - Root* field_0x4; + const void* mData; + Root* mRoot; }; /** @@ -83,7 +85,7 @@ struct JAUSoundTable_ { * */ struct JAUSoundTableRoot { - static inline u32 magicNumber() { return 0x5420; } + static inline u32 magicNumber() { return 'T '; } // Second half of "BST " BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; @@ -134,7 +136,7 @@ struct JAUSoundTableGroup { BE(u32) mNumItems; BE(u32) field_0x4; - u8 mTypeIds[0]; + u8 mTypeIds[0]; // TODO: Should probably be BE(u32), but I can't objdiff rn. }; /** @@ -142,7 +144,7 @@ struct JAUSoundTableGroup { * */ struct JAUSoundTable : public JASGlobalInstance { - JAUSoundTable(bool param_0) : JASGlobalInstance(param_0) { + JAUSoundTable(bool setInstance) : JASGlobalInstance(setInstance) { } ~JAUSoundTable() {} @@ -157,11 +159,11 @@ struct JAUSoundTable : public JASGlobalInstance { if (offset == 0) { return NULL; } - return (JAUSoundTableItem*)((u8*)field_0x0.field_0x0 + offset); + return (JAUSoundTableItem*)((u8*)field_0x0.mData + offset); } - const void* getResource() const { return field_0x0.field_0x0; } - bool isValid() const { return field_0x0.field_0x0 != NULL; } + const void* getResource() const { return field_0x0.mData; } + bool isValid() const { return field_0x0.mData != NULL; } JAUSoundTable_ field_0x0; }; @@ -171,7 +173,7 @@ struct JAUSoundTable : public JASGlobalInstance { * */ struct JAUSoundNameTableRoot { - static inline u32 magicNumber() { return 0x544e; } + static inline u32 magicNumber() { return 'TN'; } // Second half of "BSTN" BE(u32) mSectionNumber; BE(u32) mSectionOffsets[0]; }; diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 6a15869a7c..2ff00b6a14 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -713,7 +713,7 @@ void JASAramStream::channelStart() { wave_info.mpPenult = 0; // probably a fake match, this should be set in the JASWaveInfo constructor static u32 const one = 1; - wave_info.field_0x20 = &one; + wave_info.mpLoaded = &one; JASChannel* jc = JKR_NEW JASChannel(channelCallback, this); JUT_ASSERT(963, jc); jc->setPriority(0x7f7f); diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index ac11742e6b..80bda99ad9 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -11,7 +11,7 @@ JASBasicWaveBank::JASBasicWaveBank() { mWaveGroupArray = NULL; mHandleCount = 0; mGroupCount = 0; - OSInitMutex(&field_0x4); + OSInitMutex(&mWaveTableMutex); } JASBasicWaveBank::~JASBasicWaveBank() { @@ -44,13 +44,13 @@ void JASBasicWaveBank::setWaveTableSize(u32 param_0, JKRHeap* param_1) { } void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (!handle->mHeap) { handle->mHeap = ¶m_0->mHeap; - handle->field_0x4.field_0x20 = ¶m_0->_48; - handle->field_0x4.mOffsetStart = param_0->mCtrlWaveArray[i].field_0x4; + handle->mWaveInfo.mpLoaded = ¶m_0->mCurrentlyLoaded; + handle->mWaveInfo.mOffsetStart = param_0->mCtrlWaveArray[i].mOffsetStart; } } } @@ -58,13 +58,13 @@ void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) DUSK_GAME_DATA u32 JASBasicWaveBank::mNoLoad; void JASBasicWaveBank::decWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) { - JASMutexLock lock(&field_0x4); + JASMutexLock lock(&mWaveTableMutex); for (u32 i = 0; i < param_0->getWaveCount(); i++) { TWaveHandle* handle = mWaveTable + param_0->getWaveID(i); if (handle->mHeap == ¶m_0->mHeap) { handle->mHeap = NULL; - handle->field_0x4.field_0x20 = &mNoLoad; - handle->field_0x4.mOffsetStart = -1; + handle->mWaveInfo.mpLoaded = &mNoLoad; + handle->mWaveInfo.mOffsetStart = -1; } } } @@ -80,15 +80,15 @@ JASWaveHandle* JASBasicWaveBank::getWaveHandle(u32 param_0) const { } void JASBasicWaveBank::setWaveInfo(JASBasicWaveBank::TWaveGroup* wgrp, int index, - u16 param_2, JASWaveInfo const& param_3) { + u16 waveId, JASWaveInfo const& param_3) { JUT_ASSERT(204, wgrp); JUT_ASSERT(205, index < wgrp->mWaveCount); JUT_ASSERT(206, index >= 0); - mWaveTable[param_2].field_0x4 = param_3; - mWaveTable[param_2].field_0x4.field_0x20 = &mNoLoad; - mWaveTable[param_2].field_0x4.mOffsetStart = -1; - wgrp->mCtrlWaveArray[index].field_0x0 = param_2; - wgrp->mCtrlWaveArray[index].field_0x4 = param_3.mOffsetStart; + mWaveTable[waveId].mWaveInfo = param_3; + mWaveTable[waveId].mWaveInfo.mpLoaded = &mNoLoad; + mWaveTable[waveId].mWaveInfo.mOffsetStart = -1; + wgrp->mCtrlWaveArray[index].waveId = waveId; + wgrp->mCtrlWaveArray[index].mOffsetStart = param_3.mOffsetStart; } JASBasicWaveBank::TWaveGroup::TWaveGroup() { @@ -122,7 +122,7 @@ void JASBasicWaveBank::TWaveGroup::onEraseDone() { u32 JASBasicWaveBank::TWaveGroup::getWaveID(int index) const { JUT_ASSERT(298, index < mWaveCount); JUT_ASSERT(299, index >= 0); - return mCtrlWaveArray[index].field_0x0; + return mCtrlWaveArray[index].waveId; } intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { @@ -131,5 +131,5 @@ intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const { if (base == 0) { return 0; } - return (intptr_t)base + field_0x4.mOffsetStart; + return (intptr_t)base + mWaveInfo.mOffsetStart; } diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index a99c13ce63..d2d31d1e6a 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -237,7 +237,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -315,7 +315,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.field_0x20[0] == 0) { + if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index 52935324d8..fc49df148d 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -28,7 +28,7 @@ JASWaveHandle* JASSimpleWaveBank::getWaveHandle(u32 no) const { void JASSimpleWaveBank::setWaveInfo(u32 no, JASWaveInfo const& waveInfo) { mWaveTable[no].mWaveInfo = waveInfo; - mWaveTable[no].mWaveInfo.field_0x20 = &_48; + mWaveTable[no].mWaveInfo.mpLoaded = &mCurrentlyLoaded; mWaveTable[no].mHeap = &mHeap; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index 519eaa3307..f097e93289 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -60,7 +60,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[j].ptr(header); - u16 local_74 = JSULoHalf(ctrl_wave->_00); + u16 local_74 = JSULoHalf(ctrl_wave->mWaveAndGroupId); wave_bank->setWaveInfo(wave_group, j, local_74, wave_info); } wave_group->setFileName(archive->mFileName); @@ -94,7 +94,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap TWaveArchive* archive = archiveBank->mArchiveOffsets[0].ptr(header); for (int i = 0; i < ctrl->mWaveCount; i++) { TCtrlWave* ctrlWave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrlWave->_00); + u32 tmp = JSULoHalf(ctrlWave->mWaveAndGroupId); if (max < tmp) { max = tmp; } @@ -116,8 +116,8 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap wave_info.mpLast = wave->mpLast; wave_info.mpPenult = wave->mpPenult; TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[i].ptr(header); - u32 tmp = JSULoHalf(ctrl_wave->_00); - wave_bank->setWaveInfo(tmp, wave_info); + u32 waveId = JSULoHalf(ctrl_wave->mWaveAndGroupId); + wave_bank->setWaveInfo(waveId, wave_info); } wave_bank->setFileName(archive->mFileName); diff --git a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp index 02730f46b4..5ba257c423 100644 --- a/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp +++ b/libs/JSystem/src/JAudio2/JASWaveArcLoader.cpp @@ -38,7 +38,7 @@ char* JASWaveArcLoader::getCurrentDir() { } JASWaveArc::JASWaveArc() : mHeap(this) { - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; mEntryNum = -1; mFileLength = 0; @@ -57,7 +57,7 @@ bool JASWaveArc::loadSetup(u32 param_0) { if (mStatus != 1) { return false; } - _48 = 1; + mCurrentlyLoaded = 1; mStatus = 2; return true; } @@ -71,7 +71,7 @@ bool JASWaveArc::eraseSetup() { mStatus = 0; return false; } - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 0; return true; } @@ -91,7 +91,7 @@ void JASWaveArc::loadToAramCallback(void* this_) { bool JASWaveArc::sendLoadCmd() { JASMutexLock mutexLock(&mMutex); - _48 = 0; + mCurrentlyLoaded = 0; mStatus = 1; loadToAramCallbackParams commandInfo; commandInfo.mWavArc = this; From 30c36df76b50ff577f17bfa5a8cb6ff9a613cac4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:40:32 +0200 Subject: [PATCH 05/36] Add LE(T) macro I figured since I was messing with some parsing code might as well be prudent. In case somebody revives big-endian CPUs. --- include/helpers/endian.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/helpers/endian.h b/include/helpers/endian.h index 689f9f0dab..da0c8a4a36 100644 --- a/include/helpers/endian.h +++ b/include/helpers/endian.h @@ -285,6 +285,7 @@ inline void be_swap(Mtx& val) { } } +#define LE(T) T #define BE(T) BE #define BE_HOST(T) (T.host()) #else From ad80933ccffe3877d9eaaea161acf7ca065720cb Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:49:13 +0200 Subject: [PATCH 06/36] Allow audio samples to be played back from anywhere in memory rather than ARAM. This is accomplished through a new mAramBaseAddress field on the DSP channels, which effectively relocates ARAM for that channel. --- .../include/JSystem/JAudio2/JASBasicWaveBank.h | 6 ++++++ libs/JSystem/include/JSystem/JAudio2/JASChannel.h | 12 ++++++++++++ .../include/JSystem/JAudio2/JASDSPInterface.h | 14 ++++++++++++++ .../include/JSystem/JAudio2/JASSimpleWaveBank.h | 6 ++++++ libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h | 6 ++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 5 ++++- libs/JSystem/src/JAudio2/JASChannel.cpp | 6 ++++++ src/dusk/audio/DuskDsp.cpp | 5 +++-- 8 files changed, 57 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index 5a84750e3b..cf739b18a2 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -13,6 +13,12 @@ struct JASBasicWaveBank : public JASWaveBank { TWaveHandle() { mHeap = NULL; } virtual intptr_t getWavePtr() const; virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; } +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif bool compareHeap(JASHeap* heap) const { return mHeap == heap;} /* 0x04 */ JASWaveInfo mWaveInfo; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index af922638da..59512fbadd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -166,6 +166,18 @@ public: u32 field_0x104; }; +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; +#endif + static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ; static DUSK_GAME_DATA OSMessage sBankDisposeMsg[16]; static DUSK_GAME_DATA OSMessage sBankDisposeList[16]; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index b413052da7..7f830b6469 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -179,6 +179,20 @@ namespace JASDsp { /* 0x130 */ u8 _unused9[0x148 - 0x130]; /* 0x148 */ s16 iir_filter_params[8]; /* 0x158 */ u8 _unused10[0x180 - 0x158]; +#endif + +#if TARGET_PC + /** + * Memory address at which this sound effect should consider ARAM to start. + * This means fields like mWaveAramAddress will be relative to this pointer instead. + * + * By changing this, sound effects can effectively be played back from anywhere in memory, + * rather than just the emulated ARAM space. + * + * If nullptr, the regular emulated ARAM is used. + */ + void const* mAramBaseAddress; +#endif }; void boot(void (*)(void*)); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h index 4fbeee32b9..1dce0968a9 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h @@ -10,6 +10,12 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc { intptr_t getWavePtr() const; TWaveHandle(); const JASWaveInfo* getWaveInfo() const; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; } +#endif /* 0x04 */ JASWaveInfo mWaveInfo; /* 0x28 */ JASHeap* mHeap; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 9d1c16d775..20b4be89fd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -45,6 +45,12 @@ public: virtual ~JASWaveHandle() {} virtual const JASWaveInfo* getWaveInfo() const = 0; virtual intptr_t getWavePtr() const = 0; +#if TARGET_PC + /** + * @see JASChannel::mAramBaseAddress + */ + [[nodiscard]] virtual void const* getAramBaseAddress() const = 0; +#endif }; /** diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 29ebe11cb5..69dd05795f 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -33,7 +33,7 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } intptr_t wavePtr = waveHandle->getWavePtr(); - if (!wavePtr) { + if (!wavePtr IF_DUSK(&& !waveHandle->getAramBaseAddress())) { return NULL; } @@ -44,6 +44,9 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 channel->setPriority(param_4); channel->field_0xdc.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; +#if TARGET_PC + channel->mAramBaseAddress = waveHandle->getAramBaseAddress(); +#endif channel->field_0xdc.mChannelType = stack_60.field_0x1c; channel->setBankDisposeID(param_0); channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index d2d31d1e6a..d227ad3fc1 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -26,6 +26,9 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mCallback(i_callback), mCallbackData(i_callbackData), mUpdateTimer(0), +#if TARGET_PC + mAramBaseAddress(nullptr), +#endif mBankDisposeID(NULL), mKey(0), mVelocity(0x7f), @@ -256,6 +259,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { switch (field_0xdc.mChannelType) { case 0: i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); +#if TARGET_PC + i_channel->mAramBaseAddress = mAramBaseAddress; +#endif break; case 2: i_channel->setOscInfo(mOscillatorSomething); diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 4009b37e39..3240c0f810 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -317,7 +317,7 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } OutputSubframe channelSubframe = {}; - if (channel.mWaveAramAddress == 0) { + if (channel.mWaveAramAddress == 0 && !channel.mAramBaseAddress) { RenderOscChannel(channel, channelAux, channelSubframe); } else { ValidateChannel(channel); @@ -455,7 +455,8 @@ static int ReadChannelSamplesChunk( assert(desiredSamples >= 0); - auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress; + auto aramBase = static_cast(channel.mAramBaseAddress ? channel.mAramBaseAddress : ARGetStorageAddress()); + aramBase += channel.mWaveAramAddress; auto curSamplePosition = channel.mSamplePosition; u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock; From 0c0bc7c9e2b76eb7790182170e4bd32662347cc2 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 15:49:49 +0200 Subject: [PATCH 07/36] Compile out unused fields in DSP TChannel definition --- .../include/JSystem/JAudio2/JASDSPInterface.h | 17 +++++++++++++++++ libs/JSystem/src/JAudio2/JASDSPInterface.cpp | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h index 7f830b6469..c45b72f2d8 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDSPInterface.h @@ -109,7 +109,9 @@ namespace JASDsp { * Pitch shift via changing playback speed. */ /* 0x004 */ u16 mPitch; +#if !TARGET_PC /* 0x006 */ short _unused1; +#endif /** * Set to 1 when playback starts, cleared by DSP later, @@ -118,19 +120,27 @@ namespace JASDsp { * (Corroborated by fields JASAramStream checks never being cleared explicitly by CPU.) */ /* 0x008 */ u16 mResetFlag; +#if !TARGET_PC /* 0x00A */ u8 _unused2[0x00C - 0x00A]; +#endif /* 0x00C */ s16 mPauseFlag; +#if !TARGET_PC /* 0x00E */ short _unused3; +#endif /* 0x010 */ OutputChannelConfig mOutputChannels[DSP_OUTPUT_CHANNELS]; +#if !TARGET_PC /* 0x040 */ u8 _unused4[0x050 - 0x040]; +#endif /* 0x050 */ u16 mAutoMixerPanDolby; // pan is upper 8 bits, dolby lower 8. /* 0x052 */ u16 mAutoMixerFxMix; /* 0x054 */ u16 mAutoMixerInitVolume; /* 0x056 */ u16 mAutoMixerVolume; /* 0x058 */ u16 mAutoMixerBeenSet; +#if !TARGET_PC /* 0x05A */ u8 _unused5[0x060 - 0x05A]; /* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP. /* 0x062 */ u8 _unused6[0x064 - 0x062]; +#endif /** * Samples per ADPCM frame for ADPCM audio. Seems just set to 1 for PCM formats. @@ -139,7 +149,9 @@ namespace JASDsp { /* 0x064 */ u16 mSamplesPerBlock; /* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP. /* 0x068 */ u32 mSamplePosition; // Only ever initialized by code, name is guess. +#if !TARGET_PC /* 0x06C */ u8 _unused7[0x070 - 0x06C]; +#endif /** * Current audio read position in ARAM. Updated by DSP. @@ -151,11 +163,13 @@ namespace JASDsp { * Gets written by DSP, but also CPU. */ /* 0x074 */ u32 mSamplesLeft; // Never directly cleared to zero. Seems sus. Cleared by DSP? +#if !TARGET_PC /* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP. /* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP. /* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP. /* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP. /* 0x0D0 */ u8 _unused8[0x100 - 0x0D0]; +#endif /* 0x100 */ u16 mBytesPerBlock; /* 0x102 */ u16 mLoopFlag; @@ -176,8 +190,11 @@ namespace JASDsp { /* 0x118 */ u32 mWaveAramAddress; /* 0x11C */ int mSampleCount; /* 0x120 */ s16 fir_filter_params[8]; +#if !TARGET_PC /* 0x130 */ u8 _unused9[0x148 - 0x130]; +#endif /* 0x148 */ s16 iir_filter_params[8]; +#if !TARGET_PC /* 0x158 */ u8 _unused10[0x180 - 0x158]; #endif diff --git a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp index 554660f6dc..04bec5dcc7 100644 --- a/libs/JSystem/src/JAudio2/JASDSPInterface.cpp +++ b/libs/JSystem/src/JAudio2/JASDSPInterface.cpp @@ -523,9 +523,12 @@ void JASDsp::TChannel::init() { void JASDsp::TChannel::playStart() { JUT_ASSERT(508, dspMutex); field_0x10c = 0; +#if !TARGET_PC field_0x060 = 0; +#endif mResetFlag = 1; field_0x066 = 0; +#if !TARGET_PC int i; for (i = 0; i < 4; i++) { field_0x078[i] = 0; @@ -534,6 +537,7 @@ void JASDsp::TChannel::playStart() { for (i = 0; i < 20; i++) { field_0x080[i] = 0; } +#endif mIsActive = 1; } @@ -601,9 +605,11 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress, break; } } +#if !TARGET_PC for (i = 0; i < 16; i++) { field_0x0b0[i] = 0; } +#endif } } From 98b203fd269aab2479a6c89982011349bf29cec1 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 16:02:20 +0200 Subject: [PATCH 08/36] Mod api audio_res service for replacing audio samples (waves) More things planned for the future, this is a basic prototype and far from finished. Still needs lots of work --- CMakeLists.txt | 25 +++ files.cmake | 4 + libs/JSystem/src/JAudio2/JASBank.cpp | 11 ++ sdk/include/mods/svc/audio_res.h | 65 +++++++ src/dusk/mods/svc/audio_res/audio_res.cpp | 227 ++++++++++++++++++++++ src/dusk/mods/svc/audio_res/audio_res.hpp | 93 +++++++++ src/dusk/mods/svc/audio_res/opus.cpp | 93 +++++++++ src/dusk/mods/svc/audio_res/wave.cpp | 145 ++++++++++++++ src/dusk/mods/svc/registry.cpp | 1 + src/dusk/mods/svc/registry.hpp | 1 + 10 files changed, 665 insertions(+) create mode 100644 sdk/include/mods/svc/audio_res.h create mode 100644 src/dusk/mods/svc/audio_res/audio_res.cpp create mode 100644 src/dusk/mods/svc/audio_res/audio_res.hpp create mode 100644 src/dusk/mods/svc/audio_res/opus.cpp create mode 100644 src/dusk/mods/svc/audio_res/wave.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a8d01151..9dc478ce2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF) option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF) +option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" ON) # Edit & Continue if (MSVC) @@ -219,6 +220,25 @@ FetchContent_Declare(miniz ) set(_fetch_content_deps cxxopts json miniz) + +if (DUSK_ENABLE_OPUS) + message(STATUS "dusklight: Fetching opusfile") + + # Opusfile options + SET(OP_DISABLE_HTTP ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_DOCS ON CACHE BOOL "" FORCE) + SET(OP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE) + + message(STATUS "dusklight: Fetching opusfile") + FetchContent_Declare( + opusfile + GIT_REPOSITORY https://github.com/xiph/opusfile.git + GIT_TAG 6dfd29e7adb87f2e193575fc3fa88cbf1a0b27df + ) + + list(APPEND _fetch_content_deps opusfile) +endif () + if (DUSK_ENABLE_CODE_MODS) message(STATUS "dusklight: Fetching funchook") # cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a @@ -311,6 +331,10 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p if (DUSK_ENABLE_CODE_MODS) list(APPEND GAME_LIBS funchook-static) endif () +if (DUSK_ENABLE_OPUS) + list(APPEND GAME_LIBS OpusFile::opusfile) + list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1) +endif () if (DUSK_ENABLE_SENTRY_NATIVE) list(APPEND GAME_LIBS sentry) @@ -568,6 +592,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR add_subdirectory(mods/template_mod) add_subdirectory(mods/ao_mod) add_subdirectory(mods/shadow_mod) + add_subdirectory(mods/audio_mod) endif () if (APPLE) diff --git a/files.cmake b/files.cmake index 51b2bdf95e..74e6ef0e69 100644 --- a/files.cmake +++ b/files.cmake @@ -1483,6 +1483,10 @@ set(DUSK_FILES src/dusk/mods/log_buffer.hpp src/dusk/mods/manifest.cpp 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/wave.cpp + src/dusk/mods/svc/audio_res/opus.cpp src/dusk/mods/svc/camera.cpp src/dusk/mods/svc/config.cpp src/dusk/mods/svc/config.hpp diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 69dd05795f..e96b4df174 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -1,11 +1,15 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASBank.h" + +#include "dusk/mods/svc/audio_res/audio_res.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; + // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), @@ -28,6 +32,13 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 if (!waveHandle) { return NULL; } +#if TARGET_PC + auto const found_replacement = s_replacements.find(AudioWaveKey(SoundEffects, stack_60.field_0x1a)); + if (found_replacement != s_replacements.end()) { + waveHandle = found_replacement->second.get(); + } +#endif + const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); if (!waveInfo) { return NULL; diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h new file mode 100644 index 0000000000..f5ec3693dc --- /dev/null +++ b/sdk/include/mods/svc/audio_res.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#define AUDIO_RES_SERVICE_ID "dev.twilitrealm.dusklight.audio_res" +#define AUDIO_RES_SERVICE_MAJOR 1u +#define AUDIO_RES_SERVICE_MINOR 0u + +#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 struct AudioRawWave { + AudioWaveFormat format; + float sample_rate; + int16_t sample_value_last; + int16_t sample_value_penult; +} AudioRawWave; + +typedef struct AudioWaveInfo { + uint8_t base_key; + bool loop; + uint32_t loop_start_sample; + uint32_t loop_end_sample; + + AudioRawWave const* raw_wave; +} AudioWaveInfo; + +typedef struct AudioResService { + ServiceHeader header; + + ModResult (*replace_wave)( + ModContext* ctx, + AudioWaveBank bank, + uint16_t wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); +} AudioResService; + +#ifdef __cplusplus +#include "mods/service.hpp" + +template <> +struct mods::ServiceTraits { + static constexpr const char* id = AUDIO_RES_SERVICE_ID; + static constexpr uint16_t major_version = AUDIO_RES_SERVICE_MAJOR; + static constexpr uint16_t minor_version = AUDIO_RES_SERVICE_MINOR; +}; +#endif diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp new file mode 100644 index 0000000000..20b3e8a95f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -0,0 +1,227 @@ +#include "audio_res.hpp" + +#include "helpers/cast.hpp" +#include "mods/svc/audio_res.h" + +#include "../registry.hpp" +#include "../slot_map.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" + +namespace dusk::mods::svc { + +namespace audio_res { +namespace { + +using namespace dusk::helpers::cast; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap 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(std::move(pcm16)); + } else { + slot.data = std::make_shared(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(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; + } + + // TODO: Handle conflicts + + audio_res::AudioWaveReplacementValue value; + value.data = slot.data; + value.wave_info = wave_info_from_slot(slot); + audio_res::s_replacements.emplace( + audio_res::AudioWaveKey{.bank = bank, .wave_id = wave_id}, + std::make_unique(std::move(value))); + + const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +constexpr AudioResService s_audioResService{ + .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), + .replace_wave = &audio_res::insert_replace_wave}; + +} + +absl::flat_hash_map> s_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; +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + +} // namespace audio_res + +constinit const ServiceModule g_audioResModule{ + .id = AUDIO_RES_SERVICE_ID, + .majorVersion = AUDIO_RES_SERVICE_MAJOR, + .minorVersion = AUDIO_RES_SERVICE_MINOR, + .service = &audio_res::s_audioResService, + // .modDetached = overlay_remove_mod, + // .lifecycleApplied = overlay_sync_files, + // .frameEnd = + // [] { + // if (consume_overlays_dirty()) { + // overlay_sync_files(); + // } + // }, +}; + +} diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp new file mode 100644 index 0000000000..b336dbeda8 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#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 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 +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + template + 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() override; + [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; + [[nodiscard]] intptr_t getWavePtr() const override; + [[nodiscard]] void const* getAramBaseAddress() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map> s_replacements; + +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 data; +}; + +using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +} diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp new file mode 100644 index 0000000000..3541f6be81 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -0,0 +1,93 @@ +#if DUSK_OPUS + +#include "audio_res.hpp" +#include "aurora/lib/logging.hpp" +#include "dusk/mod_loader.hpp" +#include "helpers/cast.hpp" +#include "opusfile.h" + +namespace { + +aurora::Module Log("dusk::mods::svc::audio_res::opus"); + +struct OpusHandle { + OggOpusFile* file; + + ~OpusHandle() { + op_free(file); + } + + operator OggOpusFile*() const { + return file; + } +}; + +} + +namespace dusk::mods::svc::audio_res { +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + OpusHead head; + const int result = op_test(&head, fileData.data(), fileData.size()); + if (result != 0) { + return MOD_UNSUPPORTED; + } + + if (head.channel_count != 1) { + Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", mod.metadata.id, slot.bundle_path, head.channel_count); + return MOD_INVALID_ARGUMENT; + } + + if (head.stream_count != 1) { + Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + int error; + auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error); + if (error != 0) { + Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, slot.bundle_path, error); + return MOD_ERROR; + } + + const OpusHandle handle(handle_raw); + auto const length = op_pcm_total(handle, -1); + if (length < 0) { + Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, slot.bundle_path); + return MOD_ERROR; + } + + SampleDataPcm16 pcm_buffer; + pcm_buffer.data.resize(length); + + std::span span_pcm(pcm_buffer.data); + + while (!span_pcm.empty()) { + auto read_result = op_read( + handle, + span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), + nullptr); + + if (read_result <= 0) { + Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, slot.bundle_path, read_result); + return MOD_ERROR; + } + + if (read_result > span_pcm.size()) { + Log.fatal("What teh fuck?"); + } + + span_pcm = span_pcm.subspan(read_result); + } + + pcm_buffer.be_swap(); + + 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; + + return MOD_OK; +} +} + +#endif diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp new file mode 100644 index 0000000000..69e1e2e10f --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -0,0 +1,145 @@ +#include "audio_res.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"); + +struct ChunkHeader { + char magic[4]; + LE(u32) size; +}; + +struct RiffChunk { + ChunkHeader header; // magic "RIFF" + char id[4]; // "WAVE" + // Data after chunk. +}; + +constexpr u16 WAVE_FORMAT_PCM = 0x0001; + +struct FmtPcmChunkData { + ChunkHeader header; + + u16 wFormatTag; + u16 nChannels; + u32 nSamplesPerSec; + u32 nAvgBytesPerSec; + u16 nBlockAlign; + u16 wBitsPerSample; +}; + +bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { + return memcmp(field, expected, 4) == 0; +} + +} + +namespace dusk::mods::svc::audio_res { + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { + using namespace helpers::alignment; + + if (fileData.size() < sizeof(RiffChunk)) { + return MOD_UNSUPPORTED; + } + + auto const riffChunk = read_unaligned(&fileData[0]); + if (!check_four_cc(riffChunk.header.magic, "RIFF")) { + return MOD_UNSUPPORTED; + } + + if (!check_four_cc(riffChunk.id, "WAVE")) { + return MOD_UNSUPPORTED; + } + + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size()) { + Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + bool has_read_fmt = false; + SampleDataPcm16 pcmData; + + auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size); + while (waveData.size() > sizeof(ChunkHeader)) { + auto const chunkHeader = read_unaligned(&waveData[0]); + + if (check_four_cc(chunkHeader.magic, "fmt ")) { + if (has_read_fmt) { + Log.error("[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (waveData.size() < sizeof(FmtPcmChunkData)) { + Log.error("[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const fmtChunk = read_unaligned(&waveData[0]); + + if (fmtChunk.nChannels != 1) { + Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (fmtChunk.wBitsPerSample != 16) { + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + has_read_fmt = true; + slot.sample_rate = static_cast(fmtChunk.nSamplesPerSec); + + } else if (check_four_cc(chunkHeader.magic, "data")) { + if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) { + Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + auto const sampleData = waveData.subspan(sizeof(chunkHeader), chunkHeader.size); + for (size_t i = 0; i + 1 < sampleData.size(); i += 2) { + pcmData.data.push_back(read_unaligned(&sampleData[i])); + } + } + + auto skip = sizeof(chunkHeader) + chunkHeader.size; + if (skip % 2 != 0) { + skip += 1; + } + + if (waveData.size() < skip) { + Log.error("[{}] wav file {}: unexpected EOF", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + waveData = waveData.subspan(skip); + } + + if (!has_read_fmt) { + Log.error("[{}] wav file {}: missing fmt chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + if (pcmData.data.empty()) { + Log.error("[{}] wav file {}: missing data chunk", mod.metadata.id, slot.bundle_path); + return MOD_INVALID_ARGUMENT; + } + + pcmData.be_swap(); + + slot.format = Pcm16; + slot.sample_count = pcmData.size() / sizeof(u16); + slot.data = std::make_unique(std::move(pcmData)); + + return MOD_OK; +} + +} diff --git a/src/dusk/mods/svc/registry.cpp b/src/dusk/mods/svc/registry.cpp index 2033d47475..a9d82e0993 100644 --- a/src/dusk/mods/svc/registry.cpp +++ b/src/dusk/mods/svc/registry.cpp @@ -210,6 +210,7 @@ void ModLoader::init_services() { &svc::g_gameModule, &svc::g_cameraModule, &svc::g_gfxModule, + &svc::g_audioResModule, }) { svc::register_module(*module); diff --git a/src/dusk/mods/svc/registry.hpp b/src/dusk/mods/svc/registry.hpp index 191626e0ef..a2f597def4 100644 --- a/src/dusk/mods/svc/registry.hpp +++ b/src/dusk/mods/svc/registry.hpp @@ -72,5 +72,6 @@ extern const ServiceModule g_uiModule; extern const ServiceModule g_gameModule; extern const ServiceModule g_cameraModule; extern const ServiceModule g_gfxModule; +extern const ServiceModule g_audioResModule; } // namespace dusk::mods::svc From de44bbaa268e91dbcd2ec2463abcb5b08c2991f9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 25 Jul 2026 16:02:42 +0200 Subject: [PATCH 09/36] Example mod for audio_res service --- mods/audio_mod/CMakeLists.txt | 19 ++++++++++++++ mods/audio_mod/mod.json | 7 ++++++ mods/audio_mod/res/attribution.txt | 1 + mods/audio_mod/res/go.opus | Bin 0 -> 6249 bytes mods/audio_mod/res/go.wav | Bin 0 -> 62814 bytes mods/audio_mod/src/mod.cpp | 39 +++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+) create mode 100644 mods/audio_mod/CMakeLists.txt create mode 100644 mods/audio_mod/mod.json create mode 100644 mods/audio_mod/res/attribution.txt create mode 100644 mods/audio_mod/res/go.opus create mode 100644 mods/audio_mod/res/go.wav create mode 100644 mods/audio_mod/src/mod.cpp diff --git a/mods/audio_mod/CMakeLists.txt b/mods/audio_mod/CMakeLists.txt new file mode 100644 index 0000000000..083a348ed2 --- /dev/null +++ b/mods/audio_mod/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.25) +project(audio_mod CXX) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root") + option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if (DUSK_MOD_USE_FULL_TREE) + add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL) + else () + add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL) + endif () +endif () + +add_mod(audio_mod + SOURCES src/mod.cpp + MOD_JSON mod.json + RES_DIR res +) diff --git a/mods/audio_mod/mod.json b/mods/audio_mod/mod.json new file mode 100644 index 0000000000..7151999aa6 --- /dev/null +++ b/mods/audio_mod/mod.json @@ -0,0 +1,7 @@ +{ + "id": "dev.twilitrealm.audio_mod", + "name": "[DEMO] Audio Mod", + "version": "1.0.0", + "author": "You, the listener", + "description": "An example Dusklight mod" +} diff --git a/mods/audio_mod/res/attribution.txt b/mods/audio_mod/res/attribution.txt new file mode 100644 index 0000000000..5c9d15d0fd --- /dev/null +++ b/mods/audio_mod/res/attribution.txt @@ -0,0 +1 @@ +go.opus and go.wav are licensed CC0, from https://kenney.nl/assets/voiceover-pack \ No newline at end of file diff --git a/mods/audio_mod/res/go.opus b/mods/audio_mod/res/go.opus new file mode 100644 index 0000000000000000000000000000000000000000..d5373b60b19c60a8c5099fa94f569993f2d352cb GIT binary patch literal 6249 zcmeZIPY-5bVt@j}X1mKEX2rhA>llUo3rdSUQWH}c87&wac7wzjz{_<%Br(01mw|!7C$TKe%t+7BNYBu~h=qZHL6(7mAvG^KKP9!u)+ezn+001K z$O0s(kdv8|Ur-9x6%yp=?Clro>*N|_Ys|sGz!2gY;^S(Yo-fM4z~JH-;%aMTU}&mq zV4-VZ7-DE_Wncz&56k&#kZTwiK<)wgMNu`dOZh+Jf7R7n_8q(Z>hsGh2lj8bv)ro%wWlhl{w* z*AUCAGbM|j__fykP7~~WyTi<;C34+I%aqkxqQ4kk-e~RSGWJW>{WE3Pt=c5k)223x z{MO!n0Z&$X=q*-kZkEsE`Qy^Roii`wy`BY|(d)a}!7W=kHf=9I_({YwCE=L&)mzCv zbrq71@h`I{#q2PBB(URPg}BAFz}rQ3hkn!rus9zVXgYP2;||vw%cb*8j11MQ{p?F4 zy6@kd{MuoGRP*KZzxq>>FR?PP*PmFk^zoAw4>gK?epk#tkhJQh_(J{^fyutXt0fk! z-JO@VYgMGf8v)xZvz@yecS@--znh^vYo&rwn8KHnwT5>$w(;2TW#3zW?a<9RNuhTa zvs{gP0#_vw#r zf0>(TR(m(m<`37|jq9qH@V{zsKVom@sT9$-aDMga?V{gj<}k)@UAHkHRH4`TWp@@tsI>HohXr+I?;2DYxzS?HrRPOR=+sNFNIS z!zmn@R8cgeBXsrGI!)dh*NGQ+pE(}WcKZE9tK8vs+ttTEl9P7+-1RU})9u%3nU-6x z(i6^Y7Rr6qH9uhGz5u`{^&Xyq-%NJ?Efd04*tLAea&aN{qR*an3!JV5!=Ji z`1`Mp%(uv?#fROlbV;6Ot$FrUXUX)bTh6sIMSQ$k@L%ts&4W$MvY%R7Bl#w&x}H zE332wOZ$|>!)P5{Gjb$lmM7XdEl{v`{E@77G*+q0#Bc3FjRy>`Zf@_H`@&sI;1OqztekN9 z;{w_G_V0fil3f;U6g>KMvDIwt=B4lWSNf~n`V!Z0ujvWn+n6Js)9y)#^8_oT{0ez# zJniEx=Ex5QmFqqn;SZ2E@bQ>vx@5@-*7b!-6MkFmu#(-SplV*&_3y-_!t|**C*J** zJdu3CHhZt=|ED665A%`?=4Q%T?7P+F&RH90Sv$KT{^O5BFP0qmXZ_J^0t5fmlgBre zK6q4XTebJ_dQrJsYkwTkv-qSV^iG7?*7jrJy`9y&G*0Zfd;Lzd>V@T+!G-ndaW)-V zuf9rYi+{-8-h9XCz24r|nZBm|Jx1jZ*KK7!(YmX$#N_RPmPyKeuFsUuF57X-*p}

GjC_f9Nv2*|+eZilO_q8MAl(=6oReKDoK< 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, "template_mod initialized"); + AudioWaveHandle handle; + svc_audio_res->replace_wave( + mod_ctx, + SoundEffects, + 4238, + "res/go.opus", + nullptr, + &handle); + svc_audio_res->replace_wave( + mod_ctx, + SoundEffects, + 4237, + "res/go.opus", + nullptr, + &handle); + return MOD_OK; +} + +MOD_EXPORT ModResult mod_update(ModError*) { + return MOD_OK; +} + +MOD_EXPORT ModResult mod_shutdown(ModError*) { + svc_log->info(mod_ctx, "template_mod unloaded"); + return MOD_OK; +} +} From f6e05eb04fc178d423b0dc57d18bbab852222d8c Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 26 Jul 2026 14:25:28 +0200 Subject: [PATCH 10/36] Unhardcode sound effects wave bank in replacements --- libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h | 2 +- libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h | 2 +- libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h | 6 ++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 3 ++- libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp | 2 +- libs/JSystem/src/JAudio2/JASWSParser.cpp | 4 ++-- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h index cf739b18a2..ed4ddbf4cd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicWaveBank.h @@ -50,7 +50,7 @@ struct JASBasicWaveBank : public JASWaveBank { u32 getWaveCount() const { return mWaveCount; } }; - JASBasicWaveBank(); + JASBasicWaveBank(IF_DUSK(u32 bankId)); ~JASBasicWaveBank(); TWaveGroup* getWaveGroup(u32); void setGroupCount(u32, JKRHeap*); diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h index 1dce0968a9..8ccbc659bd 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASSimpleWaveBank.h @@ -21,7 +21,7 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc { /* 0x28 */ JASHeap* mHeap; }; - JASSimpleWaveBank(); + JASSimpleWaveBank(IF_DUSK(u32 bankId)); ~JASSimpleWaveBank(); void setWaveTableSize(u32, JKRHeap*); JASWaveHandle* getWaveHandle(u32) const; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 20b4be89fd..f559e86c4c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -59,6 +59,12 @@ public: */ class JASWaveBank { public: +#if TARGET_PC + const u32 bankId; + + JASWaveBank(u32 bankId) : bankId(bankId) { } +#endif + virtual ~JASWaveBank() {} virtual JASWaveHandle* getWaveHandle(u32) const = 0; virtual JASWaveArc* getWaveArc(u32) = 0; diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index e96b4df174..9491204cc7 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -33,7 +33,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } #if TARGET_PC - auto const found_replacement = s_replacements.find(AudioWaveKey(SoundEffects, stack_60.field_0x1a)); + auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + auto const found_replacement = s_replacements.find(key); if (found_replacement != s_replacements.end()) { waveHandle = found_replacement->second.get(); } diff --git a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp index 80bda99ad9..9f9e16325c 100644 --- a/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicWaveBank.cpp @@ -6,7 +6,7 @@ #include "JSystem/JUtility/JUTAssert.h" #include -JASBasicWaveBank::JASBasicWaveBank() { +JASBasicWaveBank::JASBasicWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveGroupArray = NULL; mHandleCount = 0; diff --git a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp index fc49df148d..f9e4382512 100644 --- a/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp +++ b/libs/JSystem/src/JAudio2/JASSimpleWaveBank.cpp @@ -3,7 +3,7 @@ #include "JSystem/JAudio2/JASSimpleWaveBank.h" #include -JASSimpleWaveBank::JASSimpleWaveBank() { +JASSimpleWaveBank::JASSimpleWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) { mWaveTable = NULL; mWaveTableSize = 0; } diff --git a/libs/JSystem/src/JAudio2/JASWSParser.cpp b/libs/JSystem/src/JAudio2/JASWSParser.cpp index f097e93289..91552b24c5 100644 --- a/libs/JSystem/src/JAudio2/JASWSParser.cpp +++ b/libs/JSystem/src/JAudio2/JASWSParser.cpp @@ -30,7 +30,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap* u32 free_size = heap->getFreeSize(); THeader* header = (THeader*)stream; - JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank; + JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } @@ -82,7 +82,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap return NULL; } - JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank; + JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank IF_DUSK((header->mId)); if (wave_bank == NULL) { return NULL; } From 4e46c04c919b4960e58fd2a8c6f4923106ffbf77 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 26 Jul 2026 15:00:53 +0200 Subject: [PATCH 11/36] Add field to JASChannel to keep sample data backing wave replacements alive How to ensure stuff doesn't get freed when mod reloading gets involved? More shared_ptr --- .../include/JSystem/JAudio2/JASChannel.h | 20 +++++++++++++++++++ .../include/JSystem/JAudio2/JASWaveInfo.h | 10 ++++++++++ libs/JSystem/src/JAudio2/JASBank.cpp | 7 +++++-- src/dusk/mods/svc/audio_res/audio_res.cpp | 4 ++++ src/dusk/mods/svc/audio_res/audio_res.hpp | 8 ++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 59512fbadd..2310cc590f 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -8,6 +8,7 @@ #include "JSystem/JAudio2/JASWaveInfo.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include +#include struct JASDSPChannel; @@ -15,6 +16,20 @@ namespace JASDsp { struct TChannel; } +#if TARGET_PC +/** + * An object to manage the lifetime of the sample data pointed to by JASChannel. + * + * JASChannel can (optionally) accept an unique_ptr to an object to this type, + * in which case it will be destroyed when the JASChannel is done. + * + * @see JASChannel::mSampleReference + */ +struct JASSampleDataReference { + virtual ~JASSampleDataReference() = default; +}; +#endif + /** * @ingroup jsystem-jaudio * @@ -176,6 +191,11 @@ public: * If nullptr, the regular emulated ARAM is used. */ void const* mAramBaseAddress; + + /** + * @see JASSampleDataReference + */ + std::unique_ptr mSampleReference; #endif static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index f559e86c4c..45874f5aa7 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -2,6 +2,11 @@ #define JASWAVEINFO_H #include +#include + +#if TARGET_PC +struct JASSampleDataReference; +#endif struct JASWaveArc; @@ -50,6 +55,11 @@ public: * @see JASChannel::mAramBaseAddress */ [[nodiscard]] virtual void const* getAramBaseAddress() const = 0; + + /** + * Create a @ref JASSampleDataReference to keep the sample data for this wave alive. + */ + [[nodiscard]] virtual std::unique_ptr getSampleReference() const { return nullptr; } #endif }; diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 9491204cc7..1dc66b377e 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -38,6 +38,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 if (found_replacement != s_replacements.end()) { waveHandle = found_replacement->second.get(); } + + auto const aramBase = waveHandle->getAramBaseAddress(); #endif const JASWaveInfo* waveInfo = waveHandle->getWaveInfo(); @@ -45,7 +47,7 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } intptr_t wavePtr = waveHandle->getWavePtr(); - if (!wavePtr IF_DUSK(&& !waveHandle->getAramBaseAddress())) { + if (!wavePtr IF_DUSK(&& !aramBase)) { return NULL; } @@ -57,7 +59,8 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 channel->field_0xdc.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; #if TARGET_PC - channel->mAramBaseAddress = waveHandle->getAramBaseAddress(); + channel->mAramBaseAddress = aramBase; + channel->mSampleReference = waveHandle->getSampleReference(); #endif channel->field_0xdc.mChannelType = stack_60.field_0x1c; channel->setBankDisposeID(param_0); diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 20b3e8a95f..d95aba652a 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -201,6 +201,10 @@ intptr_t AudioWaveReplacementValue::getWavePtr() const { return 0; } +std::unique_ptr AudioWaveReplacementValue::getSampleReference() const { + return std::make_unique(data); +} + void SampleDataPcm16::be_swap() { for (auto& sample : data) { ::be_swap(sample); diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index b336dbeda8..bed4567af8 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASWaveInfo.h" #include "absl/container/flat_hash_map.h" #include "mods/svc/audio_res.h" @@ -40,6 +41,12 @@ struct SampleDataU8 : SampleDataBuf { } }; +struct SampleReference : JASSampleDataReference { + explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} + + std::shared_ptr data; +}; + struct AudioWaveKey { AudioWaveBank bank; u32 wave_id; @@ -59,6 +66,7 @@ struct AudioWaveReplacementValue : JASWaveHandle { [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; [[nodiscard]] intptr_t getWavePtr() const override; [[nodiscard]] void const* getAramBaseAddress() const override; + [[nodiscard]] std::unique_ptr getSampleReference() const override; JASWaveInfo wave_info; std::shared_ptr data; From 92906a3269793004bbc6c44b9ac0b5ddb12ad44e Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 27 Jul 2026 15:41:08 +0200 Subject: [PATCH 12/36] Audio replacement priority & lifecycle handling --- libs/JSystem/src/JAudio2/JASBank.cpp | 3 +- sdk/include/mods/svc/audio_res.h | 2 + src/dusk/mods/svc/audio_res/audio_res.cpp | 85 ++++++++++++++++++----- src/dusk/mods/svc/audio_res/audio_res.hpp | 6 +- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index 1dc66b377e..b85258bb8b 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -34,9 +34,10 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 } #if TARGET_PC auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + std::lock_guard lock(s_replacements_mutex); auto const found_replacement = s_replacements.find(key); if (found_replacement != s_replacements.end()) { - waveHandle = found_replacement->second.get(); + waveHandle = &found_replacement->second; } auto const aramBase = waveHandle->getAramBaseAddress(); diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index f5ec3693dc..0d126c1912 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -51,6 +51,8 @@ typedef struct AudioResService { char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + + ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); } AudioResService; #ifdef __cplusplus diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index d95aba652a..dc7b2337fd 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -6,6 +6,7 @@ #include "../registry.hpp" #include "../slot_map.hpp" #include "aurora/lib/logging.hpp" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/mods/loader/loader.hpp" namespace dusk::mods::svc { @@ -15,6 +16,8 @@ namespace { using namespace dusk::helpers::cast; +bool audio_replacements_dirty = false; + aurora::Module Log("dusk::mods::svc::audio_res"); SlotMap s_waveReplacements; @@ -163,14 +166,7 @@ ModResult insert_replace_wave( slot.loop_end_sample = slot.sample_count; } - // TODO: Handle conflicts - - audio_res::AudioWaveReplacementValue value; - value.data = slot.data; - value.wave_info = wave_info_from_slot(slot); - audio_res::s_replacements.emplace( - audio_res::AudioWaveKey{.bank = bank, .wave_id = wave_id}, - std::make_unique(std::move(value))); + audio_replacements_dirty = true; const auto handle = s_waveReplacements.emplace(*mod, std::move(slot)); if (out_handle) { @@ -180,13 +176,64 @@ ModResult insert_replace_wave( 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 = &audio_res::insert_replace_wave}; + .replace_wave = &insert_replace_wave, + .remove_wave = &remove_wave, +}; + +void sync_audio_replacements() { + audio_replacements_dirty = false; + + absl::flat_hash_map 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 replacements_remove_mod(LoadedMod& mod) { + s_waveReplacements.erase_all(mod); + + audio_replacements_dirty = true; +} } -absl::flat_hash_map> s_replacements; +absl::flat_hash_map s_replacements; +std::mutex s_replacements_mutex; AudioWaveReplacementValue::~AudioWaveReplacementValue() = default; const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const { @@ -218,14 +265,14 @@ constinit const ServiceModule g_audioResModule{ .majorVersion = AUDIO_RES_SERVICE_MAJOR, .minorVersion = AUDIO_RES_SERVICE_MINOR, .service = &audio_res::s_audioResService, - // .modDetached = overlay_remove_mod, - // .lifecycleApplied = overlay_sync_files, - // .frameEnd = - // [] { - // if (consume_overlays_dirty()) { - // overlay_sync_files(); - // } - // }, + .modDetached = audio_res::replacements_remove_mod, + .lifecycleApplied = audio_res::sync_audio_replacements, + .frameEnd = + [] { + if (audio_res::audio_replacements_dirty) { + audio_res::sync_audio_replacements(); + } + } + }; }; -} diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index bed4567af8..4ae3e0dd1c 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -51,6 +51,8 @@ struct AudioWaveKey { AudioWaveBank bank; u32 wave_id; + AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; + template friend H AbslHashValue(H h, const AudioWaveKey& k) { return H::combine(std::move(h), k.bank, k.wave_id); @@ -62,6 +64,7 @@ struct AudioWaveKey { }; struct AudioWaveReplacementValue : JASWaveHandle { + AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) : wave_info(wave_info), data(std::move(data)) {}; ~AudioWaveReplacementValue() override; [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; [[nodiscard]] intptr_t getWavePtr() const override; @@ -72,7 +75,8 @@ struct AudioWaveReplacementValue : JASWaveHandle { std::shared_ptr data; }; -extern absl::flat_hash_map> s_replacements; +extern absl::flat_hash_map s_replacements; +extern std::mutex s_replacements_mutex; struct RuntimeWaveReplacementSlot { std::string bundle_path; From 5cf52b824091801e6f82cc9137d826c8a9ef03ff Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 27 Jul 2026 15:45:42 +0200 Subject: [PATCH 13/36] Acknowledge use of WSYS header ID in recent changes --- docs/imhex/jaudio/wsys.hexpat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/imhex/jaudio/wsys.hexpat b/docs/imhex/jaudio/wsys.hexpat index 88027ade04..aa131ea226 100644 --- a/docs/imhex/jaudio/wsys.hexpat +++ b/docs/imhex/jaudio/wsys.hexpat @@ -207,7 +207,7 @@ struct THeader { /** * ID of wave bank. * This matches the value passed to the BAA load command. - * The game does not use this value itself. + * The game originally does not use this value itself, but Dusklight does rely on it. */ u32 mId; From 9b0af0bf5c9d5d81830364f02cc01f3b270be483 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 02:17:09 +0200 Subject: [PATCH 14/36] BST hexpat --- docs/imhex/jaudio/bst.hexpat | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/imhex/jaudio/bst.hexpat diff --git a/docs/imhex/jaudio/bst.hexpat b/docs/imhex/jaudio/bst.hexpat new file mode 100644 index 0000000000..07bf28684e --- /dev/null +++ b/docs/imhex/jaudio/bst.hexpat @@ -0,0 +1,71 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct ItemSoundEffect { + u8 mPriority; + u8 mVolume; + padding[2]; + u32 mSwBit; + float mPitch; +}; + +struct ItemSequence { + u8 mPriority; + u8 mVolume; + u16 mResourceId; +}; + +struct ItemStream { + u8 mPriority; + u8 mVolume; + u16 mStreamPanParameters; + char* mStreamFilePath[] : u32; +}; + +struct SoundTableGroupEntry { + u8 mTypeId; + if (mTypeId == 0x51) { + ItemSoundEffect* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x60) { + ItemSequence* mOffset : u24 [[inline]]; + } else if (mTypeId == 0x70 || mTypeId == 0x71) { + ItemStream* mOffset : u24 [[inline]]; + } else { + u24 mOffset; + } +}; + +struct TGroup { + u32 mNumItems; + padding[4]; + SoundTableGroupEntry mEntries[mNumItems]; +}; + +struct TSection { + u32 mNumGroups; + Offset mGroupOffsets[mNumGroups]; +}; + +struct Root { + u32 mSectionNumber; + Offset mSectionOffsets[mSectionNumber]; +}; + +struct THeader { + type::Magic<"BST "> mMagic; + + padding[8]; + Root* mRoot : u32; +}; + +THeader header @ 0x0; \ No newline at end of file From 67eea1599a0f74345f0cafeba5219d29315c7544 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 02:20:21 +0200 Subject: [PATCH 15/36] JAudio doc/naming pass --- include/Z2AudioLib/Z2Audience.h | 129 ++++++--- .../include/JSystem/JAudio2/JAISound.h | 10 +- .../include/JSystem/JAudio2/JAISoundParams.h | 12 +- .../include/JSystem/JAudio2/JAUAudibleParam.h | 22 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 166 ++++++++++- libs/JSystem/src/JAudio2/JAISe.cpp | 6 +- libs/JSystem/src/JAudio2/JAISeMgr.cpp | 22 +- libs/JSystem/src/JAudio2/JAISound.cpp | 8 +- src/Z2AudioLib/Z2Audience.cpp | 268 +++++++++--------- src/Z2AudioLib/Z2SceneMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundInfo.cpp | 150 +++++----- src/Z2AudioLib/Z2SoundObject.cpp | 2 +- 12 files changed, 495 insertions(+), 302 deletions(-) diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index e6d2f7da3a..b55b7a7424 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -9,6 +9,8 @@ #include "JSystem/JAudio2/JAUAudibleParam.h" #include "JSystem/TPosition3.h" +#define Z2_AUDIO_PLAYERS 1 + struct Z2Audible; struct Z2AudibleAbsPos { @@ -49,10 +51,14 @@ struct Z2AudioCamera { f32 getCamDist() const { return mCamDist; } - /* 0x00 */ JGeometry::TPosition3f32 field_0x0; + /* 0x00 */ JGeometry::TPosition3f32 mViewMatrix; /* 0x30 */ JGeometry::TVec3 mVel; /* 0x3C */ JGeometry::TVec3 mPos; - /* 0x48 */ JGeometry::TVec3 field_0x48; + + /** + * + */ + /* 0x48 */ JGeometry::TVec3 mLastPos; /* 0x54 */ f32 mFovySin; /* 0x58 */ f32 mVolCenterZ; /* 0x5C */ f32 mTargetVolume; @@ -83,14 +89,14 @@ struct Z2SpotMic { /* 0x04 */ f32 field_0x4; /* 0x08 */ f32 field_0x8; /* 0x0C */ f32 field_0xc; - /* 0x10 */ Z2AudioCamera* field_0x10[1]; + /* 0x10 */ Z2AudioCamera* field_0x10[Z2_AUDIO_PLAYERS]; /* 0x14 */ Vec* mPosPtr; - /* 0x18 */ f32 field_0x18[1]; + /* 0x18 */ f32 field_0x18[Z2_AUDIO_PLAYERS]; /* 0x1C */ f32 field_0x1c; - /* 0x20 */ f32 field_0x20[1]; + /* 0x20 */ f32 field_0x20[Z2_AUDIO_PLAYERS]; /* 0x24 */ bool mIgnoreIfOut; /* 0x25 */ bool mMicOn; - /* 0x26 */ bool field_0x26[1]; + /* 0x26 */ bool field_0x26[Z2_AUDIO_PLAYERS]; }; // Size: 0x28 struct Z2Audience3DSetting { @@ -102,62 +108,99 @@ struct Z2Audience3DSetting { void updateDolbyDist(f32, f32); void calcVolumeFactorAll() { - field_0x0[1] = 1.25f * field_0x0[0]; - field_0x0[2] = 1.5f * field_0x0[0]; - field_0x0[3] = 2.0f * field_0x0[0]; - field_0x0[4] = 3.0f * field_0x0[0]; - field_0x0[5] = 4.0f * field_0x0[0]; - field_0x0[6] = 6.0f * field_0x0[0]; - field_0x0[7] = 8.0f * field_0x0[0]; - field_0x0[8] = 0.9f * field_0x0[0]; - field_0x0[9] = 0.8f * field_0x0[0]; - field_0x0[10] = 0.7f * field_0x0[0]; - field_0x0[11] = 0.6f * field_0x0[0]; - field_0x0[12] = 0.5f * field_0x0[0]; - field_0x0[13] = 0.4f * field_0x0[0]; - field_0x0[14] = 0.3f * field_0x0[0]; + mDistanceMaxes[1] = 1.25f * mDistanceMaxes[0]; + mDistanceMaxes[2] = 1.5f * mDistanceMaxes[0]; + mDistanceMaxes[3] = 2.0f * mDistanceMaxes[0]; + mDistanceMaxes[4] = 3.0f * mDistanceMaxes[0]; + mDistanceMaxes[5] = 4.0f * mDistanceMaxes[0]; + mDistanceMaxes[6] = 6.0f * mDistanceMaxes[0]; + mDistanceMaxes[7] = 8.0f * mDistanceMaxes[0]; + mDistanceMaxes[8] = 0.9f * mDistanceMaxes[0]; + mDistanceMaxes[9] = 0.8f * mDistanceMaxes[0]; + mDistanceMaxes[10] = 0.7f * mDistanceMaxes[0]; + mDistanceMaxes[11] = 0.6f * mDistanceMaxes[0]; + mDistanceMaxes[12] = 0.5f * mDistanceMaxes[0]; + mDistanceMaxes[13] = 0.4f * mDistanceMaxes[0]; + mDistanceMaxes[14] = 0.3f * mDistanceMaxes[0]; for (int i = 0; i < 15; i++) { - field_0x70[i] = (field_0x40 - 1.0f) / (field_0x0[i] - field_0x3c); + mVolumeFactor[i] = (mMinDistanceVolume - 1.0f) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcPriorityFactorAll() { for (int i = 0; i < 15; i++) { - field_0xac[i] = field_0x64 / (field_0x0[i] - field_0x3c); + mPriorityFactor[i] = mMaxDistancePriority / (mDistanceMaxes[i] - mMaxVolumeDistance); } } void calcFxMixFactorAll() { for (int i = 0; i < 15; i++) { - field_0xe8[i] = (field_0x54 - field_0x50) / (field_0x0[i] - field_0x3c); + mFxMixFactor[i] = (mMaxDistanceFxMix - mMinDistanceFxMix) / (mDistanceMaxes[i] - mMaxVolumeDistance); } } - /* 0x000 */ f32 field_0x0[15]; - /* 0x03C */ f32 field_0x3c; - /* 0x040 */ f32 field_0x40; - /* 0x044 */ f32 field_0x44; - /* 0x048 */ f32 field_0x48; - /* 0x04C */ f32 field_0x4c; - /* 0x050 */ f32 field_0x50; - /* 0x054 */ f32 field_0x54; - /* 0x058 */ f32 field_0x58; - /* 0x05C */ f32 field_0x5c; + /** + * Maximum distance a sound can reach before being "far away" + * Being far away affects stuff like culling, lowering its priority, forcibly stopping it, etc. + * Sounds select which entry they use based on their VolBits. + */ + /* 0x000 */ f32 mDistanceMaxes[15]; + + /** + * Distance at which the max volume of a sound is reached. + * i.e. sounds do *not* get louder if they get closer than this. + */ + /* 0x03C */ f32 mMaxVolumeDistance; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x040 */ f32 mMinDistanceVolume; + /* 0x044 */ f32 mDolbyFrontDistanceMax; + /* 0x048 */ f32 mDolbyBehindDistanceMax; + /* 0x04C */ f32 mDolbyCenterValue; + + /** + * FX Mix value at minimum distance (@ref mMaxVolumeDistance) + */ + /* 0x050 */ f32 mMinDistanceFxMix; + + /** + * FX Mix value at maximum distance (@ref mDistanceMaxes) + */ + /* 0x054 */ f32 mMaxDistanceFxMix; + /* 0x058 */ f32 mPanFactor; + /* 0x05C */ f32 mSonicSpeed; // Used for doppler effect calculations. /* 0x060 */ f32 field_0x60; - /* 0x064 */ u32 field_0x64; + + /** + * Priority that sounds receive when "far away". + * @see mDistanceMaxes + */ + /* 0x064 */ u32 mMaxDistancePriority; /* 0x068 */ f32 field_0x68; /* 0x06C */ f32 field_0x6c; - /* 0x070 */ f32 field_0x70[15]; - /* 0x0AC */ f32 field_0xac[15]; - /* 0x0E8 */ f32 field_0xe8[15]; + /* 0x070 */ f32 mVolumeFactor[15]; + /* 0x0AC */ f32 mPriorityFactor[15]; + /* 0x0E8 */ f32 mFxMixFactor[15]; /* 0x124 */ bool mVolumeDistInit; /* 0x125 */ bool mDolbyDistInit; }; // Size: 0x128 struct Z2AudibleRelPos { - /* 0x00 */ JGeometry::TVec3 field_0x00; - /* 0x0C */ f32 field_0xC; - /* 0x10 */ f32 field_0x10; + /* 0x00 */ JGeometry::TVec3 mCameraRelative; + + /** + * Distance from mCameraRelative. This is from the object root and not the + * exact distance used for volume/priority calculations. + */ + /* 0x0C */ f32 mTrueDistance; + + /** + * Distance from mCameraRelative but offset by mVolCenterZ. + * This presumably means the distance is more centered on the object than mTrueDistance. + */ + /* 0x10 */ f32 mCenterDistance; }; struct Z2AudibleChannel { @@ -171,7 +214,7 @@ struct Z2AudibleChannel { } /* 0x00 */ JASSoundParams mParams; - /* 0x14 */ Z2AudibleRelPos field_0x14; + /* 0x14 */ Z2AudibleRelPos mRelPos; /* 0x28 */ f32 field_0x28; /* 0x2c */ f32 mPan; /* 0x30 */ f32 mDolby; @@ -200,8 +243,8 @@ struct Z2Audible : public JAIAudible, public JASPoolAllocObject { /* 0x10 */ JAUAudibleParam mParam; /* 0x14 */ Z2AudibleAbsPos mAbsPos; - /* 0x2C */ Z2AudibleChannel mChannel[1]; - /* 0x64 */ f32 field_0x64[1]; + /* 0x2C */ Z2AudibleChannel mChannel[Z2_AUDIO_PLAYERS]; + /* 0x64 */ f32 mMicDistances[Z2_AUDIO_PLAYERS]; }; struct Z2Audience : public JAIAudience, public JASGlobalInstance { diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index fa262a7426..4de69b8416 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -101,7 +101,7 @@ struct JAISoundStatus_ { bool isMute() const { return field_0x0.flags.mute; } bool isPaused() const { return field_0x0.flags.paused; } void pauseWhenOut() { - field_0x1.flags.flag3 = 1; + field_0x1.flags.mPauseWhenOut = 1; } /* 0x0 */ union { @@ -120,9 +120,9 @@ struct JAISoundStatus_ { /* 0x1 */ union { u8 value; struct { - u8 flag1 : 1; + u8 mComesBack : 1; u8 flag2 : 1; - u8 flag3 : 1; + u8 mPauseWhenOut : 1; u8 flag4 : 1; u8 flag5 : 1; u8 flag6 : 1; @@ -309,7 +309,7 @@ public: bool hasLifeTime() const { return status_.field_0x1.flags.flag2; } void removeLifeTime_() { - status_.field_0x1.flags.flag1 = false; + status_.field_0x1.flags.mComesBack = false; status_.field_0x1.flags.flag2 = 0; } @@ -346,7 +346,7 @@ public: void setComesBack(bool param_0) { JUT_ASSERT(354, status_.state.flags.calcedOnce == 0); - status_.field_0x1.flags.flag1 = 1; + status_.field_0x1.flags.mComesBack = 1; if (param_0) { status_.pauseWhenOut(); } diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h index 22d175154a..eccdc8deac 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundParams.h @@ -9,14 +9,14 @@ */ struct JAISoundParamsProperty { void init() { - field_0x0 = 1.0f; - field_0x4 = 0.0f; - field_0x8 = 1.0f; + mVolume = 1.0f; + mFxMix = 0.0f; + mPitch = 1.0f; } - /* 0x00 */ f32 field_0x0; - /* 0x04 */ f32 field_0x4; - /* 0x08 */ f32 field_0x8; + /* 0x00 */ f32 mVolume; + /* 0x04 */ f32 mFxMix; + /* 0x08 */ f32 mPitch; }; // Size: 0xC /** diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h index cc5653fe25..4c0ade1282 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUAudibleParam.h @@ -10,7 +10,7 @@ */ struct JAUAudibleParam { f32 getDopplerPower() const { - return field_0x0.bytes.b0_0 * (1.0f / 15.0f); + return field_0x0.bytes.mDopplerPower * (1.0f / 15.0f); } union { @@ -19,14 +19,18 @@ struct JAUAudibleParam { BE(u16) f1; } half; struct { - u8 b0_0 : 4; - u8 b0_4 : 1; - u8 b0_5 : 1; - u8 b0_6 : 1; - u8 b0_7 : 1; - u8 b1_0 : 1; - u8 b1_1 : 1; - u8 b1_2_7 : 6; + u8 mDopplerPower : 4; + u8 mCalculatePriority : 1; + u8 mCalcDistanceVolume : 1; + u8 mCalcFxMix : 1; + u8 mCullAtMaxDistance : 1; + u8 mCalcPan : 1; + u8 mCalcDolby : 1; + /** + * Most bits in this field are unused, 8 indicates clamping of volume + * to ensure it doesn't go below 0.2 in mixChannelOut() + */ + u8 mClampMinVolume : 6; u8 b2; u8 b3; } bytes; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 3571e386b6..40ac65bf45 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -5,16 +5,162 @@ #include "JSystem/JAudio2/JASGadget.h" #include "helpers/endian.h" +// Constants for TypeIDs of JAUSoundTable entries. + +/** + * Used as a test to see if the type ID is valid. All other type IDs contain this bit. + */ +#define SOUND_TYPEID_VALID 0x40 + +/** + * Sound is a sound effect. + */ +#define SOUND_TYPEID_SOUND_EFFECT 0x51 + +/** + * Sound is a music sequence. + */ +#define SOUND_TYPEID_SEQUENCE 0x60 + +/** + * Sound is a streamed music file. + */ +#define SOUND_TYPEID_STREAM 0x70 + +/** + * Sound is a streamed music file. Unsure of difference from SOUND_TYPEID_STREAM + */ +#define SOUND_TYPEID_STREAM_ALT 0x71 + +// +// Values for mSwBit on sound effect items. +// + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 + +#define SOUND_VOL_DIST_BIT_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_MASK >> 16) +#define SOUND_VOL_DIST_BIT_2_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_2_MASK >> 16) +#define SOUND_VOL_SOMETHING_MASK_SHIFTED (SOUND_SW_VOL_SOMETHING_MASK >> 16) + /** * @ingroup jsystem-jaudio - * + * A single entry in the sound table. + * Fields are interpreted differently depending on the type ID of the entry, + * which is not stored in this struct. */ struct JAUSoundTableItem { u8 mPriority; - u8 field_0x1; - BE(u16) mResourceId; - BE(u32) field_0x4; - BE(f32) field_0x8; + u8 mVolume; + union { + /** + * For music sequences: the resource ID of the sequence. + */ + BE(u16) mResourceId; + + /** + * For streamed music: bitpacked channel configuration. + */ + BE(u16) mStreamPanParameters; + }; + union { + /** + * For sound effects: bitpacked field controlling a *bunch* of audio parameters. + */ + BE(u32) mSwBit; + + /** + * For streamed music: offset (relative to start of BST) + * to null-terminated string containing music file path. + */ + BE(u32) mStreamFilePath; + }; + + /** + * For sound effects: pitch multiplier. + */ + BE(f32) mPitch; }; /** @@ -33,8 +179,8 @@ struct JAUSoundTable_ { mRoot = NULL; } - void init(const void* param_0) { - mData = param_0; + void init(const void* dataStart) { + mData = dataStart; // magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?) // I also do not know how it is different between JAUSoundTable and JAUSoundNameTable // Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber(). @@ -60,16 +206,16 @@ struct JAUSoundTable_ { return (Section*)((u8*)mData + offset); } - Group* getGroup(Section* param_1, int index) const { + Group* getGroup(Section* section, int index) const { int iVar1; if (index < 0) { return NULL; } - if ((u32)index >= param_1->mNumGroups) { + if ((u32)index >= section->mNumGroups) { return NULL; } - u32 offset = param_1->getGroupOffset(index); + u32 offset = section->getGroupOffset(index); if (offset == 0) { return NULL; } diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index 504c875bad..eb8ea7b90e 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -70,7 +70,7 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, if (inner_.field_0x26c) { switch (inner_.track.getStatus()) { case JASTrack::STATUS_STOPPED: - if (status_.field_0x1.flags.flag1) { + if (status_.field_0x1.flags.mComesBack) { startTrack_(params); } else { stop_JAISound_(); @@ -84,8 +84,8 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params, startTrack_(params); } } - } else if (status_.field_0x1.flags.flag1) { - if (status_.field_0x1.flags.flag3) { + } else if (status_.field_0x1.flags.mComesBack) { + if (status_.field_0x1.flags.mPauseWhenOut) { inner_.track.pause(true); } else { stopTrack_(); diff --git a/libs/JSystem/src/JAudio2/JAISeMgr.cpp b/libs/JSystem/src/JAudio2/JAISeMgr.cpp index 0809ede5c6..25ed54ca39 100644 --- a/libs/JSystem/src/JAudio2/JAISeMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeMgr.cpp @@ -167,7 +167,7 @@ JAISeMgr::JAISeMgr(bool setInstance) : JASGlobalInstance(setInstance) } bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { if (mCategoryMgrs[i].JAISeCategoryMgr::isUsingSeqData(seqDataRegion)) { return true; } @@ -177,7 +177,7 @@ bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { bool r30 = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { switch (mCategoryMgrs[i].JAISeCategoryMgr::releaseSeqData(seqDataRegion)) { case 0: return 0; @@ -191,14 +191,14 @@ int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) { } void JAISeMgr::setCategoryArrangement(const JAISeCategoryArrangement& arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].setMaxActiveSe(arrangement.mItems[i].mMaxActiveSe); mCategoryMgrs[i].setMaxInactiveSe(arrangement.mItems[i].mMaxInactiveSe); } } void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { int active = mCategoryMgrs[i].getMaxActiveSe(); JUT_ASSERT(299, active <= 255); arrangement->mItems[i].mMaxActiveSe = active; @@ -209,19 +209,19 @@ void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) { } void JAISeMgr::stop() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stop(); } } void JAISeMgr::stopSoundID(JAISoundID id) { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].stopSoundID(id); } } void JAISeMgr::initParams() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].getParams()->init(); } } @@ -270,16 +270,16 @@ JAISe* JAISeMgr::newSe_(int category, u32 priority) { void JAISeMgr::calc() { mParams.calc(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_calc_(); } - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_freeDeadSe_(); } } void JAISeMgr::mixOut() { - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { mCategoryMgrs[i].JAISeMgr_mixOut_(mParams, mSoundActivity); } } @@ -324,7 +324,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry int JAISeMgr::getNumActiveSe() const { int num = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < NUM_CATEGORIES; i++) { num += mCategoryMgrs[i].getNumSe(); } return num; diff --git a/libs/JSystem/src/JAudio2/JAISound.cpp b/libs/JSystem/src/JAudio2/JAISound.cpp index fe6b45bb8b..6dec60c39d 100644 --- a/libs/JSystem/src/JAudio2/JAISound.cpp +++ b/libs/JSystem/src/JAudio2/JAISound.cpp @@ -54,9 +54,9 @@ s32 JAISoundStatus_::unlockIfLocked() { } void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* outParams, f32 param_2) { - outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.field_0x0) * param_2; - outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.field_0x4); - outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.field_0x8); + outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.mVolume) * param_2; + outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.mFxMix); + outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.mPitch); outParams->mPan = (inParams.mPan + move_.params_.mPan) - 0.5f; outParams->mDolby = inParams.mDolby + move_.params_.mDolby; } @@ -181,7 +181,7 @@ bool JAISound::calc_JAISound_() { } if (audience_ != NULL && audible_ != NULL) { - if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.flag1 == 0) { + if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.mComesBack == 0) { stop_JAISound_(); } } else { diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index dda1a4b2d0..5dfe3baed9 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -36,14 +36,14 @@ Z2Audible::Z2Audible(const JGeometry::TVec3& pos, const JGeometry::TVec3= 0); - JUT_ASSERT(81, index < 1); + JUT_ASSERT(81, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index].mParams; } void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundParams& inParams, int index) { JUT_ASSERT(89, index >= 0); - JUT_ASSERT(90, index < 1); + JUT_ASSERT(90, index < 1); // Z2_AUDIO_PLAYERS Z2AudibleChannel* channel = &mChannel[index]; - u8 iStack_94 = (mParam.field_0x0.half.f1 & 0xf00) >> 8; - if (iStack_94 == 0) { + u8 volSomething = (mParam.field_0x0.half.f1 & SOUND_VOL_SOMETHING_MASK_SHIFTED) >> 8; + if (volSomething == 0) { channel->mParams.combine(outParams, inParams); return; } @@ -91,7 +91,7 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa f32 dVar9 = inParams.mVolume * local_b8.length(); f32 dVar10 = 1.0f; - f32 dVar12 = Z2Calc::linearTransform(iStack_94, 0.0f, 15.0f, 1.0f, 0.3f, true); + f32 dVar12 = Z2Calc::linearTransform(volSomething, 0.0f, 15.0f, 1.0f, 0.3f, true); if (inParams.mVolume > dVar12 && dVar9 > 0.001f) { dVar10 = Z2Calc::getParamByExp(dVar9, 0.3f, 0.001f, 0.1f, dVar12, 1.0f, Z2Calc::CURVE_POSITIVE); @@ -141,18 +141,18 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa Z2AudibleChannel* Z2Audible::getChannel(int index) { JUT_ASSERT(220, index >= 0); - JUT_ASSERT(221, index < 1); + JUT_ASSERT(221, index < 1); // Z2_AUDIO_PLAYERS return &mChannel[index]; } u32 Z2Audible::getDistVolBit() { u16 uVar1 = getAudibleParam()->field_0x0.half.f1; if (uVar1 != 0) { - if ((uVar1 & 7) != 0) { - return uVar1 & 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED) != 0) { + return uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED; } - if ((uVar1 & 0x70) != 0) { - return ((int)(uVar1 & 0x70) >> 4) + 7; + if ((uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) != 0) { + return ((int)(uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) >> 4) + 7; } } @@ -166,15 +166,15 @@ Z2Audience3DSetting::Z2Audience3DSetting() { } void Z2Audience3DSetting::init() { - field_0x40 = Z2Param::MIN_DISTANCE_VOLUME; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x54 = Z2Param::DISTANCE_FX_PARAM; - field_0x50 = 0.0f; - field_0x4c = Z2Param::DOLBY_CENTER_VALUE; - field_0x58 = 0.5f; - field_0x64 = 0xff; - field_0x5c = Z2Param::SONIC_SPEED; + mMinDistanceVolume = Z2Param::MIN_DISTANCE_VOLUME; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mMaxDistanceFxMix = Z2Param::DISTANCE_FX_PARAM; + mMinDistanceFxMix = 0.0f; + mDolbyCenterValue = Z2Param::DOLBY_CENTER_VALUE; + mPanFactor = 0.5f; + mMaxDistancePriority = 0xff; + mSonicSpeed = Z2Param::SONIC_SPEED; field_0x60 = 1.5f; initVolumeDist(); @@ -184,8 +184,8 @@ void Z2Audience3DSetting::init() { void Z2Audience3DSetting::initVolumeDist() { if (mVolumeDistInit) return; - field_0x0[0] = Z2Param::DISTANCE_MAX; - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mDistanceMaxes[0] = Z2Param::DISTANCE_MAX; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; calcVolumeFactorAll(); calcPriorityFactorAll(); @@ -197,12 +197,12 @@ void Z2Audience3DSetting::initVolumeDist() { static f32 cNearFarRatio = Z2Param::MAX_VOLUME_DISTANCE / Z2Param::DISTANCE_MAX; void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { - field_0x0[0] = param_0; + mDistanceMaxes[0] = param_0; if (param_0 > Z2Param::DISTANCE_MAX) { - field_0x3c = cNearFarRatio * param_0; + mMaxVolumeDistance = cNearFarRatio * param_0; } else { - field_0x3c = Z2Param::MAX_VOLUME_DISTANCE; + mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE; } calcVolumeFactorAll(); @@ -214,29 +214,29 @@ void Z2Audience3DSetting::updateVolumeDist(f32 param_0) { void Z2Audience3DSetting::initDolbyDist() { if (!mDolbyDistInit) { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; - field_0x68 = -field_0x4c / field_0x44; - field_0x6c = (1.0f - field_0x4c) / field_0x48; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + field_0x68 = -mDolbyCenterValue / mDolbyFrontDistanceMax; + field_0x6c = (1.0f - mDolbyCenterValue) / mDolbyBehindDistanceMax; mDolbyDistInit = true; } } void Z2Audience3DSetting::updateDolbyDist(f32 param_0, f32 param_1) { if (param_0 > -1.0f * Z2Param::DOLBY_FLONT_DISTANCE_MAX) { - field_0x44 = -1.0f * param_0; + mDolbyFrontDistanceMax = -1.0f * param_0; } else { - field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX; + mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX; } - if (param_1 < 2.0f * field_0x44) { - param_1 = 2.0f * field_0x44; + if (param_1 < 2.0f * mDolbyFrontDistanceMax) { + param_1 = 2.0f * mDolbyFrontDistanceMax; } if (param_1 > Z2Param::DOLBY_BEHIND_DISTANCE_MAX) { - field_0x48 = param_1; + mDolbyBehindDistanceMax = param_1; } else { - field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; + mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX; } mDolbyDistInit = false; @@ -254,28 +254,28 @@ Z2AudioCamera::Z2AudioCamera() { } void Z2AudioCamera::init() { - JGeometry::TPosition3f32 aTStack_38; - aTStack_38.identity(); + JGeometry::TPosition3f32 viewMatrix; + viewMatrix.identity(); JGeometry::TVec3 VStack_44; VStack_44.set(100000.0f, 100000.0f, 100000.0f); - setCameraState(aTStack_38, *(Vec*)&VStack_44, true); + setCameraState(viewMatrix, *(Vec*)&VStack_44, true); } -void Z2AudioCamera::setCameraState(f32 const (*param_0)[4], Vec& pos, bool param_2) { - field_0x0.set(param_0); - if (param_2) { +void Z2AudioCamera::setCameraState(f32 const (*viewMatrix)[4], Vec& pos, bool initial) { + mViewMatrix.set(viewMatrix); + if (initial) { mPos.set(pos); - field_0x48.set(mPos); + mLastPos.set(mPos); mVel.zero(); } else { - field_0x48.set(mPos); + mLastPos.set(mPos); mPos.set(pos); - mVel.sub(mPos, field_0x48); + mVel.sub(mPos, mLastPos); } } -void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, bool param_6) { +void Z2AudioCamera::setCameraState(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, bool initial) { JGeometry::TVec3 aTStack_c0; VECSubtract(¶m_2, &pos, (Vec*)&aTStack_c0); mCamDist = aTStack_c0.length(); @@ -312,7 +312,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 Z2GetAudience()->getSetting()->updateDolbyDist(mCamDist, mCamDist); } } else { - Vec aTStack_cc = {param_0[0][0], param_0[0][1], param_0[0][2]}; + Vec aTStack_cc = {viewMatrix[0][0], viewMatrix[0][1], viewMatrix[0][2]}; Mtx rotMtx; MTXRotAxisRad(rotMtx, &aTStack_cc, (M_PI / 180.0f) * (-1.0f * dVar10)); JGeometry::TVec3 aTStack_d8; @@ -324,7 +324,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3 } } - setCameraState(param_0, pos, param_6); + setCameraState(viewMatrix, pos, initial); } void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { @@ -337,17 +337,17 @@ void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) { return; } - Z2AudibleRelPos* relPos = &channel->field_0x14; - MTXMultVec(field_0x0, (Vec*)&audible->getPos(), (Vec*)&relPos->field_0x00); - relPos->field_0xC = relPos->field_0x00.length(); + Z2AudibleRelPos* relPos = &channel->mRelPos; + MTXMultVec(mViewMatrix, (Vec*)&audible->getPos(), (Vec*)&relPos->mCameraRelative); + relPos->mTrueDistance = relPos->mCameraRelative.length(); - JGeometry::TVec3 aTStack_38(relPos->field_0x00); + JGeometry::TVec3 aTStack_38(relPos->mCameraRelative); aTStack_38.z += mVolCenterZ; - relPos->field_0x10 = aTStack_38.length(); + relPos->mCenterDistance = aTStack_38.length(); } bool Z2AudioCamera::convertAbsToRel(Vec& src, Vec* dst) const { - MTXMultVec(field_0x0, &src, dst); + MTXMultVec(mViewMatrix, &src, dst); return isInSight(*dst); } @@ -395,7 +395,7 @@ void Z2SpotMic::calcVolumeFactor(int camID) { void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { JUT_ASSERT(622, camID >= 0); - JUT_ASSERT(623, camID < 1); + JUT_ASSERT(623, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn && mPosPtr != 0 && camera != NULL) { clearMicState(camID); @@ -406,8 +406,8 @@ void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) { aVStack_58.z += camera->getVolCenterZ(); f32 dVar10 = aVStack_58.length(); - f32 dVar11 = Z2GetAudience()->getSetting()->field_0x0[0]; - f32 dVar12 = Z2GetAudience()->getSetting()->field_0x3c; + f32 dVar11 = Z2GetAudience()->getSetting()->mDistanceMaxes[0]; + f32 dVar12 = Z2GetAudience()->getSetting()->mMaxVolumeDistance; if (dVar10 > dVar11) { field_0x18[camID] = field_0xc; } else { @@ -443,21 +443,21 @@ f32 Z2SpotMic::calcMicDist(Z2Audible* audible) { return aTStack_1c.length(); } -u32 Z2SpotMic::calcMicPriority(f32 param_0) { - if (param_0 > field_0x4) { - return Z2GetAudience()->getSetting()->field_0x64; +u32 Z2SpotMic::calcMicPriority(f32 micDistance) { + if (micDistance > field_0x4) { + return Z2GetAudience()->getSetting()->mMaxDistancePriority; } - if (param_0 < field_0x0) { + if (micDistance < field_0x0) { return 0; } - return field_0x1c * (param_0 - field_0x0); + return field_0x1c * (micDistance - field_0x0); } f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { JUT_ASSERT(687, camID >= 0); - JUT_ASSERT(688, camID < 1); + JUT_ASSERT(688, camID < 1); // Z2_AUDIO_PLAYERS if (mMicOn == false) { return param_2; @@ -497,7 +497,7 @@ f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) { } Z2Audience::Z2Audience() : JASGlobalInstance(true), field_0x4(1.0f), field_0x8(0x7f) { - mNumPlayers = 1; + mNumPlayers = Z2_AUDIO_PLAYERS; mUsingOffMicVol = false; mAudioCamera[0].init(); mAudioCamera[0].setMainCamera(true); @@ -512,11 +512,11 @@ bool Z2Audience::isActive() const { return Z2Audible::getTotalMemCount() != Z2Audible::getFreeMemCount(); } -void Z2Audience::setAudioCamera(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3, - f32 param_4, bool param_5, int camID, bool param_7) { +void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3, + f32 param_4, bool param_5, int camID, bool initial) { JUT_ASSERT(687, camID >= 0); JUT_ASSERT(688, camID < mNumPlayers); - mAudioCamera[camID].setCameraState(param_0, pos, param_2, param_3, param_4, param_5, param_7); + mAudioCamera[camID].setCameraState(viewMatrix, pos, param_2, param_3, param_4, param_5, initial); mLinkMic->setMicState(&mAudioCamera[camID], camID); } @@ -529,7 +529,7 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID } JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID); - bool x = params.field_0x0.bytes.b0_0 != 0; + bool x = params.field_0x0.bytes.mDopplerPower != 0; Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x); if (audible == NULL) { @@ -543,13 +543,13 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID if (channel != NULL) { u32 distVolBit = audible->getDistVolBit(); mAudioCamera[i].convertAbsToRel(audible, i); - calcDeltaPriority_(channel->field_0x14.field_0x10, distVolBit, false); + calcDeltaPriority_(channel->mRelPos.mCenterDistance, distVolBit, false); } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - audible->field_0x64[i] = mSpotMic[i].calcMicDist(audible); + audible->mMicDistances[i] = mSpotMic[i].calcMicDist(audible); } } @@ -569,7 +569,7 @@ Z2Audible::~Z2Audible() {} u32 Z2Audience::calcPriority(JAIAudible* audible) { Z2Audible* Z2audible = (Z2Audible*)audible; - if (!Z2audible->getAudibleParam()->field_0x0.bytes.b0_4) { + if (!Z2audible->getAudibleParam()->field_0x0.bytes.mCalculatePriority) { for (int i = 0; i < mNumPlayers; i++) { mAudioCamera[i].convertAbsToRel(Z2audible, i); } @@ -584,17 +584,17 @@ u32 Z2Audience::calcPriority(JAIAudible* audible) { mAudioCamera[i].convertAbsToRel(Z2audible, i); u32 distvolBit = Z2audible->getDistVolBit(); const JAUAudibleParam* puVar1 = Z2audible->getAudibleParam(); - deltaPriority[i] = calcDeltaPriority_(channel->field_0x14.field_0x10, distvolBit, puVar1->field_0x0.bytes.b0_7); + deltaPriority[i] = calcDeltaPriority_(channel->mRelPos.mCenterDistance, distvolBit, puVar1->field_0x0.bytes.mCullAtMaxDistance); if (deltaPriority[i] < rv) { rv = deltaPriority[i]; } } } - for (int i = 0; i < 1; i++) { + for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) { if (mSpotMic[i].isOn()) { - Z2audible->field_0x64[i] = mSpotMic[i].calcMicDist(Z2audible); - u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->field_0x64[i]); + Z2audible->mMicDistances[i] = mSpotMic[i].calcMicDist(Z2audible); + u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->mMicDistances[i]); if (micPriority < rv) { rv = micPriority; } @@ -619,31 +619,31 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi return; } - JASSoundParams local_60; + JASSoundParams calcParams; u32 distVolBit = Z2audible->getDistVolBit(); - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_5) { - local_60.mVolume = calcVolume_(channel->field_0x14.field_0x10, distVolBit); - f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->field_0x64[0], channelNum, local_60.mVolume); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDistanceVolume) { + calcParams.mVolume = calcVolume_(channel->mRelPos.mCenterDistance, distVolBit); + f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->mMicDistances[0], channelNum, calcParams.mVolume); if (dVar9 < 0.0f) { - dVar9 = calcOffMicSound(local_60.mVolume); + dVar9 = calcOffMicSound(calcParams.mVolume); } - local_60.mVolume = dVar9; + calcParams.mVolume = dVar9; JAUAudibleParam* params = Z2audible->getAudibleParam(); - if ((params->field_0x0.bytes.b1_2_7 & 8) && (local_60.mVolume <= 0.2f)) { - local_60.mVolume = 0.2f; + if ((params->field_0x0.bytes.mClampMinVolume & 8) && (calcParams.mVolume <= 0.2f)) { + calcParams.mVolume = 0.2f; } } else { - local_60.mVolume = 1.0f; + calcParams.mVolume = 1.0f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_1 && mNumPlayers == 1) { - local_60.mDolby = calcRelPosDolby(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDolby && mNumPlayers == 1) { + calcParams.mDolby = calcRelPosDolby(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } else { - local_60.mDolby = 0.5f; + calcParams.mDolby = 0.5f; } - if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_0) { + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcPan) { if (mNumPlayers > 2) { f32 fVar1; if (channelNum & 1) { @@ -651,24 +651,24 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi } else { fVar1 = 0.0f; } - local_60.mPan = fVar1; + calcParams.mPan = fVar1; } else { - local_60.mPan = calcRelPosPan(*(Vec*)&channel->field_0x14.field_0x00, channelNum); + calcParams.mPan = calcRelPosPan(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum); } } else { - local_60.mPan = 0.5f; + calcParams.mPan = 0.5f; } - local_60.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); - f32 dVar9; - if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_6) { - dVar9 = calcFxMix_(channel->field_0x14.field_0xC, distVolBit); + calcParams.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]); + f32 fxMix; + if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcFxMix) { + fxMix = calcFxMix_(channel->mRelPos.mTrueDistance, distVolBit); } else { - dVar9 = 0.0f; + fxMix = 0.0f; } - local_60.mFxMix = dVar9; - local_60.clamp(); - Z2audible->setOuterParams(outParams, local_60, channelNum); + calcParams.mFxMix = fxMix; + calcParams.clamp(); + Z2audible->setOuterParams(outParams, calcParams, channelNum); } @@ -688,18 +688,18 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) { aTStack_3c.y *= 1.5f; f32 len = aTStack_3c.length(); - if (len > mSetting.field_0x0[0] * param_1) { - return mSetting.field_0x40; + if (len > mSetting.mDistanceMaxes[0] * param_1) { + return mSetting.mMinDistanceVolume; } - if (len < mSetting.field_0x3c) { + if (len < mSetting.mMaxVolumeDistance) { return 1.0f; } return JGeometry::TUtil::clamp( - 1.0f + ((mSetting.field_0x40 - 1.0f) / - ((mSetting.field_0x0[0] * param_1) - mSetting.field_0x3c)) * - (len - mSetting.field_0x3c), + 1.0f + ((mSetting.mMinDistanceVolume - 1.0f) / + ((mSetting.mDistanceMaxes[0] * param_1) - mSetting.mMaxVolumeDistance)) * + (len - mSetting.mMaxVolumeDistance), 0.0f, 1.0f); } @@ -717,7 +717,7 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { return 0.5f; } - dVar6 = (0.5f + mSetting.field_0x58 * (local_54.x / dVar6)); + dVar6 = (0.5f + mSetting.mPanFactor * (local_54.x / dVar6)); if (local_54.z <= 0.0f) { f32 fovySin = mAudioCamera[camID].getFovySin(); if (dVar6 < 0.5f) { @@ -757,52 +757,52 @@ f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) { return 0.5f - 0.5f * cosf(t * static_cast(M_PI)); } #endif - if (fVar1 > mSetting.field_0x48) { + if (fVar1 > mSetting.mDolbyBehindDistanceMax) { return 1.0f; } - if (fVar1 < mSetting.field_0x44) { + if (fVar1 < mSetting.mDolbyFrontDistanceMax) { return 0.0f; } if (fVar1 < 0.0f) { return Z2Calc::getParamByExp( - fVar1, mSetting.field_0x44, 0.0f, + fVar1, mSetting.mDolbyFrontDistanceMax, 0.0f, 0.3f, 0.0f, - mSetting.field_0x4c, Z2Calc::CURVE_POSITIVE); + mSetting.mDolbyCenterValue, Z2Calc::CURVE_POSITIVE); } return Z2Calc::getParamByExp( - fVar1, 0.0f, mSetting.field_0x48, - 0.3f, mSetting.field_0x4c, + fVar1, 0.0f, mSetting.mDolbyBehindDistanceMax, + 0.3f, mSetting.mDolbyCenterValue, 1.0f, Z2Calc::CURVE_NEGATIVE); } f32 Z2Audience::calcVolume_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x40; + if (param_0 > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMinDistanceVolume; } - if (param_0 < mSetting.field_0x3c) { + if (param_0 < mSetting.mMaxVolumeDistance) { return 1.0f; } - return JGeometry::TUtil::clamp((mSetting.field_0x70[distVolBit] * (param_0 - mSetting.field_0x3c)) + 1.0f, 0.0f, 1.0f); + return JGeometry::TUtil::clamp((mSetting.mVolumeFactor[distVolBit] * (param_0 - mSetting.mMaxVolumeDistance)) + 1.0f, 0.0f, 1.0f); } -u32 Z2Audience::calcDeltaPriority_(f32 param_0, int distVolBit, bool param_2) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - if (param_2) { +u32 Z2Audience::calcDeltaPriority_(f32 distance, int distVolBit, bool cullMaxDistance) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + if (cullMaxDistance) { return -1; } - return mSetting.field_0x64; + return mSetting.mMaxDistancePriority; } - if (param_0 < mSetting.field_0x3c) { + if (distance < mSetting.mMaxVolumeDistance) { return 0; } - return mSetting.field_0xac[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mPriorityFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, @@ -810,24 +810,24 @@ f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3& param_0, const JGeometry::TVec3& param_2, f32 param_3) const { f32 dVar8 = param_0.dot(param_1); f32 dVar7 = param_0.dot(param_2); - return (mSetting.field_0x5c + param_3 * dVar8) / (mSetting.field_0x5c + param_3 * dVar7); + return (mSetting.mSonicSpeed + param_3 * dVar8) / (mSetting.mSonicSpeed + param_3 * dVar7); } -f32 Z2Audience::calcFxMix_(f32 param_0, int distVolBit) const { - if (param_0 > mSetting.field_0x0[distVolBit]) { - return mSetting.field_0x54; +f32 Z2Audience::calcFxMix_(f32 distance, int distVolBit) const { + if (distance > mSetting.mDistanceMaxes[distVolBit]) { + return mSetting.mMaxDistanceFxMix; } - if (param_0 < mSetting.field_0x3c) { - return mSetting.field_0x50; + if (distance < mSetting.mMaxVolumeDistance) { + return mSetting.mMinDistanceFxMix; } - return mSetting.field_0x50 + mSetting.field_0xe8[distVolBit] * (param_0 - mSetting.field_0x3c); + return mSetting.mMinDistanceFxMix + mSetting.mFxMixFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance); } f32 Z2Audience::calcPitch_(Z2AudibleChannel* channel, const Z2Audible* audible, const Z2AudioCamera* camera) const { JAUAudibleParam audParam = *audible->getAudibleParam(); - if (audParam.field_0x0.bytes.b0_0) { + if (audParam.field_0x0.bytes.mDopplerPower) { JGeometry::TVec3 aTStack_4c; - aTStack_4c.normalize(channel->field_0x14.field_0x00); + aTStack_4c.normalize(channel->mRelPos.mCameraRelative); JAUAudibleParam audParam = *audible->getAudibleParam(); f32 doppler = audParam.getDopplerPower(); diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index 1b2152fcb7..f51fc10d92 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) { JSUList* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList(); JSULink* stream; for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) { - if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != 0x71) { + if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != SOUND_TYPEID_STREAM) { stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME); } } diff --git a/src/Z2AudioLib/Z2SoundInfo.cpp b/src/Z2AudioLib/Z2SoundInfo.cpp index cb67b4c805..9befa79b26 100644 --- a/src/Z2AudioLib/Z2SoundInfo.cpp +++ b/src/Z2AudioLib/Z2SoundInfo.cpp @@ -23,7 +23,7 @@ u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const { if (data != NULL) { switch ((typeID & 0xf0)) { - case 0x60: + case SOUND_TYPEID_SEQUENCE: return (u16)data->mResourceId; } } @@ -53,7 +53,7 @@ u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { return data->mPriority; } @@ -67,64 +67,64 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_FX_MIX) != 0) { + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: - audibleParam.field_0x0.bytes.b0_0 = 0; - audibleParam.field_0x0.bytes.b0_4 = 1; - audibleParam.field_0x0.bytes.b0_5 = 1; - audibleParam.field_0x0.bytes.b0_6 = 1; - audibleParam.field_0x0.bytes.b0_7 = 0; - audibleParam.field_0x0.bytes.b1_0 = 1; - audibleParam.field_0x0.bytes.b1_1 = 1; - audibleParam.field_0x0.bytes.b1_2_7 = 0; + audibleParam.field_0x0.bytes.mDopplerPower = 0; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; + audibleParam.field_0x0.bytes.mCalcPan = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; + audibleParam.field_0x0.bytes.mClampMinVolume = 0; audibleParam.field_0x0.half.f1 = 0; break; } @@ -139,53 +139,53 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID) { - case 81: - audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8; - if ((getSwBit(soundID) & 1) != 0) { - audibleParam.field_0x0.bytes.b0_4 = 0; + case SOUND_TYPEID_SOUND_EFFECT: + audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { - audibleParam.field_0x0.bytes.b0_4 = 1; + audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & 2) != 0) { - audibleParam.field_0x0.bytes.b0_5 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { - audibleParam.field_0x0.bytes.b0_5 = 1; + audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } if ((getSwBit(soundID) & 4) != 0) { - audibleParam.field_0x0.bytes.b0_6 = 0; + audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { - audibleParam.field_0x0.bytes.b0_6 = 1; + audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & 0x800000) != 0) { - audibleParam.field_0x0.bytes.b0_7 = 1; + if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { - audibleParam.field_0x0.bytes.b0_7 = 0; + audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & 0x1000) != 0) { - audibleParam.field_0x0.bytes.b1_0 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + audibleParam.field_0x0.bytes.mCalcPan = 0; } else { - audibleParam.field_0x0.bytes.b1_0 = 1; + audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & 0x2000) != 0) { - audibleParam.field_0x0.bytes.b1_1 = 0; + if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { - audibleParam.field_0x0.bytes.b1_1 = 1; + audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & 0x80000) != 0) { + if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & 0x7; - iVar1 += (getSwBit(soundID) >> 16) & 0x70; - iVar1 += (getSwBit(soundID) >> 16) & 0xf00; - audibleParam.field_0x0.bytes.b1_2_7 = uVar7; + iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; default: @@ -208,19 +208,19 @@ void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { } switch(typeID) { - case 81: - sePtr->getProperty().field_0x8 *= data->field_0x8; - u32 uStack_6c = (getSwBit(soundID) & 0xf0) >> 4; - if (uStack_6c > 8) { - sePtr->getProperty().field_0x8 += Z2Calc::linearTransform(uStack_6c, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); + case SOUND_TYPEID_SOUND_EFFECT: + sePtr->getProperty().mPitch *= data->mPitch; + u32 pitchParam = (getSwBit(soundID) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; + if (pitchParam > 8) { + sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); } else { - sePtr->getProperty().field_0x8 += (uStack_6c / 48.0f) * Z2Calc::getRandom_0_1(); + sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1(); } - u32 uVar1 = (u32)getSwBit(soundID) >> 0x1c; + u32 uVar1 = (u32)getSwBit(soundID) >> SOUND_SW_RANDOM_VOLUME_OFFSET; if (uVar1 != 0) { f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1(); - sePtr->getProperty().field_0x0 -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); + sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); } break; } @@ -240,13 +240,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); switch (typeID & 0xf0) { - case 0x70: + case SOUND_TYPEID_STREAM: u16 uVar1; s32 iVar4; data = JASGlobalInstance::getInstance()->getData(soundID); JUT_ASSERT(356, data); - uVar1 = data->mResourceId; + uVar1 = data->mStreamPanParameters; numChild = streamPtr->getNumChild(); iVar4 = 0; for (; iVar4 < numChild && uVar1 != 0; uVar1 >>= JAUStdSoundTableType::STRM_CH_SHIFT, iVar4++) { @@ -277,12 +277,12 @@ const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { const void* resource; switch (JASGlobalInstance::getInstance()->getTypeID(soundID) & 0xf0) { - case 0x70: + case SOUND_TYPEID_STREAM: data = JASGlobalInstance::getInstance()->getData(soundID); JUT_ASSERT(394, data); resource = JASGlobalInstance::getInstance()->getResource(); JUT_ASSERT(398, resource); - return JAUStdSoundTableType::StringOffset::getString(resource, data->field_0x4); + return JAUStdSoundTableType::StringOffset::getString(resource, data->mStreamFilePath); default: return NULL; } @@ -300,8 +300,8 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const { u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); if (data != NULL) { switch(typeID) { - case 81: - return data->field_0x4; + case SOUND_TYPEID_SOUND_EFFECT: + return data->mSwBit; } } @@ -313,7 +313,7 @@ void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const { JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); - if (data != NULL && (typeID & 0x40) != 0) { - soundPtr->getProperty().field_0x0 = (1.0f / 127.0f) * data->field_0x1; + if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { + soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume; } } diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index 23e8171736..f38fa14e6a 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -222,7 +222,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startCollisionSE(u32 hitID, u32 mapinfo, Z2So if (30 <= mapinfo && mapinfo <= 52) { Z2Audible* audible = (Z2Audible*)(*handle)->getAudible(); if (audible != NULL) { - audible->getAudibleParam()->field_0x0.bytes.b1_2_7 = 8; + audible->getAudibleParam()->field_0x0.bytes.mClampMinVolume = 8; } } } From 4bcecb3926bda9b752cfc69d1d2b38c0138ca7c0 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Fri, 31 Jul 2026 06:46:41 +0200 Subject: [PATCH 16/36] Linux/Apple compile warnings consistency This re-organizes the CMakeLists.txt a lil so that some of the warning compile flags on Linux also get applied to Apple platforms. --- CMakeLists.txt | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 696ec1af80..1933ca70f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,24 +194,21 @@ if (DUSK_MOVIE_SUPPORT) endif () endif () -if (CMAKE_SYSTEM_NAME STREQUAL Linux) +if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") # -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers # (CW, GCC, Clang, MSVC) encode them identically in big-endian order. # For >4-char literals (which GCC/Clang truncate to int), use the MULTI_CHAR() macro. # -Wdeprecated-declarations: JSystem uses std::iterator, deprecated in C++17 - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-multichar") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations") - set(CMAKE_INSTALL_RPATH "$ORIGIN") - set(CMAKE_BUILD_RPATH "$ORIGIN") -elseif (APPLE) - add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs) - set(CMAKE_INSTALL_RPATH "@loader_path") - set(CMAKE_BUILD_RPATH "@loader_path") -elseif (MSVC) add_compile_options( - $<$:/bigobj> - $<$:/MP> - $<$:/FS> + $<$:-Wno-multichar> + $<$:-Wno-trigraphs> + $<$:-Wno-deprecated-declarations> + ) +elseif (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + add_compile_options( + $<$:/bigobj> + $<$:/MP> + $<$:/FS> ) if (NOT DUSK_BUILD_WARNINGS) @@ -226,6 +223,15 @@ elseif (MSVC) add_compile_options($<$:/utf-8>) endif () +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + set(CMAKE_INSTALL_RPATH "$ORIGIN") + set(CMAKE_BUILD_RPATH "$ORIGIN") +elseif (APPLE) + add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs) + set(CMAKE_INSTALL_RPATH "@loader_path") + set(CMAKE_BUILD_RPATH "@loader_path") +endif () + include(FetchContent) # Declare all dependencies first so CMake can download them in parallel From 6d7269b654ab2d7c46900d566777d748433c1994 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 15:03:46 +0200 Subject: [PATCH 17/36] Fix missing sizeof in JASBasicBank.cpp Likely doesn't matter but good to clean up ig --- libs/JSystem/src/JAudio2/JASBasicBank.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JASBasicBank.cpp b/libs/JSystem/src/JAudio2/JASBasicBank.cpp index 9b3c032bbb..868e7189dd 100644 --- a/libs/JSystem/src/JAudio2/JASBasicBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicBank.cpp @@ -14,9 +14,9 @@ void JASBasicBank::newInstTable(u8 num, JKRHeap* heap) { mInstNumMax = num; mInstTable = JKR_NEW_ARRAY_ARGS(JASInst*, mInstNumMax, heap, 0); #if TARGET_ANDROID - JASCalc::_bzero(mInstTable, mInstNumMax * 4); + JASCalc::_bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #else - JASCalc::bzero(mInstTable, mInstNumMax * 4); + JASCalc::bzero(mInstTable, mInstNumMax * sizeof(mInstTable[0])); #endif } } From c43ed69b6d8b3ef7c7da24e9b893057d22232e68 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 15:19:21 +0200 Subject: [PATCH 18/36] More JAudio naming, focused on instruments this time --- .../include/JSystem/JAudio2/JASBasicInst.h | 32 ++++----- .../include/JSystem/JAudio2/JASChannel.h | 2 +- .../include/JSystem/JAudio2/JASDrumSet.h | 8 +-- libs/JSystem/src/JAudio2/JASAramStream.cpp | 4 +- libs/JSystem/src/JAudio2/JASBNKParser.cpp | 28 ++++---- libs/JSystem/src/JAudio2/JASBank.cpp | 72 +++++++++---------- libs/JSystem/src/JAudio2/JASBasicBank.cpp | 4 +- libs/JSystem/src/JAudio2/JASBasicInst.cpp | 30 ++++---- libs/JSystem/src/JAudio2/JASChannel.cpp | 10 +-- libs/JSystem/src/JAudio2/JASDrumSet.cpp | 30 ++++---- libs/JSystem/src/JAudio2/JASTrack.cpp | 12 ++-- libs/JSystem/src/JAudio2/JASVoiceBank.cpp | 6 +- 12 files changed, 119 insertions(+), 119 deletions(-) diff --git a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h index 5fe6b84db4..fdd141a5c5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASBasicInst.h @@ -14,20 +14,20 @@ const int OSC_MAX = 2; */ struct JASInstParam : public JASSoundParams { JASInstParam() { - field_0x14 = NULL; - field_0x18 = 0; - field_0x1a = 0; - field_0x1c = 0; - field_0x1d = 0; - field_0x1e = 0; + mOscillators = NULL; + mDirectRelease = 0; + mWaveId = 0; + mChannelType = 0; + mOscillatorCount = 0; + mDontSetKey = 0; } - JASOscillator::Data** field_0x14; - u16 field_0x18; - u16 field_0x1a; - u8 field_0x1c; - u8 field_0x1d; - u8 field_0x1e; + JASOscillator::Data** mOscillators; + u16 mDirectRelease; + u16 mWaveId; + u8 mChannelType; // Always CHANNEL_WAVE in practice + u8 mOscillatorCount; + u8 mDontSetKey; }; /** @@ -52,10 +52,10 @@ struct JASBasicInst : public JASInst { void setHighKey(int key) { mHighKey = key; } /* 0x0 */ s32 mHighKey; - /* 0x4 */ u16 field_0x4; + /* 0x4 */ u16 mWaveId; /* 0x6 */ u16 field_0x6; - /* 0x8 */ f32 field_0x8; - /* 0xC */ f32 field_0xc; + /* 0x8 */ f32 mVolumeMult; + /* 0xC */ f32 mPitchMult; }; JASBasicInst(); @@ -74,7 +74,7 @@ struct JASBasicInst : public JASInst { /* 0x04 */ f32 mVolume; /* 0x08 */ f32 mPitch; - /* 0x0C */ JASOscillator::Data const* field_0xc[OSC_MAX]; + /* 0x0C */ JASOscillator::Data const* mOscillators[OSC_MAX]; /* 0x10 */ u32 mKeymapCount; /* 0x14 */ TKeymap* mKeymap; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index 2310cc590f..f8579c4be3 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -174,7 +174,7 @@ public: struct { u32 mChannelType; // CHANNEL_WAVE or CHANNEL_OSCILLATOR JASWaveInfo mWaveInfo; - } field_0xdc; + } mAnon; union { u32 mWaveAramAddress; u32 mOscillatorSomething; diff --git a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h index 8e5c8df244..f791b21865 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASDrumSet.h @@ -21,10 +21,10 @@ struct JASDrumSet : public JASInst { /* 0x00 */ f32 mVolume; /* 0x04 */ f32 mPitch; /* 0x08 */ f32 mPan; - /* 0x0C */ u16 field_0xc; - /* 0x0E */ u16 field_0xe; - /* 0x10 */ f32 field_0x10; - /* 0x14 */ f32 field_0x14; + /* 0x0C */ u16 mDirectRelease; + /* 0x0E */ u16 mWaveId; + /* 0x10 */ f32 mVolumeMult; + /* 0x14 */ f32 mPitchMult; }; JASDrumSet(); diff --git a/libs/JSystem/src/JAudio2/JASAramStream.cpp b/libs/JSystem/src/JAudio2/JASAramStream.cpp index 2ff00b6a14..74026d94ff 100644 --- a/libs/JSystem/src/JAudio2/JASAramStream.cpp +++ b/libs/JSystem/src/JAudio2/JASAramStream.cpp @@ -722,9 +722,9 @@ void JASAramStream::channelStart() { } jc->setInitPitch(mSampleRate / JASDriver::getDacRate()); jc->setOscInit(0, &OSC_ENV); - jc->field_0xdc.mWaveInfo = wave_info; + jc->mAnon.mWaveInfo = wave_info; jc->mWaveAramAddress = mAramAddress + sBlockSize * mAramBlocksPerChannel * i; - jc->field_0xdc.mChannelType = 0; + jc->mAnon.mChannelType = 0; int ret = jc->playForce(); JUT_ASSERT(977, ret); JUT_ASSERT_MSG(979, mChannels[i] == NULL, "channelStart for already playing channel"); diff --git a/libs/JSystem/src/JAudio2/JASBNKParser.cpp b/libs/JSystem/src/JAudio2/JASBNKParser.cpp index ad8aa1ee46..6fbd3ad5cf 100644 --- a/libs/JSystem/src/JAudio2/JASBNKParser.cpp +++ b/libs/JSystem/src/JAudio2/JASBNKParser.cpp @@ -115,9 +115,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h JASBasicInst::TKeymap* keymap = instp->getKeyRegion(j); keymap->setHighKey(*data >> 0x18); u32 fVar4 = data[1]; - keymap->field_0x4 = JSULoHalf(data[3]); - keymap->field_0x8 = *(BE(f32)*)&data[4]; - keymap->field_0xc = *(BE(f32)*)&data[5]; + keymap->mWaveId = JSULoHalf(data[3]); + keymap->mVolumeMult = *(BE(f32)*)&data[4]; + keymap->mPitchMult = *(BE(f32)*)&data[5]; data += 2; for (int k = 0; k < fVar4; k++) { data += 4; @@ -159,9 +159,9 @@ JASBasicBank* JASBNKParser::Ver1::createBasicBank(void const* stream, JKRHeap* h ptr++; } u32 pVar6 = ptr[0]; - percp->field_0xe = JSULoHalf(ptr[2]); - percp->field_0x10 = *(BE(f32)*)&ptr[3]; - percp->field_0x14 = *(BE(f32)*)&ptr[4]; + percp->mWaveId = JSULoHalf(ptr[2]); + percp->mVolumeMult = *(BE(f32)*)&ptr[3]; + percp->mPitchMult = *(BE(f32)*)&ptr[4]; for (int k = 0; k < pVar6; k++) {} drump->setPerc(j, percp); } @@ -260,9 +260,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h TKeymap* tkeymap = tinst->mKeymapOffset[j].ptr(header); keymap->setHighKey(tkeymap->mHighKey); TVmap* tvmap = tkeymap->mVmapOffset.ptr(header); - keymap->field_0x4 = JSULoHalf(tvmap->field_0x4); - keymap->field_0x8 = tvmap->field_0x8; - keymap->field_0xc = tvmap->field_0xc; + keymap->mWaveId = JSULoHalf(tvmap->field_0x4); + keymap->mVolumeMult = tvmap->field_0x8; + keymap->mPitchMult = tvmap->field_0xc; } bank->setInst(i, instp); @@ -288,9 +288,9 @@ JASBasicBank* JASBNKParser::Ver0::createBasicBank(void const* stream, JKRHeap* h percp->setRelease(tperc->mRelease[j]); } TVmap* vmap = tpmap->mVmapOffset.ptr(header); - percp->field_0xe = JSULoHalf(vmap->field_0x4); - percp->field_0x10 = vmap->field_0x8; - percp->field_0x14 = vmap->field_0xc; + percp->mWaveId = JSULoHalf(vmap->field_0x4); + percp->mVolumeMult = vmap->field_0x8; + percp->mPitchMult = vmap->field_0xc; setp->setPerc(j, percp); } } @@ -314,8 +314,8 @@ JASOscillator::Data* JASBNKParser::Ver0::findOscPtr(JASBasicBank* bank, THeader if (inst != NULL) { JASInstParam param; inst->getParam(0x3c, 0x7f, ¶m); - if (j < param.field_0x1d) { - return param.field_0x14[j]; + if (j < param.mOscillatorCount) { + return param.mOscillators[j]; } } } diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index b85258bb8b..b36b076dc8 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.cpp @@ -11,31 +11,31 @@ using namespace dusk::mods::svc::audio_res; // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4, - void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_6) { - if (param_1 >= 0xf0) { - return noteOnOsc(param_1 - 0xf0, param_2, param_3, param_4, param_5, param_6); +JASChannel* JASBank::noteOn(JASBank const* bank, int program, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { + if (program >= 0xf0) { + return noteOnOsc(program - 0xf0, key, velocity, priority, callback, callback_data); } - if (!param_0) { + if (!bank) { return NULL; } - JASInstParam stack_60; - if (!param_0->getInstParam(param_1, param_2, param_3, &stack_60)) { + JASInstParam instParam; + if (!bank->getInstParam(program, key, velocity, &instParam)) { return NULL; } - JASWaveBank* waveBank = param_0->getWaveBank(); + JASWaveBank* waveBank = bank->getWaveBank(); if (!waveBank) { return NULL; } - JASWaveHandle* waveHandle = waveBank->getWaveHandle(stack_60.field_0x1a); + JASWaveHandle* waveHandle = waveBank->getWaveHandle(instParam.mWaveId); if (!waveHandle) { return NULL; } #if TARGET_PC - auto const key = AudioWaveKey(static_cast(waveBank->bankId), stack_60.field_0x1a); + auto const wave_key = AudioWaveKey(static_cast(waveBank->bankId), instParam.mWaveId); std::lock_guard lock(s_replacements_mutex); - auto const found_replacement = s_replacements.find(key); + auto const found_replacement = s_replacements.find(wave_key); if (found_replacement != s_replacements.end()) { waveHandle = &found_replacement->second; } @@ -52,32 +52,32 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 return NULL; } - JASChannel* channel = JKR_NEW JASChannel(param_5, param_6); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_4); - channel->field_0xdc.mWaveInfo = *waveInfo; + channel->setPriority(priority); + channel->mAnon.mWaveInfo = *waveInfo; channel->mWaveAramAddress = wavePtr; #if TARGET_PC channel->mAramBaseAddress = aramBase; channel->mSampleReference = waveHandle->getSampleReference(); #endif - channel->field_0xdc.mChannelType = stack_60.field_0x1c; - channel->setBankDisposeID(param_0); - channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); - if (stack_60.field_0x1e == 0) { - channel->setKey(param_2 - waveInfo->mBaseKey); + channel->mAnon.mChannelType = instParam.mChannelType; + channel->setBankDisposeID(bank); + channel->setInitPitch(instParam.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate())); + if (instParam.mDontSetKey == 0) { + channel->setKey(key - waveInfo->mBaseKey); } - channel->setInitVolume(stack_60.mVolume); - channel->setVelocity(param_3); - channel->setInitPan(stack_60.mPan); - channel->setInitFxmix(stack_60.mFxMix); - channel->setInitDolby(stack_60.mDolby); - for (u32 i = 0; i < stack_60.field_0x1d; i++) { - channel->setOscInit(i, stack_60.field_0x14[i]); + channel->setInitVolume(instParam.mVolume); + channel->setVelocity(velocity); + channel->setInitPan(instParam.mPan); + channel->setInitFxmix(instParam.mFxMix); + channel->setInitDolby(instParam.mDolby); + for (u32 i = 0; i < instParam.mOscillatorCount; i++) { + channel->setOscInit(i, instParam.mOscillators[i]); } - channel->setDirectRelease(stack_60.field_0x18); + channel->setDirectRelease(instParam.mDirectRelease); if (!channel->play()) { return NULL; } @@ -85,24 +85,24 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 } // NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations -JASChannel* JASBank::noteOnOsc(int param_0, u8 param_1, u8 param_2, u16 param_3, - void (*param_4)(u32, JASChannel*, JASDsp::TChannel*, void*), - void* param_5) { +JASChannel* JASBank::noteOnOsc(int param_0, u8 key, u8 velocity, u16 priority, + void (*callback)(u32, JASChannel*, JASDsp::TChannel*, void*), + void* callback_data) { static JASOscillator::Point const OSC_RELEASE_TABLE[2] = { {0x0001, 0x000A, 0x0000}, {0x000F, 0x0000, 0x0000}, }; static const JASOscillator::Data OSC_ENV = {0, 1.0f, NULL, OSC_RELEASE_TABLE, 1.0f, 0.0f}; - JASChannel* channel = JKR_NEW JASChannel(param_4, param_5); + JASChannel* channel = JKR_NEW JASChannel(callback, callback_data); if (!channel) { return NULL; } - channel->setPriority(param_3); + channel->setPriority(priority); channel->mOscillatorSomething = param_0; - channel->field_0xdc.mChannelType = 2; + channel->mAnon.mChannelType = CHANNEL_OSCILLATOR; channel->setInitPitch(16736.016f / JASDriver::getDacRate()); - channel->setKey(param_1 - channel->field_0xdc.mWaveInfo.mBaseKey); - channel->setVelocity(param_2); + channel->setKey(key - channel->mAnon.mWaveInfo.mBaseKey); + channel->setVelocity(velocity); channel->setOscInit(0, &OSC_ENV); if (!channel->play()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JASBasicBank.cpp b/libs/JSystem/src/JAudio2/JASBasicBank.cpp index 868e7189dd..16fef1d746 100644 --- a/libs/JSystem/src/JAudio2/JASBasicBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicBank.cpp @@ -21,13 +21,13 @@ void JASBasicBank::newInstTable(u8 num, JKRHeap* heap) { } } -bool JASBasicBank::getInstParam(int prg_no, int param_1, int param_2, +bool JASBasicBank::getInstParam(int prg_no, int key, int velocity, JASInstParam* o_param) const { JASInst* inst = getInst(prg_no); if (inst == NULL) { return false; } - return inst->getParam(param_1, param_2, o_param); + return inst->getParam(key, velocity, o_param); } void JASBasicBank::setInst(int prg_no, JASInst* inst) { diff --git a/libs/JSystem/src/JAudio2/JASBasicInst.cpp b/libs/JSystem/src/JAudio2/JASBasicInst.cpp index fba53d010f..50e764eeac 100644 --- a/libs/JSystem/src/JAudio2/JASBasicInst.cpp +++ b/libs/JSystem/src/JAudio2/JASBasicInst.cpp @@ -14,9 +14,9 @@ JASBasicInst::JASBasicInst() { mKeymapCount = 0; mKeymap = NULL; #if TARGET_ANDROID - JASCalc::_bzero(field_0xc, sizeof(field_0xc)); + JASCalc::_bzero(mOscillators, sizeof(mOscillators)); #else - JASCalc::bzero(field_0xc, sizeof(field_0xc)); + JASCalc::bzero(mOscillators, sizeof(mOscillators)); #endif } @@ -24,18 +24,18 @@ JASBasicInst::~JASBasicInst() { JKR_DELETE_ARRAY(mKeymap); } -bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); - param_2->field_0x1c = 0; - param_2->field_0x1e = 0; - param_2->field_0x14 = (JASOscillator::Data**)&field_0xc; - param_2->field_0x1d = 2; - param_2->mVolume = mVolume; - param_2->mPitch = mPitch; +bool JASBasicInst::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); + o_param->mChannelType = 0; + o_param->mDontSetKey = 0; + o_param->mOscillators = (JASOscillator::Data**)&mOscillators; + o_param->mOscillatorCount = 2; + o_param->mVolume = mVolume; + o_param->mPitch = mPitch; TKeymap* keyMap = NULL; for (int i = 0; i < mKeymapCount; i++) { - if (param_0 <= mKeymap[i].mHighKey) { + if (key <= mKeymap[i].mHighKey) { keyMap = &mKeymap[i]; break; } @@ -45,9 +45,9 @@ bool JASBasicInst::getParam(int param_0, int param_1, JASInstParam* param_2) con return false; } - param_2->mVolume *= keyMap->field_0x8; - param_2->mPitch *= keyMap->field_0xc; - param_2->field_0x1a = u16(keyMap->field_0x4); + o_param->mVolume *= keyMap->mVolumeMult; + o_param->mPitch *= keyMap->mPitchMult; + o_param->mWaveId = u16(keyMap->mWaveId); return true; } @@ -61,7 +61,7 @@ void JASBasicInst::setKeyRegionCount(u32 count, JKRHeap* param_1) { void JASBasicInst::setOsc(int index, JASOscillator::Data const* param_1) { JUT_ASSERT(128, index < OSC_MAX); JUT_ASSERT(129, index >= 0); - field_0xc[index] = param_1; + mOscillators[index] = param_1; } JASBasicInst::TKeymap* JASBasicInst::getKeyRegion(int index) { diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index d227ad3fc1..3799832119 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -37,7 +37,7 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) : mKeySweepCount(0), mSkipSamples(0) { - field_0xdc.mChannelType = 0; + mAnon.mChannelType = 0; field_0x104 = 0; mMixConfig[0].whole = 0x150; mMixConfig[1].whole = 0x210; @@ -240,7 +240,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_START, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; @@ -256,9 +256,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) { return -1; } - switch (field_0xdc.mChannelType) { + switch (mAnon.mChannelType) { case 0: - i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples); + i_channel->setWaveInfo(mAnon.mWaveInfo, mWaveAramAddress, mSkipSamples); #if TARGET_PC i_channel->mAramBaseAddress = mAramBaseAddress; #endif @@ -321,7 +321,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) { mCallback(CB_PLAY, this, i_channel, mCallbackData); } - if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) { + if (mAnon.mWaveInfo.mpLoaded[0] == 0) { JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing"); mDspCh->free(); mDspCh = NULL; diff --git a/libs/JSystem/src/JAudio2/JASDrumSet.cpp b/libs/JSystem/src/JAudio2/JASDrumSet.cpp index 399cc117ec..4b6a95b289 100644 --- a/libs/JSystem/src/JAudio2/JASDrumSet.cpp +++ b/libs/JSystem/src/JAudio2/JASDrumSet.cpp @@ -26,8 +26,8 @@ void JASDrumSet::newPercArray(u8 num, JKRHeap* heap) { } } -bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { - UNUSED(param_1); +bool JASDrumSet::getParam(int key, int velocity, JASInstParam* o_param) const { + UNUSED(velocity); JUT_ASSERT(48, key >= 0); if (mPercArray == NULL) { return false; @@ -43,12 +43,12 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { return false; } - param_2->field_0x1c = 0; - param_2->field_0x1e = 1; - param_2->mVolume = perc->mVolume; - param_2->mPitch = perc->mPitch; - param_2->mPan = perc->mPan; - param_2->field_0x18 = u16(perc->field_0xc); + o_param->mChannelType = 0; + o_param->mDontSetKey = 1; + o_param->mVolume = perc->mVolume; + o_param->mPitch = perc->mPitch; + o_param->mPan = perc->mPan; + o_param->mDirectRelease = u16(perc->mDirectRelease); static JASOscillator::Data osc; @@ -61,11 +61,11 @@ bool JASDrumSet::getParam(int key, int param_1, JASInstParam* param_2) const { static JASOscillator::Data* oscp = &osc; - param_2->field_0x14 = &oscp; - param_2->field_0x1d = 1; - param_2->mVolume *= perc->field_0x10; - param_2->mPitch *= perc->field_0x14; - param_2->field_0x1a = u16(perc->field_0xe); + o_param->mOscillators = &oscp; + o_param->mOscillatorCount = 1; + o_param->mVolume *= perc->mVolumeMult; + o_param->mPitch *= perc->mPitchMult; + o_param->mWaveId = u16(perc->mWaveId); return true; } @@ -107,12 +107,12 @@ JASDrumSet::TPerc::TPerc() { mVolume = 1.0f; mPitch = 1.0f; mPan = 0.5f; - field_0xc = 1000; + mDirectRelease = 1000; } void JASDrumSet::TPerc::setRelease(u32 release) { JUT_ASSERT(224, release < 0x10000); - field_0xc = release; + mDirectRelease = release; } u32 JASDrumSet::getType() const { return 'PERC'; } diff --git a/libs/JSystem/src/JAudio2/JASTrack.cpp b/libs/JSystem/src/JAudio2/JASTrack.cpp index 4b1a7ba7aa..32e66e140c 100644 --- a/libs/JSystem/src/JAudio2/JASTrack.cpp +++ b/libs/JSystem/src/JAudio2/JASTrack.cpp @@ -288,13 +288,13 @@ void JASTrack::setLatestKey(u8 param_0) { field_0x22b += getTransposeTotal(); } -JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 param_1, u32 param_2, +JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 key, u32 velocity, u32 i_updateTimer) { JASBank* bank = NULL; if (mBankTable != NULL) { bank = mBankTable->getBank(mBankNumber); } - JASChannel* channel = JASBank::noteOn(bank, mProgNumber, param_1, param_2, + JASChannel* channel = JASBank::noteOn(bank, mProgNumber, key, velocity, mNoteOnPrio | mReleasePrio << 8, channelUpdateCallback, i_channelMgr); if (channel == NULL) { @@ -315,17 +315,17 @@ JASChannel* JASTrack::channelStart(JASTrack::TChannelMgr* i_channelMgr, u32 para return channel; } -int JASTrack::noteOn(u32 noteid, u32 param_1, u32 param_2) { +int JASTrack::noteOn(u32 noteid, u32 key, u32 velocity) { JUT_ASSERT(486, noteid != 0 && noteid < TChannelMgr::CHANNEL_MAX); if (isMute()) { return 0; } int ret = 1; - param_1 += getTransposeTotal(); + key += getTransposeTotal(); for (u32 i = 0; i < mChannelMgrCount; i++) { if (mChannelMgrs[i] != NULL) { mChannelMgrs[i]->noteOff(noteid, 0); - JASChannel* channel = channelStart(mChannelMgrs[i], param_1, param_2, 0); + JASChannel* channel = channelStart(mChannelMgrs[i], key, velocity, 0); if (channel == NULL) { ret = 0; } @@ -356,7 +356,7 @@ int JASTrack::gateOn(u32 param_0, u32 i_velocity, f32 i_time, u32 i_flags) { } else { JASChannel* channel = channel_mgr->mChannels[0]; if (channel != NULL) { - channel->setKey(uvar7 - channel->field_0xdc.mWaveInfo.mBaseKey); + channel->setKey(uvar7 - channel->mAnon.mWaveInfo.mBaseKey); channel->setVelocity(i_velocity); channel->setUpdateTimer(update_timer); } diff --git a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp index 0d3cd3adb2..3ba1dff173 100644 --- a/libs/JSystem/src/JAudio2/JASVoiceBank.cpp +++ b/libs/JSystem/src/JAudio2/JASVoiceBank.cpp @@ -15,9 +15,9 @@ bool JASVoiceBank::getInstParam(int param_0, int param_1, int param_2, return false; } sOscTable = (JASOscillator::Data*)&sOscData; - param_3->field_0x1a = param_0; - param_3->field_0x1d = 1; - param_3->field_0x14 = &sOscTable; + param_3->mWaveId = param_0; + param_3->mOscillatorCount = 1; + param_3->mOscillators = &sOscTable; return true; } From cb09860e1f1743f1456b5b708ad22cb9084eba91 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 2 Aug 2026 16:56:03 +0200 Subject: [PATCH 19/36] BSC ImHex pattern --- docs/imhex/jaudio/bsc.hexpat | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/imhex/jaudio/bsc.hexpat diff --git a/docs/imhex/jaudio/bsc.hexpat b/docs/imhex/jaudio/bsc.hexpat new file mode 100644 index 0000000000..a9f8e3ca6e --- /dev/null +++ b/docs/imhex/jaudio/bsc.hexpat @@ -0,0 +1,26 @@ +#pragma endian big + +#include "std/sys.pat" +#include "type/magic.pat" +#include "std/array.pat" + +/** + * Wrapper for a file-global pointer that allows it to be stored in an array. + */ +struct Offset { + T* offset : u32 [[inline]]; +}; + +struct JAUSeqCollectionTable { + u32 mEntryCount; + u32 mBmsOffset[mEntryCount]; +}; + +struct JAUSeqCollectionData { + type::Magic<"SC"> mMagic; + u16 mNumSoundCategories; + u32 mSectionSize; + Offset mTableOffsets[mNumSoundCategories]; +}; + +JAUSeqCollectionData data @ 0x0; From d4ca7b696bee74d3771c5a4191d9dca1a91d829b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:05:19 +0200 Subject: [PATCH 20/36] Fix include shenanigans --- .../include/JSystem/JAudio2/JASChannel.h | 12 +----------- .../JSystem/JAudio2/JASSampleDataReference.h | 18 ++++++++++++++++++ .../include/JSystem/JAudio2/JASWaveInfo.h | 2 +- src/dusk/mods/svc/audio_res/audio_res.hpp | 1 + 4 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h diff --git a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h index f8579c4be3..77bfb45966 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASChannel.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASChannel.h @@ -17,17 +17,7 @@ namespace JASDsp { } #if TARGET_PC -/** - * An object to manage the lifetime of the sample data pointed to by JASChannel. - * - * JASChannel can (optionally) accept an unique_ptr to an object to this type, - * in which case it will be destroyed when the JASChannel is done. - * - * @see JASChannel::mSampleReference - */ -struct JASSampleDataReference { - virtual ~JASSampleDataReference() = default; -}; +#include "JSystem/JAudio2/JASSampleDataReference.h" #endif /** diff --git a/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h new file mode 100644 index 0000000000..d196b4f34c --- /dev/null +++ b/libs/JSystem/include/JSystem/JAudio2/JASSampleDataReference.h @@ -0,0 +1,18 @@ +#ifndef DUSKLIGHT_JASSAMPLEDATAREFERENCE_H +#define DUSKLIGHT_JASSAMPLEDATAREFERENCE_H + +#if TARGET_PC +/** + * An object to manage the lifetime of the sample data pointed to by JASChannel. + * + * JASChannel can (optionally) accept an unique_ptr to an object to this type, + * in which case it will be destroyed when the JASChannel is done. + * + * @see JASChannel::mSampleReference + */ +struct JASSampleDataReference { + virtual ~JASSampleDataReference() = default; +}; +#endif + +#endif // DUSKLIGHT_JASSAMPLEDATAREFERENCE_H diff --git a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h index 45874f5aa7..a092f7450e 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JASWaveInfo.h @@ -5,7 +5,7 @@ #include #if TARGET_PC -struct JASSampleDataReference; +#include "JSystem/JAudio2/JASSampleDataReference.h" #endif struct JASWaveArc; diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index 4ae3e0dd1c..ce46db0721 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASWaveInfo.h" #include "absl/container/flat_hash_map.h" From 756f8d5fb9fff0baaa970563f437d1e562f687f4 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:21:13 +0200 Subject: [PATCH 21/36] Move wsys replacements into their own .cpp file This is gonna be a big PR --- files.cmake | 2 + libs/JSystem/src/JAudio2/JASBank.cpp | 4 +- src/dusk/mods/svc/audio_res/audio_res.cpp | 258 ++------------------- src/dusk/mods/svc/audio_res/audio_res.hpp | 97 -------- src/dusk/mods/svc/audio_res/opus.cpp | 6 +- src/dusk/mods/svc/audio_res/wave.cpp | 6 +- src/dusk/mods/svc/audio_res/wsys.cpp | 260 ++++++++++++++++++++++ src/dusk/mods/svc/audio_res/wsys.hpp | 120 ++++++++++ 8 files changed, 404 insertions(+), 349 deletions(-) create mode 100644 src/dusk/mods/svc/audio_res/wsys.cpp create mode 100644 src/dusk/mods/svc/audio_res/wsys.hpp diff --git a/files.cmake b/files.cmake index af29738867..90a0dcd4f8 100644 --- a/files.cmake +++ b/files.cmake @@ -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 diff --git a/libs/JSystem/src/JAudio2/JASBank.cpp b/libs/JSystem/src/JAudio2/JASBank.cpp index b36b076dc8..0acc5b9ed4 100644 --- a/libs/JSystem/src/JAudio2/JASBank.cpp +++ b/libs/JSystem/src/JAudio2/JASBank.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, diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index dc7b2337fd..28a18453ca 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -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 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(std::move(pcm16)); - } else { - slot.data = std::make_shared(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(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 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 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 AudioWaveReplacementValue::getSampleReference() const { - return std::make_unique(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 }; }; diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index ce46db0721..5b2ae0a87a 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,106 +1,9 @@ #pragma once -#include -#include -#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 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 -// because I don't want aliasing or alignment to eat my face. -struct SampleDataPcm16 : SampleDataBuf { - std::vector data; - - [[nodiscard]] std::span get_data() const override { - return as_bytes(std::span(data)); - } - - void be_swap(); -}; - -struct SampleDataU8 : SampleDataBuf { - std::vector data; - - explicit SampleDataU8(std::vector data) : data(std::move(data)) {} - - [[nodiscard]] std::span get_data() const override { - return as_bytes(std::span(data)); - } -}; - -struct SampleReference : JASSampleDataReference { - explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} - - std::shared_ptr data; -}; - -struct AudioWaveKey { - AudioWaveBank bank; - u32 wave_id; - - AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; - - template - 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 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 getSampleReference() const override; - - JASWaveInfo wave_info; - std::shared_ptr data; -}; - -extern absl::flat_hash_map 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 data; -}; - -using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); - -ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); - } diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp index 3541f6be81..aa4e6bea99 100644 --- a/src/dusk/mods/svc/audio_res/opus.cpp +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -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 fileData) { OpusHead head; const int result = op_test(&head, fileData.data(), fileData.size()); diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index 69e1e2e10f..ae97a8f81b 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -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 fileData) { using namespace helpers::alignment; diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp new file mode 100644 index 0000000000..5abe9cea71 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -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 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(std::move(pcm16)); + } else { + slot.data = std::make_shared(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(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 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 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 AudioWaveReplacementValue::getSampleReference() const { + return std::make_unique(data); +} + +void SampleDataPcm16::be_swap() { + for (auto& sample : data) { + ::be_swap(sample); + } +} + + +} \ No newline at end of file diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp new file mode 100644 index 0000000000..76b966f65a --- /dev/null +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#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 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 +// because I don't want aliasing or alignment to eat my face. +struct SampleDataPcm16 : SampleDataBuf { + std::vector data; + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } + + void be_swap(); +}; + +struct SampleDataU8 : SampleDataBuf { + std::vector data; + + explicit SampleDataU8(std::vector data) : data(std::move(data)) {} + + [[nodiscard]] std::span get_data() const override { + return as_bytes(std::span(data)); + } +}; + +struct SampleReference : JASSampleDataReference { + explicit SampleReference(std::shared_ptr data) : data(std::move(data)) {} + + std::shared_ptr data; +}; + +struct AudioWaveKey { + AudioWaveBank bank; + u32 wave_id; + + AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {}; + + template + 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 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 getSampleReference() const override; + + JASWaveInfo wave_info; + std::shared_ptr data; +}; + +extern absl::flat_hash_map 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 data; +}; + +using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); + +ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span 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); + +} \ No newline at end of file From 2fca4631397d7834b787eb6bb4de8bf51492c775 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 6 Aug 2026 01:55:16 +0200 Subject: [PATCH 22/36] Allow allocating new audio waves ...probably. I have no means to test this code atm. --- files.cmake | 2 + sdk/include/mods/svc/audio_res.h | 8 ++ src/dusk/mods/svc/audio_res/audio_res.cpp | 1 + src/dusk/mods/svc/audio_res/wsys.cpp | 100 +++++++++++++++++----- src/dusk/mods/svc/audio_res/wsys.hpp | 9 ++ src/dusk/mods/svc/id_allocator.cpp | 9 ++ src/dusk/mods/svc/id_allocator.hpp | 43 ++++++++++ 7 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 src/dusk/mods/svc/id_allocator.cpp create mode 100644 src/dusk/mods/svc/id_allocator.hpp diff --git a/files.cmake b/files.cmake index 90a0dcd4f8..0f915e5abe 100644 --- a/files.cmake +++ b/files.cmake @@ -1498,6 +1498,8 @@ set(DUSK_FILES src/dusk/mods/svc/gfx.cpp src/dusk/mods/svc/hook.cpp src/dusk/mods/svc/host.cpp + src/dusk/mods/svc/id_allocator.cpp + src/dusk/mods/svc/id_allocator.hpp src/dusk/mods/svc/log.cpp src/dusk/mods/svc/overlay.cpp src/dusk/mods/svc/registry.cpp diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index 0d126c1912..b5af109959 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -52,6 +52,14 @@ typedef struct AudioResService { AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); + ModResult (*add_wave)( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + uint16_t* out_wave_id); + ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle 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 28a18453ca..a63eb4e3ce 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -16,6 +16,7 @@ using namespace dusk::helpers::cast; constexpr AudioResService s_audioResService{ .header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR), .replace_wave = &wsys::insert_replace_wave, + .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, }; diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 5abe9cea71..266c0ca55e 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -1,10 +1,9 @@ #include "wsys.hpp" -#include "../registry.hpp" #include "../slot_map.hpp" +#include "../id_allocator.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 { @@ -26,6 +25,12 @@ constexpr ContainerLoadFunction s_containerLoadFunctions[] = { #endif }; +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; +} 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; @@ -122,32 +127,25 @@ JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { } 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 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; + auto const found = s_waveReplacements.find_owned(handle, mod); + if (found == nullptr) { + return false; } - if (!wave_remove(*mod, handle)) { - Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle); - return MOD_INVALID_ARGUMENT; + + if (found->value.mod_defined) { + auto& allocator = id_allocator_for_bank(found->value.bank); + allocator.free(found->value.wave_id); } - return MOD_OK; + + s_waveReplacements.erase_owned(handle, mod); + return true; } -ModResult insert_replace_wave( +ModResult insert_replace_wave_core( ModContext* ctx, AudioWaveBank bank, u16 wave_id, + bool mod_defined, char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle) { @@ -164,6 +162,7 @@ ModResult insert_replace_wave( slot.bundle_path = file_name; slot.bank = bank; slot.wave_id = wave_id; + slot.mod_defined = mod_defined; ModResult result; if (wave_info && wave_info->raw_wave) { @@ -197,6 +196,65 @@ ModResult insert_replace_wave( return MOD_OK; } + +} + +absl::flat_hash_map 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) { + + return insert_replace_wave_core(ctx, bank, wave_id, false, file_name, wave_info, out_handle); +} + +ModResult insert_add_wave( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + u16* out_wave_id) { + + if (out_wave_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + *out_wave_id = 0; + + if (bank != SoundEffects && bank != MusicSamples) { + return MOD_INVALID_ARGUMENT; + } + + auto& allocator = id_allocator_for_bank(bank); + auto const new_wave_id = allocator.alloc(); + + auto const result = insert_replace_wave_core(ctx, bank, new_wave_id, true, file_name, wave_info, out_handle); + if (result != MOD_OK) { + allocator.free(new_wave_id); + } + + *out_wave_id = new_wave_id; + return result; +} + void remove_mod(LoadedMod& mod) { s_waveReplacements.erase_all(mod); diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index 76b966f65a..16d144e4b3 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -82,6 +82,7 @@ extern std::mutex s_replacements_mutex; struct RuntimeWaveReplacementSlot { std::string bundle_path; AudioWaveBank bank; + bool mod_defined; u16 wave_id; u8 base_key; @@ -115,6 +116,14 @@ ModResult insert_replace_wave( AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); +ModResult insert_add_wave( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + u16* out_wave_id); + ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); } \ No newline at end of file diff --git a/src/dusk/mods/svc/id_allocator.cpp b/src/dusk/mods/svc/id_allocator.cpp new file mode 100644 index 0000000000..3778bc7b81 --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.cpp @@ -0,0 +1,9 @@ +#include "id_allocator.hpp" + +namespace dusk::mods::svc { + +void id_allocator_exhausted() { + CRASH("ID allocator exhausted!"); +} + +} // namespace dusk::mods::svc \ No newline at end of file diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp new file mode 100644 index 0000000000..b336e0fbfa --- /dev/null +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +namespace dusk::mods::svc { + +[[noreturn]] void id_allocator_exhausted(); + +template requires std::is_integral_v +class PlainIdAllocator { + std::vector reusable; + T alloc_next; + T alloc_max; + +public: + explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) + : alloc_next(first), alloc_max(max) { + + } + + T alloc() { + if (reusable.empty()) { + if (alloc_next == alloc_max) { + id_allocator_exhausted(); + } + + return alloc_next++; + } + + auto val = reusable.back(); + reusable.pop_back(); + return val; + } + + void free(T value) { + assert(value < alloc_max && value < alloc_next); + + reusable.push_back(value); + } +}; + +} \ No newline at end of file From 2bc53dddcca315ed1fd4cbe976ab6bdf15218896 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 24 Aug 2026 21:04:16 +0200 Subject: [PATCH 23/36] BST SE/STREAM replacement support No SEQ yet since idk how I wanna handle it. --- files.cmake | 2 + include/Z2AudioLib/Z2Audience.h | 3 +- include/Z2AudioLib/Z2SoundInfo.h | 22 +- include/Z2AudioLib/Z2SoundMgr.h | 2 +- .../include/JSystem/JAudio2/JAIAudience.h | 7 +- libs/JSystem/include/JSystem/JAudio2/JAISe.h | 2 +- .../include/JSystem/JAudio2/JAISeMgr.h | 2 +- .../include/JSystem/JAudio2/JAISound.h | 27 +- .../include/JSystem/JAudio2/JAISoundInfo.h | 10 +- .../include/JSystem/JAudio2/JAIStream.h | 2 +- .../JSystem/JAudio2/JAIStreamDataMgr.h | 4 +- .../include/JSystem/JAudio2/JAIStreamMgr.h | 2 +- .../include/JSystem/JAudio2/JAUSoundInfo.h | 4 +- .../include/JSystem/JAudio2/JAUSoundTable.h | 25 +- .../JSystem/JAudio2/JAUStreamFileTable.h | 2 +- libs/JSystem/src/JAudio2/JAISe.cpp | 5 +- libs/JSystem/src/JAudio2/JAISeMgr.cpp | 9 +- libs/JSystem/src/JAudio2/JAISeq.cpp | 2 +- libs/JSystem/src/JAudio2/JAISound.cpp | 7 +- libs/JSystem/src/JAudio2/JAIStream.cpp | 5 +- libs/JSystem/src/JAudio2/JAIStreamMgr.cpp | 8 +- libs/JSystem/src/JAudio2/JAUSectionHeap.cpp | 7 +- libs/JSystem/src/JAudio2/JAUSoundTable.cpp | 16 +- .../src/JAudio2/JAUStreamFileTable.cpp | 7 +- mods/audio_mod/CMakeLists.txt | 7 +- mods/audio_mod/src/mod.cpp | 10 + sdk/include/mods/svc/audio_res.h | 96 +++++ src/Z2AudioLib/Z2Audience.cpp | 5 +- src/Z2AudioLib/Z2SceneMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundHandles.cpp | 4 +- src/Z2AudioLib/Z2SoundInfo.cpp | 144 ++++--- src/Z2AudioLib/Z2SoundMgr.cpp | 44 ++- src/Z2AudioLib/Z2SoundObject.cpp | 6 +- src/dusk/mods/svc/audio_res/audio_res.cpp | 9 + src/dusk/mods/svc/audio_res/bst.cpp | 350 ++++++++++++++++++ src/dusk/mods/svc/audio_res/bst.hpp | 99 +++++ src/dusk/mods/svc/id_allocator.hpp | 2 +- 37 files changed, 832 insertions(+), 128 deletions(-) create mode 100644 src/dusk/mods/svc/audio_res/bst.cpp create mode 100644 src/dusk/mods/svc/audio_res/bst.hpp diff --git a/files.cmake b/files.cmake index 0f915e5abe..443b060eeb 100644 --- a/files.cmake +++ b/files.cmake @@ -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/bst.cpp + src/dusk/mods/svc/audio_res/bst.hpp src/dusk/mods/svc/audio_res/wsys.cpp src/dusk/mods/svc/audio_res/wsys.hpp src/dusk/mods/svc/audio_res/wave.cpp diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index b55b7a7424..2746dfa889 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -266,7 +266,8 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance { virtual ~Z2Audience(); virtual JAIAudible* newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3*, u32); + const JGeometry::TVec3*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)); virtual int getMaxChannels(); virtual void deleteAudible(JAIAudible* audible); virtual u32 calcPriority(JAIAudible* audible); diff --git a/include/Z2AudioLib/Z2SoundInfo.h b/include/Z2AudioLib/Z2SoundInfo.h index e2fae177a3..404e0ad5df 100644 --- a/include/Z2AudioLib/Z2SoundInfo.h +++ b/include/Z2AudioLib/Z2SoundInfo.h @@ -6,23 +6,29 @@ #include "JSystem/JAudio2/JAUSoundInfo.h" #include "JSystem/JAudio2/JAUSoundTable.h" +#define STRM_CH_SHIFT_ 2 + +#define STRM_CH_CENTER 1 +#define STRM_CH_LEFT 2 +#define STRM_CH_RIGHT 3 + class Z2SoundInfo : public JAISoundInfo, public JAUSoundInfo, public JAIStreamDataMgr, public JASGlobalInstance { public: Z2SoundInfo() : JAISoundInfo(true), JAUSoundInfo(true), JASGlobalInstance(true) {} - virtual u16 getAudibleSw(JAISoundID soundID) const; + virtual u16 getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; virtual u16 getBgmSeqResourceID(JAISoundID soundID) const; - virtual s32 getStreamFileEntry(JAISoundID soundID); + virtual s32 getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); virtual int getSoundType(JAISoundID soundID) const; virtual int getCategory(JAISoundID soundID) const; - virtual u32 getPriority(JAISoundID soundID) const; - virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr) const; + virtual u32 getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; + virtual void getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const; virtual void getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const; - virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const; + virtual void getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const; virtual ~Z2SoundInfo() {} - JAUAudibleParam getAudibleSwFull(JAISoundID soundID); - const char* getStreamFilePath(JAISoundID soundID); - int getSwBit(JAISoundID soundID) const; + JAUAudibleParam getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)); + const char* getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)); + int getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const; void getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const; BOOL isValid() const { diff --git a/include/Z2AudioLib/Z2SoundMgr.h b/include/Z2AudioLib/Z2SoundMgr.h index cd6341b2cb..82486ed5bc 100644 --- a/include/Z2AudioLib/Z2SoundMgr.h +++ b/include/Z2AudioLib/Z2SoundMgr.h @@ -42,7 +42,7 @@ private: /* 0x004 */ JAISeMgr seMgr_; /* 0x728 */ JAISeqMgr seqMgr_; /* 0x79C */ JAIStreamMgr streamMgr_; - /* 0x80C */ JAISoundID soundID_; + /* 0x80C */ JAISoundID bgmMuter; }; // Size: 0x810 #if VERSION != VERSION_SHIELD_DEBUG diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h index 1b565a069e..20d700097c 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIAudience.h @@ -3,6 +3,10 @@ #include "JSystem/JGeometry.h" +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +} + class JAIAudible; class JAISoundID; struct JASSoundParams; @@ -14,7 +18,8 @@ struct JASSoundParams; struct JAIAudience { virtual ~JAIAudience(); virtual JAIAudible* newAudible(JGeometry::TVec3 const&, JAISoundID, - JGeometry::TVec3 const*, u32) = 0; + JGeometry::TVec3 const*, u32 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const*)) = 0; virtual int getMaxChannels() = 0; virtual void deleteAudible(JAIAudible*) = 0; virtual u32 calcPriority(JAIAudible*) = 0; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISe.h b/libs/JSystem/include/JSystem/JAudio2/JAISe.h index 408a84703a..2ff990bec0 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISe.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISe.h @@ -43,7 +43,7 @@ public: void startTrack_(const JASSoundParams& params); void JAISeCategoryMgr_mixOut_(bool, const JASSoundParams& params, JAISoundActivity activity); void JAISeCategoryMgr_calc_(); - void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_getSeqData_(); void prepare_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h index 928d85525b..57fdbd114a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISeMgr.h @@ -104,7 +104,7 @@ public: JAISe* newSe_(int category, u32 priority); void calc(); void mixOut(); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); int getNumActiveSe() const; /* 0x004 */ JAISoundActivity mSoundActivity; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISound.h b/libs/JSystem/include/JSystem/JAudio2/JAISound.h index 4de69b8416..9dbd84d35a 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISound.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISound.h @@ -6,7 +6,16 @@ #include "JSystem/JUtility/JUTAssert.h" #include "global.h" #include "helpers/endian.h" -#include + +#if TARGET_PC +#include + +namespace dusk::mods::svc::audio_res::bst { +struct SoundTableReplacementSlot; +struct SoundEffectReplacementSlot; +struct StreamReplacementSlot; +} +#endif class JAISound; @@ -271,10 +280,16 @@ class JAITempoMgr; */ class JAISound { public: +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; + using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot; + using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot; +#endif + void releaseHandle(); void attachHandle(JAISoundHandle* handle); JAISound(); - void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience); + void start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)); bool acceptsNewAudible() const; void newAudible(const JGeometry::TVec3&, JGeometry::TVec3 const*, u32, JAIAudience*); @@ -297,6 +312,11 @@ public: virtual bool JAISound_tryDie_() = 0; JAISoundID getID() const { return soundID_; } +#if TARGET_PC + SoundTableReplacementSlot* getReplacement() const { + return replacement.get(); + } +#endif u32 getAnimationState() const { return status_.state.flags.animationState; } bool isAnimated() const { return getAnimationState() != 0; } void setAnimationState(u32 state) { @@ -380,6 +400,9 @@ public: /* 0x34 */ u32 priority_; /* 0x38 */ s32 count_; /* 0x3C */ JAISoundParams params_; +#if TARGET_PC + std::shared_ptr replacement; +#endif }; // Size: 0x98 STATIC_ASSERT(sizeof(JAISound) == 0x98); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h index 4f536a0f63..23713d96a9 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISoundInfo.h @@ -9,13 +9,17 @@ * */ struct JAISoundInfo : public JASGlobalInstance { + IF_DUSK(using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + IF_DUSK(using SoundEffectReplacementSlot = dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot); + IF_DUSK(using StreamReplacementSlot = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + JAISoundInfo(bool); virtual int getSoundType(JAISoundID) const = 0; virtual int getCategory(JAISoundID) const = 0; - virtual u32 getPriority(JAISoundID) const = 0; - virtual void getSeInfo(JAISoundID, JAISe*) const = 0; + virtual u32 getPriority(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const = 0; + virtual void getSeInfo(JAISoundID, JAISe* IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const = 0; virtual void getSeqInfo(JAISoundID, JAISeq*) const = 0; - virtual void getStreamInfo(JAISoundID, JAIStream*) const = 0; + virtual void getStreamInfo(JAISoundID, JAIStream* IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const = 0; virtual ~JAISoundInfo(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h index d6d6694be0..31ef4da4f5 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStream.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStream.h @@ -20,7 +20,7 @@ public: JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* soundStrategyMgr); void JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category); + int category IF_DUSK_ARG(std::shared_ptr replacement)); bool prepare_prepareStream_(); void prepare_(); void prepare_startStream_(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h index eee03cc168..d528a25f85 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamDataMgr.h @@ -8,7 +8,9 @@ * */ struct JAIStreamDataMgr { - virtual s32 getStreamFileEntry(JAISoundID) = 0; + IF_DUSK(using StreamReplacementSlot2 = dusk::mods::svc::audio_res::bst::StreamReplacementSlot); + + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)) = 0; virtual ~JAIStreamDataMgr(); }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h index 7c8e68a852..27d69d57df 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAIStreamMgr.h @@ -15,7 +15,7 @@ struct JAIStreamDataMgr; class JAIStreamMgr : public JASGlobalInstance { public: JAIStreamMgr(bool setInstance); - bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr); + bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)); void freeDeadStream_(); void calc(); void stop(); diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h index 86fa88f913..4083b4e774 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundInfo.h @@ -10,8 +10,10 @@ */ class JAUSoundInfo : public JASGlobalInstance { public: + IF_DUSK(using SoundTableReplacementSlot2 = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot); + JAUSoundInfo(bool param_0) : JASGlobalInstance(param_0) {} - virtual u16 getAudibleSw(JAISoundID) const = 0; + virtual u16 getAudibleSw(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot2 const*)) const = 0; virtual u16 getBgmSeqResourceID(JAISoundID) const = 0; }; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 40ac65bf45..4a4ca33214 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -51,6 +51,11 @@ */ #define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + /** * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK */ @@ -82,10 +87,21 @@ */ #define SOUND_SW_IGNORE_DOLBY 0x0000'2000 +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + /** * 3-bit mask used to select a volume distance/falloff class for this sound. */ #define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 /** * Limit minimum volume of this sound (after distance falloff) to 0.2. @@ -97,6 +113,7 @@ * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. */ #define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 /** * Mark sound as "far away" or "culled" when at max distance (selected by distance class). @@ -290,13 +307,17 @@ struct JAUSoundTableGroup { * */ struct JAUSoundTable : public JASGlobalInstance { +#if TARGET_PC + using SoundTableReplacementSlot = dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot; +#endif + JAUSoundTable(bool setInstance) : JASGlobalInstance(setInstance) { } ~JAUSoundTable() {} void init(void const*); - u8 getTypeID(JAISoundID) const; - JAUSoundTableItem* getData(JAISoundID) const; + u8 getTypeID(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; + JAUSoundTableItem DUSK_CONST* getData(JAISoundID IF_DUSK_ARG(SoundTableReplacementSlot const*)) const; int getNumGroups_inSection(u8) const; int getNumItems_inGroup(u8, u8) const; diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h index 65eef3f8b9..1f29d54f9d 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUStreamFileTable.h @@ -29,7 +29,7 @@ struct JAUStreamFileTable { * */ struct JAUStreamDataMgr_StreamFileTable : public JAIStreamDataMgr, JAUStreamFileTable { - virtual s32 getStreamFileEntry(JAISoundID); + virtual s32 getStreamFileEntry(JAISoundID IF_DUSK_ARG(StreamReplacementSlot2 const*)); virtual ~JAUStreamDataMgr_StreamFileTable() {} }; diff --git a/libs/JSystem/src/JAudio2/JAISe.cpp b/libs/JSystem/src/JAudio2/JAISe.cpp index eb8ea7b90e..16f3c284da 100644 --- a/libs/JSystem/src/JAudio2/JAISe.cpp +++ b/libs/JSystem/src/JAudio2/JAISe.cpp @@ -3,6 +3,7 @@ #include "JSystem/JAudio2/JAISe.h" #include "JSystem/JAudio2/JAIAudience.h" #include "JSystem/JAudio2/JAISeMgr.h" +#include "dusk/mods/svc/audio_res/bst.hpp" JAISe::JAISe(JAISeMgr* seMgr, JAISoundStrategyMgr* soundStrategyMgr, u32 priority) : JSULink(this) { inner_.mSoundStrategyMgr = soundStrategyMgr; @@ -129,10 +130,10 @@ bool JAISe::JAISound_tryDie_() { return false; } -void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISe::JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(221, inner_.track.getStatus() == JASTrack::STATUS_FREE); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); inner_.field_0x26c = 0; if (inner_.mSoundStrategyMgr) { inner_.field_0x278 = inner_.mSoundStrategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISeMgr.cpp b/libs/JSystem/src/JAudio2/JAISeMgr.cpp index 25ed54ca39..825ac8d78e 100644 --- a/libs/JSystem/src/JAudio2/JAISeMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeMgr.cpp @@ -4,6 +4,7 @@ #include "JSystem/JAudio2/JAISoundHandles.h" #include "JSystem/JAudio2/JAISoundInfo.h" #include "JSystem/JAudio2/JASReport.h" +#include "dusk/mods/svc/audio_res/bst.hpp" bool JAISeCategoryMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) { { @@ -284,7 +285,7 @@ void JAISeMgr::mixOut() { } } -bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { if (handle && *handle) { (*handle)->stop(); } @@ -294,7 +295,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry u32 category; if (soundInfoPtr != NULL) { category = soundInfoPtr->getCategory(id); - priority = soundInfoPtr->getPriority(id); + priority = soundInfoPtr->getPriority(id IF_DUSK_ARG(replacement.get())); } else { category = id.id_.info.type.parts.groupID; priority = 0; @@ -309,10 +310,10 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry } JAIAudience* audiencePtr = getAudience(category); - se->JAISeMgr_startID_(id, posPtr, audiencePtr); + se->JAISeMgr_startID_(id, posPtr, audiencePtr IF_DUSK_ARG(replacement)); if (soundInfoPtr != NULL) { - soundInfoPtr->getSeInfo(id, se); + soundInfoPtr->getSeInfo(id, se IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAISeq.cpp b/libs/JSystem/src/JAudio2/JAISeq.cpp index c05494b82e..36895d2d63 100644 --- a/libs/JSystem/src/JAudio2/JAISeq.cpp +++ b/libs/JSystem/src/JAudio2/JAISeq.cpp @@ -28,7 +28,7 @@ void JAISeq::JAISeqMgr_startID_(JAISoundID id, const JGeometry::TVec3* posP JAIAudience* audience, int category, int param_4) { inner_.field_0x39c = category; reserveChildTracks_(param_4); - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(nullptr)); if (inner_.strategyMgr) { field_0x3a8 = inner_.strategyMgr->calc(id); diff --git a/libs/JSystem/src/JAudio2/JAISound.cpp b/libs/JSystem/src/JAudio2/JAISound.cpp index 6dec60c39d..78141503ed 100644 --- a/libs/JSystem/src/JAudio2/JAISound.cpp +++ b/libs/JSystem/src/JAudio2/JAISound.cpp @@ -63,7 +63,7 @@ void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* o JAISound::JAISound() : params_() {} -void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience) { +void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr replacement)) { handle_ = NULL; soundID_ = id; status_.init(); @@ -72,9 +72,10 @@ void JAISound::start_JAISound_(JAISoundID id, const JGeometry::TVec3* posPt audience_ = audience; prepareCount_ = 0; count_ = 0; + IF_DUSK(this->replacement = std::move(replacement)); if (posPtr != NULL && audience_ != NULL) { - audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0); + audible_ = audience_->newAudible(*posPtr, soundID_, NULL, 0 IF_DUSK_ARG(getReplacement())); } else { audible_ = NULL; } @@ -98,7 +99,7 @@ void JAISound::newAudible(const JGeometry::TVec3& pos, } JUT_ASSERT(157, audience_); - audible_ = audience_->newAudible(pos, soundID_, param_1, param_2); + audible_ = audience_->newAudible(pos, soundID_, param_1, param_2 IF_DUSK_ARG(getReplacement())); } void JAISound::stop(u32 fadeTime) { diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 5799080b12..e75292a34b 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -5,6 +5,7 @@ #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStreamDataMgr.h" #include "JSystem/JAudio2/JAIAudience.h" +#include "dusk/mods/svc/audio_res/bst.hpp" static void JAIStream_JASAramStreamCallback_(u32 type, JASAramStream* aramStream, void* userData) { JAIStream* stream = (JAIStream*)userData; @@ -34,10 +35,10 @@ JAIStream::JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* so void JAIStream::JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, - int category) { + int category IF_DUSK_ARG(std::shared_ptr replacement)) { field_0x298 = category; field_0x294 = streamFileEntry; - start_JAISound_(id, posPtr, audience); + start_JAISound_(id, posPtr, audience IF_DUSK_ARG(std::static_pointer_cast(replacement))); field_0x290 = 0; if (field_0x2c0 != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp index 2baafdf24a..ca982bafc7 100644 --- a/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAIStreamMgr.cpp @@ -14,13 +14,13 @@ JAIStreamMgr::JAIStreamMgr(bool setInstance) : JASGlobalInstance(s mActivity.init(); } -bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { +bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3* posPtr IF_DUSK_ARG(std::shared_ptr replacement)) { JUT_ASSERT(37, streamDataMgr_); if (handle != NULL && *handle) { (*handle)->stop(); } - s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id); + s32 streamFileEntry = streamDataMgr_->getStreamFileEntry(id IF_DUSK_ARG(replacement.get())); if (streamFileEntry < 0) { JUT_WARN(46, "Cannot find the stream file entry for ID:%08x\n", id.id_.composite_) return false; @@ -38,9 +38,9 @@ bool JAIStreamMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeom return false; } - stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category); + stream->JAIStreamMgr_startID_(id, streamFileEntry, posPtr, mAudience, category IF_DUSK_ARG(replacement)); if (soundInfo != NULL) { - soundInfo->getStreamInfo(id, stream); + soundInfo->getStreamInfo(id, stream IF_DUSK_ARG(replacement.get())); } if (handle != NULL) { diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp index cc94097712..fe12281a01 100644 --- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp +++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp @@ -47,7 +47,12 @@ namespace { mStreamFileDVDEntryNums[i] = DVDConvertPathToEntrynum(stack_14.getFilePath(i)); } } - virtual s32 getStreamFileEntry(JAISoundID id) { + virtual s32 getStreamFileEntry(JAISoundID id IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("TStreamDataMgr::getStreamFileEntry not implemented for replacements"); + } +#endif u32 short_id = id.id_.info.waveID; if (short_id >= mNumStreamFiles) { return -1; diff --git a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp index cab2e45a3c..802b1c3cb9 100644 --- a/libs/JSystem/src/JAudio2/JAUSoundTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUSoundTable.cpp @@ -6,6 +6,8 @@ #include "JSystem/JAudio2/JAUSoundTable.h" +#include "dusk/mods/svc/audio_res/bst.hpp" + void JAUSoundTable::init(void const* param_0) { if (param_0 != NULL) { field_0x0.init(param_0); @@ -14,11 +16,16 @@ void JAUSoundTable::init(void const* param_0) { } } -u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { +u8 JAUSoundTable::getTypeID(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return 0xff; } +#endif +#if TARGET_PC + if (replacement) { + return replacement->get_type_id(); + } #endif if (param_0.isAnonymous()) { return 0xff; @@ -34,11 +41,16 @@ u8 JAUSoundTable::getTypeID(JAISoundID param_0) const { return group->getTypeID(param_0.id_.info.waveID); } -JAUSoundTableItem* JAUSoundTable::getData(JAISoundID param_0) const { +JAUSoundTableItem DUSK_CONST* JAUSoundTable::getData(JAISoundID param_0 IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { #if DUSK_AUDIO_DISABLED if (this == NULL) { return NULL; } +#endif +#if TARGET_PC + if (replacement) { + return &replacement->item; + } #endif if (param_0.isAnonymous()) { return NULL; diff --git a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp index f30c5c4a01..7f37f21f3e 100644 --- a/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp +++ b/libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp @@ -31,7 +31,12 @@ const char* JAUStreamFileTable::getFilePath(int index) const { return (char*)(data + *(int*)(data + 8 + (index * sizeof(s32)))); } -s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId) { +s32 JAUStreamDataMgr_StreamFileTable::getStreamFileEntry(JAISoundID soundId IF_DUSK_ARG(StreamReplacementSlot2 const* replacement)) { +#if TARGET_PC + if (replacement) { + CRASH("JAUStreamDataMgr_StreamFileTable::getStreamFileEntry not implemented for replacements"); + } +#endif const char* filePath = getFilePath(soundId.id_.info.waveID); if (filePath == NULL) { return -1; diff --git a/mods/audio_mod/CMakeLists.txt b/mods/audio_mod/CMakeLists.txt index 083a348ed2..cc882572b8 100644 --- a/mods/audio_mod/CMakeLists.txt +++ b/mods/audio_mod/CMakeLists.txt @@ -13,7 +13,8 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) endif () add_mod(audio_mod - SOURCES src/mod.cpp - MOD_JSON mod.json - RES_DIR res + SOURCES src/mod.cpp + MOD_JSON mod.json + RES_DIR res + OVERLAY_DIR overlay ) diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp index a68e6bb98e..aa3da367ec 100644 --- a/mods/audio_mod/src/mod.cpp +++ b/mods/audio_mod/src/mod.cpp @@ -25,6 +25,16 @@ MOD_EXPORT ModResult mod_initialize(ModError*) { "res/go.opus", nullptr, &handle); + + constexpr AudioSoundTableEffectInfo effect(128, 1, 1.5); + + svc_audio_res->replace_sound_table_effect( + mod_ctx, + SE_CATEGORY_CHARA_SE, + 0, // MIDNA_APPEAR + &effect, + nullptr); + return MOD_OK; } diff --git a/sdk/include/mods/svc/audio_res.h b/sdk/include/mods/svc/audio_res.h index b5af109959..a5a0b7b71e 100644 --- a/sdk/include/mods/svc/audio_res.h +++ b/sdk/include/mods/svc/audio_res.h @@ -6,6 +6,10 @@ #define AUDIO_RES_SERVICE_MAJOR 1u #define AUDIO_RES_SERVICE_MINOR 0u +/* + * WSYS structs + */ + #define AUDIO_RES_DEFAULT_KEY 0x3C; typedef enum AudioWaveBank : uint8_t { @@ -21,6 +25,7 @@ typedef enum AudioWaveFormat : uint8_t { } AudioWaveFormat; typedef uint64_t AudioWaveHandle; +typedef uint64_t AudioSoundTableHandle; /** * @@ -41,9 +46,68 @@ typedef struct AudioWaveInfo { AudioRawWave const* raw_wave; } AudioWaveInfo; +/* + * BST structs + */ + +typedef enum SoundEffectCategory : uint8_t { + SE_CATEGORY_SYSTEM_SE, + SE_CATEGORY_PLAYER_VOICE, + SE_CATEGORY_PLAYER_SE, + SE_CATEGORY_FOOTNOTE_SE, + SE_CATEGORY_COLLISION_SE, + SE_CATEGORY_CHARA_VOICE, + SE_CATEGORY_CHARA_SE, + SE_CATEGORY_ENEMY_SE, + SE_CATEGORY_OBJECT_SE, + SE_CATEGORY_ENV_SE +} SoundEffectCategory; + +typedef struct AudioSoundTableEffectInfo { + uint8_t priority; + float volume; + float pitch; + + bool always_max_priority; + + bool ignore_distance_volume; + bool ignore_distance_fx_mix; + bool ignore_pan; + bool ignore_dolby; + + uint8_t random_volume; + uint8_t random_pitch; + + uint8_t doppler_power; + + uint8_t volume_dist_class; + bool clamp_min_volume; + bool cull_at_max_distance; +} AudioSoundTableEffectInfo; + +typedef enum StreamPan : uint8_t { + STREAM_PAN_CENTER, + STREAM_PAN_LEFT, + STREAM_PAN_RIGHT, +} StreamPan; + +#define STREAM_MAX_CHILDREN 6 + +typedef struct AudioSoundTableStreamInfo { + uint8_t priority; + float volume; + StreamPan pan_parameters[STREAM_MAX_CHILDREN]; + char const* file_path; + bool stop_on_scene_change; +} AudioSoundTableStreamInfo; + typedef struct AudioResService { ServiceHeader header; + /* + * WSYS API + */ + ModResult (*replace_wave)( ModContext* ctx, AudioWaveBank bank, @@ -61,6 +125,38 @@ typedef struct AudioResService { uint16_t* out_wave_id); ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle); + + /* + * BST API + */ + + ModResult (*replace_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + + ModResult (*add_sound_table_effect)( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + + ModResult (*replace_sound_table_stream)( + ModContext* ctx, + uint16_t stream_id, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle); + + ModResult (*add_sound_table_stream)( + ModContext* ctx, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + + ModResult (*remove_sound_table)(ModContext* ctx, AudioSoundTableHandle handle); } AudioResService; #ifdef __cplusplus diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index 5dfe3baed9..0a2d52540c 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -521,14 +521,15 @@ void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f3 } JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3& pos, JAISoundID soundID, - const JGeometry::TVec3* param_2, u32 param_3) { + const JGeometry::TVec3* param_2, u32 param_3 + IF_DUSK_ARG(dusk::mods::svc::audio_res::bst::SoundTableReplacementSlot const* replacement)) { u32 channelNum = param_3 | ~((1 << mNumPlayers) + -1); if (channelNum == 0xFFFFFFFF) { JUT_WARN(826, "%s", "You masked all audible channels !"); return NULL; } - JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID); + JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID IF_DUSK_ARG(replacement)); bool x = params.field_0x0.bytes.mDopplerPower != 0; Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x); diff --git a/src/Z2AudioLib/Z2SceneMgr.cpp b/src/Z2AudioLib/Z2SceneMgr.cpp index f51fc10d92..6c727ee803 100644 --- a/src/Z2AudioLib/Z2SceneMgr.cpp +++ b/src/Z2AudioLib/Z2SceneMgr.cpp @@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) { JSUList* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList(); JSULink* stream; for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) { - if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != SOUND_TYPEID_STREAM) { + if (bVar2 || sound_table->getTypeID(stream->getObject()->getID() IF_DUSK_ARG(stream->getObject()->getReplacement())) != SOUND_TYPEID_STREAM) { stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME); } } diff --git a/src/Z2AudioLib/Z2SoundHandles.cpp b/src/Z2AudioLib/Z2SoundHandles.cpp index de7e265ae7..99e11e0784 100644 --- a/src/Z2AudioLib/Z2SoundHandles.cpp +++ b/src/Z2AudioLib/Z2SoundHandles.cpp @@ -89,7 +89,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { return handle; } - u32 prio = soundInfo->getPriority((*handle)->getID()); + u32 prio = soundInfo->getPriority((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())); if (prio < low_prio) { low_prio = prio; rethandle = handle; @@ -102,7 +102,7 @@ Z2SoundHandlePool* Z2SoundHandles::getLowPrioSound(JAISoundID soundID) { } #endif - if (soundInfo->getPriority(soundID) >= low_prio) { + if (soundInfo->getPriority(soundID IF_DUSK_ARG(nullptr)) >= low_prio) { return rethandle; } diff --git a/src/Z2AudioLib/Z2SoundInfo.cpp b/src/Z2AudioLib/Z2SoundInfo.cpp index 9befa79b26..394034e322 100644 --- a/src/Z2AudioLib/Z2SoundInfo.cpp +++ b/src/Z2AudioLib/Z2SoundInfo.cpp @@ -1,11 +1,13 @@ #include "Z2AudioLib/Z2SoundInfo.h" -#include "JSystem/JAudio2/JAISeq.h" + #include "JSystem/JAudio2/JAISe.h" +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISoundChild.h" #include "JSystem/JAudio2/JAIStream.h" #include "JSystem/JAudio2/JAUSoundTable.h" #include "JSystem/JUtility/JUTAssert.h" #include "Z2AudioLib/Z2Calc.h" +#include "dusk/mods/svc/audio_res/bst.hpp" struct JAUStdSoundTableType { static DUSK_GAME_DATA const u32 STRM_CH_SHIFT; @@ -18,8 +20,8 @@ struct JAUStdSoundTableType { u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const { JUT_ASSERT(20, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(nullptr)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(nullptr)); if (data != NULL) { switch ((typeID & 0xf0)) { @@ -48,10 +50,22 @@ int Z2SoundInfo::getCategory(JAISoundID soundID) const { return soundID.id_.info.type.parts.groupID; } -u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { +u32 Z2SoundInfo::getPriority(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(63, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + +#if TARGET_PC + // This function gets called at least once in a context where we can't easily pass in the replacement. + // So check it here too. + // It's only a tiny priority difference, highly unlikely to cause any issues. + std::shared_ptr replacement_shared; + if (!replacement) { + replacement_shared = dusk::mods::svc::audio_res::bst::get_override_for(soundID); + replacement = replacement_shared.get(); + } +#endif + + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { return data->mPriority; @@ -60,59 +74,65 @@ u32 Z2SoundInfo::getPriority(JAISoundID soundID) const { return 0; } -JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { +#define SW_BIT DUSK_IF_ELSE(sw_bit, getSwBit(soundID)) + +JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) { JAUAudibleParam audibleParam; JUT_ASSERT(82, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit;) + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { case SOUND_TYPEID_SOUND_EFFECT: - audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; - if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_FX_MIX) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { audibleParam.field_0x0.bytes.mCalcPan = 0; } else { audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; @@ -132,59 +152,63 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) { return audibleParam; } -u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { +u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JAUAudibleParam audibleParam; JUT_ASSERT(184, isValid()); int iVar1, uVar7; - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + IF_DUSK(int sw_bit); + + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID) { case SOUND_TYPEID_SOUND_EFFECT: - audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET; - if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { + IF_DUSK(sw_bit = getSwBit(soundID, replacement);) + + audibleParam.field_0x0.bytes.mDopplerPower = (u32)SW_BIT >> SOUND_SW_DOPPLER_POWER_OFFSET; + if ((SW_BIT & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) { audibleParam.field_0x0.bytes.mCalculatePriority = 0; } else { audibleParam.field_0x0.bytes.mCalculatePriority = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0; } else { audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1; } - if ((getSwBit(soundID) & 4) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_FX_MIX) != 0) { audibleParam.field_0x0.bytes.mCalcFxMix = 0; } else { audibleParam.field_0x0.bytes.mCalcFxMix = 1; } - if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { + if ((SW_BIT & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1; } else { audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_PAN) != 0) { audibleParam.field_0x0.bytes.mCalcPan = 0; } else { audibleParam.field_0x0.bytes.mCalcPan = 1; } - if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) { + if ((SW_BIT & SOUND_SW_IGNORE_DOLBY) != 0) { audibleParam.field_0x0.bytes.mCalcDolby = 0; } else { audibleParam.field_0x0.bytes.mCalcDolby = 1; } uVar7 = 0; - if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { + if ((SW_BIT & SOUND_SW_CLAMP_MIN_VOLUME) != 0) { uVar7 = 8; } - iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; - iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; + iVar1 = (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED; + iVar1 += (SW_BIT >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED; audibleParam.field_0x0.bytes.mClampMinVolume = uVar7; audibleParam.field_0x0.half.f1 = iVar1; break; @@ -197,12 +221,14 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const { return audibleParam.field_0x0.half.f0; } -void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { +#undef SW_BIT + +void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr IF_DUSK_ARG(SoundEffectReplacementSlot const* replacement)) const { getSoundInfo_(soundID, sePtr); JUT_ASSERT(292, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data == NULL) { return; } @@ -210,14 +236,14 @@ void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const { switch(typeID) { case SOUND_TYPEID_SOUND_EFFECT: sePtr->getProperty().mPitch *= data->mPitch; - u32 pitchParam = (getSwBit(soundID) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; + u32 pitchParam = (getSwBit(soundID IF_DUSK_ARG(replacement)) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET; if (pitchParam > 8) { sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1(); } else { sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1(); } - u32 uVar1 = (u32)getSwBit(soundID) >> SOUND_SW_RANDOM_VOLUME_OFFSET; + u32 uVar1 = (u32)getSwBit(soundID IF_DUSK_ARG(replacement)) >> SOUND_SW_RANDOM_VOLUME_OFFSET; if (uVar1 != 0) { f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1(); sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18); @@ -230,20 +256,20 @@ void Z2SoundInfo::getSeqInfo(JAISoundID soundID, JAISeq* seqPtr) const { getSoundInfo_(soundID, seqPtr); } -DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = 2; +DUSK_GAME_DATA const u32 JAUStdSoundTableType::STRM_CH_SHIFT = STRM_CH_SHIFT_; -void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const { +void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr IF_DUSK_ARG(StreamReplacementSlot const* replacement)) const { int numChild; - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; getSoundInfo_(soundID, streamPtr); JUT_ASSERT(349, isValid()); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); switch (typeID & 0xf0) { case SOUND_TYPEID_STREAM: u16 uVar1; s32 iVar4; - data = JASGlobalInstance::getInstance()->getData(soundID); + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(356, data); uVar1 = data->mStreamPanParameters; @@ -255,13 +281,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const JAISoundChild* child = streamPtr->getChild(iVar4); if (child != NULL) { switch (uVar2) { - case 1: + case STRM_CH_CENTER: child->mMove.params_.mPan = 0.5f; break; - case 2: + case STRM_CH_LEFT: child->mMove.params_.mPan = 0.0f; break; - case 3: + case STRM_CH_RIGHT: child->mMove.params_.mPan = 1.0f; break; } @@ -271,14 +297,20 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const } } -const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { +const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { JUT_ASSERT(387, isValid()); - JAUSoundTableItem* data; + JAUSoundTableItem DUSK_CONST* data; const void* resource; - switch (JASGlobalInstance::getInstance()->getTypeID(soundID) & 0xf0) { + switch (JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)) & 0xf0) { case SOUND_TYPEID_STREAM: - data = JASGlobalInstance::getInstance()->getData(soundID); +#if TARGET_PC + if (replacement) { + return replacement->file_path.c_str(); + } +#endif + + data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); JUT_ASSERT(394, data); resource = JASGlobalInstance::getInstance()->getResource(); JUT_ASSERT(398, resource); @@ -288,16 +320,16 @@ const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) { } } -s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID) { - const char* path = getStreamFilePath(soundID); +s32 Z2SoundInfo::getStreamFileEntry(JAISoundID soundID IF_DUSK_ARG(StreamReplacementSlot const* replacement)) { + const char* path = getStreamFilePath(soundID IF_DUSK_ARG(replacement)); return !path ? -1 : DVDConvertPathToEntrynum(path); } -int Z2SoundInfo::getSwBit(JAISoundID soundID) const { +int Z2SoundInfo::getSwBit(JAISoundID soundID IF_DUSK_ARG(SoundTableReplacementSlot const* replacement)) const { JUT_ASSERT(418, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(replacement)); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(replacement)); if (data != NULL) { switch(typeID) { case SOUND_TYPEID_SOUND_EFFECT: @@ -310,9 +342,9 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const { void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const { JUT_ASSERT(440, isValid()); - JAUSoundTableItem* data = JASGlobalInstance::getInstance()->getData(soundID); + JAUSoundTableItem DUSK_CONST* data = JASGlobalInstance::getInstance()->getData(soundID IF_DUSK_ARG(soundPtr->getReplacement())); - u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID); + u8 typeID = JASGlobalInstance::getInstance()->getTypeID(soundID IF_DUSK_ARG(soundPtr->getReplacement())); if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) { soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume; } diff --git a/src/Z2AudioLib/Z2SoundMgr.cpp b/src/Z2AudioLib/Z2SoundMgr.cpp index 6978a61585..43bd6c7d5c 100644 --- a/src/Z2AudioLib/Z2SoundMgr.cpp +++ b/src/Z2AudioLib/Z2SoundMgr.cpp @@ -1,14 +1,15 @@ #include "Z2AudioLib/Z2SoundMgr.h" -#include "Z2AudioLib/Z2Param.h" -#include "Z2AudioLib/Z2SceneMgr.h" -#include "Z2AudioLib/Z2SpeechMgr2.h" -#include "Z2AudioLib/Z2SeqMgr.h" -#include "Z2AudioLib/Z2SeMgr.h" -#include "Z2AudioLib/Z2SoundInfo.h" #include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JAUSectionHeap.h" +#include "Z2AudioLib/Z2Param.h" +#include "Z2AudioLib/Z2SceneMgr.h" +#include "Z2AudioLib/Z2SeMgr.h" +#include "Z2AudioLib/Z2SeqMgr.h" +#include "Z2AudioLib/Z2SoundInfo.h" +#include "Z2AudioLib/Z2SpeechMgr2.h" #include "d/d_com_inf_game.h" +#include "dusk/mods/svc/audio_res/bst.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include "Z2AudioCS/Z2AudioCS.h" @@ -113,19 +114,25 @@ Z2SoundMgr::Z2SoundMgr() : seqMgr_(true), streamMgr_(true) { - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JGeometry::TVec3* posPtr) { DUSK_AUDIO_SKIP(true); + IF_DUSK(using namespace dusk::mods::svc::audio_res::bst); int soundType = Z2GetSoundInfo()->getSoundType(soundID); switch (soundType) { case 0: { - if (Z2GetSoundInfo()->getSwBit(soundID) & 8) { + #if TARGET_PC + { + auto replace_se = get_override_for_se(soundID); + #endif + + if (Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG(replace_se.get())) & SOUND_SW_MUTE_BGM) { OS_REPORT("[Z2SoundMgr::startSound] se seq Mute! id = %08x\n", *(u32*)&soundID); Z2GetSeqMgr()->bgmAllMute(3, 0.3f); - soundID_ = soundID; + bgmMuter = soundID; } #if PLATFORM_WII || PLATFORM_SHIELD @@ -136,15 +143,16 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG } #endif - bool result = seMgr_.startSound(soundID, handle, posPtr); + bool result = seMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_se))); return result; + IF_DUSK_BLOCK_END } case 1: if (soundID == Z2BGM_LUTERA_DEMO) { Z2GetSeqMgr()->bgmStart(Z2BGM_LUTERA2, 0, 0); Z2GetSeqMgr()->unMuteSceneBgm(0); Z2GetSeqMgr()->changeBgmStatus(0); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } else { bool loaded = false; JAUSectionHeap* sectionHeap = JASGlobalInstance::getInstance(); @@ -172,15 +180,21 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG return seqMgr_.startSound(soundID, handle, posPtr); } case 2: +#if TARGET_PC + { + auto replace_stream = get_override_for_stream(soundID); +#endif + if (soundID == 0x2000001 || soundID == 0x200004d) { streamMgr_.stop(180); - return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr); + return seMgr_.startSound(Z2SE_NO_SOUND, handle, posPtr IF_DUSK_ARG(nullptr)); } if (soundID == 0x2000005) { seMgr_.getCategory(9)->pause(false); seMgr_.getCategory(9)->getParams()->moveVolume(1.0f, 45); } - return streamMgr_.startSound(soundID, handle, posPtr); + return streamMgr_.startSound(soundID, handle, posPtr IF_DUSK_ARG(std::move(replace_stream))); + IF_DUSK_BLOCK_END default: char error[64]; SAFE_SPRINTF(error, "Unknown Sound-Type id :%08x\n", (u32)soundID); @@ -192,11 +206,11 @@ bool Z2SoundMgr::startSound(JAISoundID soundID, JAISoundHandle* handle, const JG void Z2SoundMgr::calc() { seMgr_.calc(); - if (!soundID_.isAnonymous() && !isPlayingSoundID(soundID_)) { + if (!bgmMuter.isAnonymous() && !isPlayingSoundID(bgmMuter)) { if (Z2GetSceneMgr()->isSceneExist()) { Z2GetSeqMgr()->bgmAllUnMute(3); } - soundID_.setAnonymous(); + bgmMuter.setAnonymous(); } seqMgr_.calc(); streamMgr_.calc(); diff --git a/src/Z2AudioLib/Z2SoundObject.cpp b/src/Z2AudioLib/Z2SoundObject.cpp index f38fa14e6a..c45a638b3c 100644 --- a/src/Z2AudioLib/Z2SoundObject.cpp +++ b/src/Z2AudioLib/Z2SoundObject.cpp @@ -70,7 +70,7 @@ void Z2SoundObjBase::dispose() { for (i = getFirst(); i != NULL; i = i->getNext()) { handle = i->getObject(); if (handle != NULL && *handle) { - if ((Z2GetSoundInfo()->getSwBit((*handle)->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit((*handle)->getID() IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { handle->releaseSound(); } else { (*handle)->stop(); @@ -82,7 +82,7 @@ void Z2SoundObjBase::dispose() { } bool Z2SoundObjBase::stopOK(Z2SoundHandlePool& handlePool) { - if ((Z2GetSoundInfo()->getSwBit(handlePool->getID()) & 0x8000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(handlePool->getID() IF_DUSK_ARG(handlePool->getReplacement())) & SOUND_SW_POOL_FLAG_2) != 0) { return false; } else { return true; @@ -112,7 +112,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startSound(JAISoundID soundID, u32 mapinfo, s Z2SoundHandlePool* handle = getHandleSoundID(soundID); if (handle != NULL) { - if ((Z2GetSoundInfo()->getSwBit(soundID) & 0x4000) != 0) { + if ((Z2GetSoundInfo()->getSwBit(soundID IF_DUSK_ARG((*handle)->getReplacement())) & SOUND_SW_POOL_FLAG_1) != 0) { handle = NULL; } else { return NULL; diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index a63eb4e3ce..7550b360cc 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -5,6 +5,7 @@ #include "../registry.hpp" #include "wsys.hpp" +#include "bst.hpp" namespace dusk::mods::svc { @@ -18,18 +19,26 @@ constexpr AudioResService s_audioResService{ .replace_wave = &wsys::insert_replace_wave, .add_wave = &wsys::insert_add_wave, .remove_wave = &wsys::remove_wave, + .replace_sound_table_effect = &bst::replace_sound_table_effect, + .add_sound_table_effect = &bst::add_sound_table_effect, + .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, }; void frame_end() { wsys::frame_end(); + bst::frame_end(); } void mod_detached(LoadedMod& mod) { wsys::remove_mod(mod); + bst::remove_mod(mod); } void sync_audio_replacements() { wsys::sync_audio_replacements(); + bst::sync_audio_replacements(); } } diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp new file mode 100644 index 0000000000..e47ad2d216 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -0,0 +1,350 @@ +#include "bst.hpp" + +#include "aurora/lib/logging.hpp" +#include "dusk/mods/loader/loader.hpp" +#include "dusk/mods/svc/id_allocator.hpp" +#include "dusk/mods/svc/slot_map.hpp" + +namespace dusk::mods::svc::audio_res::bst { + +namespace { + +bool sound_replacements_dirty = false; + +aurora::Module Log("dusk::mods::svc::audio_res"); + +SlotMap> sound_replacements; + +std::array sound_effect_id_allocator = { + PlainIdAllocator(0x1000), // SYSTEM_SE + PlainIdAllocator(0x1000), // PLAYER_VOICE + PlainIdAllocator(0x1000), // PLAYER_SE + PlainIdAllocator(0x1000), // FOOTNOTE_SE + PlainIdAllocator(0x1000), // COLLISION_SE + PlainIdAllocator(0x1000), // CHARA_VOICE + PlainIdAllocator(0x1000), // CHARA_SE + PlainIdAllocator(0x1000), // ENEMY_SE + PlainIdAllocator(0x1000), // OBJECT_SE + PlainIdAllocator(0x1000), // ENV_SE +}; + +PlainIdAllocator stream_id_allocator(0x1000); + +bool validate_category(SoundEffectCategory const category) { + return category <= SE_CATEGORY_ENV_SE; +} + +uint8_t volume_to_item(float volume) { + return static_cast(volume * 127); +} + +absl::flat_hash_map> active_se_replacements; +absl::flat_hash_map> active_stream_replacements; +std::mutex active_replacements_mutex; + +} // namespace + +SoundTableReplacementSlot::SoundTableReplacementSlot(bool const mod_defined, u16 const id) + : mod_defined(mod_defined), id(id), item() {} + +SoundTableReplacementSlot::~SoundTableReplacementSlot() = default; + +SoundEffectReplacementSlot::SoundEffectReplacementSlot( + bool mod_defined, u16 id, SoundEffectCategory category, const AudioSoundTableEffectInfo& info) + : SoundTableReplacementSlot(mod_defined, id), category(category) { + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + item.mPitch = info.pitch; + + u32 sw_bit = 0; + if (info.always_max_priority) + sw_bit |= SOUND_SW_ALWAYS_MAX_PRIORITY; + if (info.ignore_distance_volume) + sw_bit |= SOUND_SW_IGNORE_DISTANCE_VOL; + if (info.ignore_distance_fx_mix) + sw_bit |= SOUND_SW_IGNORE_FX_MIX; + if (info.ignore_pan) + sw_bit |= SOUND_SW_IGNORE_PAN; + if (info.ignore_dolby) + sw_bit |= SOUND_SW_IGNORE_DOLBY; + sw_bit |= (info.random_volume << SOUND_SW_RANDOM_VOLUME_OFFSET) & SOUND_SW_RANDOM_VOLUME_MASK; + sw_bit |= (info.random_pitch << SOUND_SW_RANDOM_PITCH_OFFSET) & SOUND_SW_RANDOM_PITCH_MASK; + sw_bit |= (info.doppler_power << SOUND_SW_DOPPLER_POWER_OFFSET) & SOUND_SW_DOPPLER_POWER_MASK; + + if (info.volume_dist_class < 8) { + sw_bit |= (info.volume_dist_class << SOUND_SW_VOL_DIST_BIT_OFFSET) & SOUND_SW_VOL_DIST_BIT_MASK; + } else { + sw_bit |= ((info.volume_dist_class - 8) << SOUND_SW_VOL_DIST_BIT_2_OFFSET) & SOUND_SW_VOL_DIST_BIT_2_MASK; + } + + if (info.clamp_min_volume) + sw_bit |= SOUND_SW_CLAMP_MIN_VOLUME; + if (info.cull_at_max_distance) + sw_bit |= SOUND_SW_CULL_AT_MAX_DISTANCE; + + item.mSwBit = sw_bit; +} + +u8 SoundEffectReplacementSlot::get_type_id() const { + return SOUND_TYPEID_SOUND_EFFECT; +} + +StreamReplacementSlot::StreamReplacementSlot( + bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info) + : SoundTableReplacementSlot(mod_defined, id), file_path(info.file_path) { + stop_on_scene_change = info.stop_on_scene_change; + item.mPriority = info.priority; + item.mVolume = volume_to_item(info.volume); + + u16 panParam = 0; + for (int i = STREAM_MAX_CHILDREN - 1; i >= 0; i--) { + panParam <<= STRM_CH_SHIFT_; + + switch (info.pan_parameters[i]) { + case STREAM_PAN_CENTER: + panParam |= STRM_CH_CENTER; + break; + case STREAM_PAN_LEFT: + panParam |= STRM_CH_LEFT; + break; + case STREAM_PAN_RIGHT: + panParam |= STRM_CH_RIGHT; + break; + } + } + item.mStreamPanParameters = panParam; +} + +u8 StreamReplacementSlot::get_type_id() const { + return stop_on_scene_change ? SOUND_TYPEID_STREAM_ALT : SOUND_TYPEID_STREAM; +} + +std::shared_ptr get_override_for(JAISoundID id) { + switch (id.id_.info.type.parts.sectionID) { + case 0: + return get_override_for_se(id); + case 2: + return get_override_for_stream(id); + default: + return nullptr; + } +} + +std::shared_ptr get_override_for_se(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 0) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + SoundEffectKey const key( + static_cast(id.id_.info.type.parts.groupID), + id.id_.info.waveID); + + auto const entry = active_se_replacements.find(key); + if (entry == active_se_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +std::shared_ptr get_override_for_stream(JAISoundID id) { + if (id.id_.info.type.parts.sectionID != 2) { + return nullptr; + } + + std::lock_guard lock(active_replacements_mutex); + + auto const entry = active_stream_replacements.find(id.id_.info.waveID); + if (entry == active_stream_replacements.end()) { + return nullptr; + } + + return entry->second; +} + +void remove_mod(LoadedMod const& mod) { + sound_replacements.erase_all(mod); + + sound_replacements_dirty = true; +} + +void frame_end() { + if (sound_replacements_dirty) { + sync_audio_replacements(); + } +} + +void sync_audio_replacements() { + sound_replacements_dirty = false; + + absl::flat_hash_map> new_se_replacements; + absl::flat_hash_map> new_stream_replacements; + + for (auto const& mod : ModLoader::instance().active_mods()) { + sound_replacements.for_each([&](auto, auto const& entry) { + if (entry.owner != &mod) { + return; + } + + std::shared_ptr const& value = entry.value; + auto se = std::dynamic_pointer_cast(value); + if (se) { + new_se_replacements.emplace(SoundEffectKey(se->category, se->id), std::move(se)); + return; + } + + auto stream = std::dynamic_pointer_cast(value); + if (stream) { + new_stream_replacements.emplace(stream->id, std::move(stream)); + return; + } + }); + } + + std::lock_guard lock(active_replacements_mutex); + + std::exchange(active_se_replacements, std::move(new_se_replacements)); + std::exchange(active_stream_replacements, std::move(new_stream_replacements)); +} + +static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, bool mod_defined, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || info == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + auto slot = std::make_shared(mod_defined, effect_id, category_id, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_effect_core(ctx, category_id, effect_id, false, info, out_handle); +} + +ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id) { + + if (out_effect_id == nullptr || !validate_category(category_id)) { + return MOD_INVALID_ARGUMENT; + } + + assert(category_id < sound_effect_id_allocator.size()); + + auto& allocator = sound_effect_id_allocator[category_id]; + auto const effect_id = allocator.alloc(); + + auto const result = insert_sound_table_effect_core(ctx, category_id, effect_id, true, info, out_handle); + if (result != MOD_OK) { + allocator.free(effect_id); + } + + *out_effect_id = effect_id; + + return result; +} + +static ModResult insert_sound_table_stream_core( + ModContext* ctx, + uint16_t stream_id, + bool mod_defined, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle) { + if (out_handle != nullptr) { + *out_handle = 0; + } + + auto mod = mod_from_context(ctx); + if (mod == nullptr || info == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + auto slot = std::make_shared(mod_defined, stream_id, *info); + sound_replacements_dirty = true; + + auto const handle = sound_replacements.emplace(*mod, std::move(slot)); + if (out_handle) { + *out_handle = handle; + } + + return MOD_OK; +} + +ModResult replace_sound_table_stream(ModContext* ctx, uint16_t stream_id, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle) { + return insert_sound_table_stream_core(ctx, stream_id, false, info, out_handle); +} + +ModResult add_sound_table_stream(ModContext* ctx, AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, uint16_t* out_stream_id) { + if (out_stream_id == nullptr) { + return MOD_INVALID_ARGUMENT; + } + + auto const stream_id = stream_id_allocator.alloc(); + + auto const result = insert_sound_table_stream_core(ctx, stream_id, true, info, out_handle); + if (result != MOD_OK) { + stream_id_allocator.free(stream_id); + } + + *out_stream_id = stream_id; + + return result; +} + +static PlainIdAllocator& id_allocator_for(SoundTableReplacementSlot const* slot) { + if (dynamic_cast(slot)) + return stream_id_allocator; + + if (auto const se = dynamic_cast(slot)) + return sound_effect_id_allocator[se->id]; + + CRASH("Unknown type???"); +} + +static bool sound_table_remove(LoadedMod const& mod, AudioSoundTableHandle const handle) { + auto const found = sound_replacements.find_owned(handle, mod); + if (found == nullptr) { + return false; + } + + if (found->value->mod_defined) { + auto& allocator = id_allocator_for(found->value.get()); + allocator.free(found->value->id); + } + + sound_replacements.erase_owned(handle, mod); + return true; +} + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle) { + auto* mod = mod_from_context(ctx); + if (mod == nullptr || handle == 0) { + return MOD_INVALID_ARGUMENT; + } + + if (!sound_table_remove(*mod, handle)) { + Log.error("[{}] remove sound table failed: unknown handle {}", mod->metadata.id, handle); + return MOD_INVALID_ARGUMENT; + } + + return MOD_OK; +} + +} // namespace dusk::mods::svc::audio_res::bst diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp new file mode 100644 index 0000000000..de13534f20 --- /dev/null +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include "JSystem/JAudio2/JAUSoundTable.h" +#include "mods/svc/audio_res.h" + +namespace dusk::mods { +struct LoadedMod; +} + +namespace dusk::mods::svc::audio_res::bst { + +struct SoundTableReplacementSlot { + bool mod_defined; + u16 id; + JAUSoundTableItem item; + + SoundTableReplacementSlot(bool mod_defined, u16 id); + + virtual ~SoundTableReplacementSlot() = 0; + + [[nodiscard]] virtual u8 get_type_id() const = 0; +}; + +struct SoundEffectReplacementSlot final : SoundTableReplacementSlot { + SoundEffectCategory category; + + SoundEffectReplacementSlot(bool mod_defined, u16 id, SoundEffectCategory category, + const AudioSoundTableEffectInfo& info); + + ~SoundEffectReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct StreamReplacementSlot final : SoundTableReplacementSlot { + std::string file_path; + bool stop_on_scene_change; + + StreamReplacementSlot(bool mod_defined, u16 id, const AudioSoundTableStreamInfo& info); + + ~StreamReplacementSlot() override = default; + + [[nodiscard]] u8 get_type_id() const override; +}; + +struct SoundEffectKey { + SoundEffectCategory category; + u16 id; + + template + friend H AbslHashValue(H h, const SoundEffectKey& k) { + return H::combine(std::move(h), k.category, k.id); + } + + [[nodiscard]] bool operator==(const SoundEffectKey& other) const { + return other.category == category && other.id == id; + } +}; + +std::shared_ptr get_override_for(JAISoundID id); +std::shared_ptr get_override_for_se(JAISoundID id); +std::shared_ptr get_override_for_stream(JAISoundID id); + +void frame_end(); +void remove_mod(LoadedMod const& mod); +void sync_audio_replacements(); + +ModResult replace_sound_table_effect( + ModContext* ctx, + SoundEffectCategory category_id, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_effect( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_effect_id); + +ModResult replace_sound_table_stream( + ModContext* ctx, + uint16_t stream_id, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_stream( + ModContext* ctx, + AudioSoundTableStreamInfo const* info, + AudioSoundTableHandle* out_handle, + uint16_t* out_stream_id); + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); + +} diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp index b336e0fbfa..49eecf6f1c 100644 --- a/src/dusk/mods/svc/id_allocator.hpp +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -14,7 +14,7 @@ class PlainIdAllocator { T alloc_max; public: - explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) + constexpr explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) : alloc_next(first), alloc_max(max) { } From 4b4529d4b6b3f8bd4a8399d1b57ce92ca0d50796 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Mon, 24 Aug 2026 21:10:12 +0200 Subject: [PATCH 24/36] Fix WSYS using take_all whoops --- src/dusk/mods/svc/audio_res/wsys.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 266c0ca55e..d27c759520 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -267,12 +267,16 @@ void sync_audio_replacements() { absl::flat_hash_map 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); + s_waveReplacements.for_each([&](auto, auto entry) { + if (entry.owner != &mod) { + return; + } + + auto const wave_info = wave_info_from_slot(entry.value); new_map.emplace( - AudioWaveKey(slot.value.bank, slot.value.wave_id), - AudioWaveReplacementValue(wave_info, slot.value.data)); - } + AudioWaveKey(entry.value.bank, entry.value.wave_id), + AudioWaveReplacementValue(wave_info, entry.value.data)); + }); } // Log.info("new: {}, old: {}", new_map.size(), s_replacements.size()); From bd7e5cb77d540c3106708139dabff965579f2f47 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 02:00:21 +0200 Subject: [PATCH 25/36] JAudio documentation updates --- docs/jaudio.md | 159 +++++++++++++++++- .../include/JSystem/JAudio2/JAUSoundTable.h | 2 +- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/docs/jaudio.md b/docs/jaudio.md index a42df217b2..70c726a548 100644 --- a/docs/jaudio.md +++ b/docs/jaudio.md @@ -30,7 +30,7 @@ Contains the BMS instructions for all BMS-based music and sound effects. Not all ### `/Audiores/Stream/*.ast` -Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. +Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description. ### `/Audiores/Waves/*.aw` @@ -79,12 +79,163 @@ Following is data descriptions for the remaining data in the `.BAA`, as pointed Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk. -For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) +*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat) -*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* +### `BST ` / Sound Table + +The `BST` / Sound table defines various parameters for music and sound effect playback. + +The layout is pretty simple: it's hierarchical with sections (sound effects, music sequences, streamed music)[^sectionids], +which have groups (only sound effects use this) and each group just has a flat list of items. +These indices match directly to the fields of the `JAISoundID`. + +*Relevant classes: `JAUSoundTable`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/bst.hexpat) + +Each item has a Type ID and a set of data. Types used by TP are as follows: + +#### `0x51` / sound effect + +Defines that this is a sound effect. The actual BMS code to execute (and unlike the other types, including its location) is looked up in the BSC. + +Layout: + +``` +u8 mPriority; // Priority relative to other sound effects. +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +padding[2]; +u32 mSwBit; // See below. +float mPitch; // Pitch multiplier +``` + +```cpp +// Values for mSwBit on sound effect items. (taken from JAUSoundTable.h) + +/** + * Sound is always calculated as max priority (0). + */ +#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001 + +/** + * Don't calculate volume by distance. + */ +#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002 + +/** + * Don't calculate FX mix (reverb) by distance. + */ +#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004 + +/** + * Mute all BGM sequences while this sound is playing. + */ +#define SOUND_SW_MUTE_BGM 0x0000'0008 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK + */ +#define SOUND_SW_RANDOM_PITCH_OFFSET 4 + +/** + * 4-bit value (0-15) to control the power of pitch randomization on sound playback. + * Code acts different for values above 8, not sure what the exact implication is. + */ +#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0 + +/** + * Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK + */ +#define SOUND_SW_DOPPLER_POWER_OFFSET 8 + +/** + * 4-bit value (0-15) to scale the power of the Doppler effect for this sound. + */ +#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00 + +/** + * Don't calculate panning (left/right) values for this sound. + */ +#define SOUND_SW_IGNORE_PAN 0x0000'1000 + +/** + * Don't calculate Dolby (behind/front) values for this sound. + */ +#define SOUND_SW_IGNORE_DOLBY 0x0000'2000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_1 0x0000'4000 + +/** + * Unsure. Relates to Z2 pooling of sound handles. + */ +#define SOUND_SW_POOL_FLAG_2 0x0000'8000 + +/** + * 3-bit mask used to select a volume distance/falloff class for this sound. + */ +#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000 +#define SOUND_SW_VOL_DIST_BIT_OFFSET 16 + +/** + * Limit minimum volume of this sound (after distance falloff) to 0.2. + */ +#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000 + +/** + * 3-bit mask used to select a *different* volume distance/falloff class for this sound. + * @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work. + */ +#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000 +#define SOUND_SW_VOL_DIST_BIT_2_OFFSET 20 + +/** + * Mark sound as "far away" or "culled" when at max distance (selected by distance class). + * This affects a bunch of stuff like culling, automatic stopping, priorities, etc. + */ +#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000 + +/** + * Not sure what this does. + * Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams? + */ +#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000 + +/** + * Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK + */ +#define SOUND_SW_RANDOM_VOLUME_OFFSET 28 + +/** + * 4-bit value (0-15) to control the power of volume randomization on sound playback. + */ +#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000 +``` + +#### `0x60` / music sequence + +Defines background music that plays through the BMS system. + +``` +u8 mPriority; +u8 mVolume; // Converted to float: mVolume * (1.0/127.0) +u16 mResourceId; // ID to look up in Z2SoundSeqs.arc +``` + +#### `0x70` / `0x71` / music sequence + +Defines a streamed music track. Difference between `0x70` and `0x71` seems to only be that `0x71` stops automatically +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). +char* mStreamFilePath[] : u32; // File path to the .ast on disc. +``` ## Further reading & credits * https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP) * XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/) -* The decompiled source code, duh. \ No newline at end of file +* The decompiled source code, duh. diff --git a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h index 4a4ca33214..3b38ff2eb6 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAUSoundTable.h @@ -28,7 +28,7 @@ #define SOUND_TYPEID_STREAM 0x70 /** - * Sound is a streamed music file. Unsure of difference from SOUND_TYPEID_STREAM + * Sound is a streamed music file. Stopped automatically on scene change by Z2SceneMgr.cpp */ #define SOUND_TYPEID_STREAM_ALT 0x71 From cd1d12eec6ad42e8968375b3a07743ad1d0564f8 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 20:50:59 +0200 Subject: [PATCH 26/36] 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, From adb2d7c50649fc8b99e092c7120ca00202756421 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 30 Aug 2026 20:51:18 +0200 Subject: [PATCH 27/36] Add .gitkeep to audio mod overlay for now --- mods/audio_mod/overlay/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mods/audio_mod/overlay/.gitkeep diff --git a/mods/audio_mod/overlay/.gitkeep b/mods/audio_mod/overlay/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 78c3efde6f7ea4c34f0c7239e6c58e9cb14d0130 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 30 Aug 2026 23:51:53 +0200 Subject: [PATCH 28/36] Move service implementation functions to separate header --- src/dusk/mods/svc/audio_res/audio_res.cpp | 2 - src/dusk/mods/svc/audio_res/audio_res.hpp | 73 ++++++++++++++++++++++- src/dusk/mods/svc/audio_res/bst.cpp | 3 +- src/dusk/mods/svc/audio_res/bst.hpp | 42 ------------- src/dusk/mods/svc/audio_res/wsys.cpp | 1 + src/dusk/mods/svc/audio_res/wsys.hpp | 24 -------- 6 files changed, 75 insertions(+), 70 deletions(-) diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 1c8f359c75..33a9b7be62 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -4,8 +4,6 @@ #include "mods/svc/audio_res.h" #include "../registry.hpp" -#include "wsys.hpp" -#include "bst.hpp" namespace dusk::mods::svc { diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index 5b2ae0a87a..767f1e2d6f 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -1,9 +1,80 @@ #pragma once - +#include "mods/svc/audio_res.h" namespace dusk::mods { struct LoadedMod; } + namespace dusk::mods::svc::audio_res { +namespace wsys { + +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, + u16 wave_id, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle); + +ModResult insert_add_wave( + ModContext* ctx, + AudioWaveBank bank, + char const* file_name, + AudioWaveInfo const* wave_info, + AudioWaveHandle* out_handle, + u16* out_wave_id); + +ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); + +} + +namespace bst { + +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, + uint16_t effect_id, + AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle); + +ModResult add_sound_table_effect( + ModContext* ctx, + SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, + 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); + +ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); + +} + } diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp index 9dcf635c2a..c7f4bd9e10 100644 --- a/src/dusk/mods/svc/audio_res/bst.cpp +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -1,5 +1,6 @@ #include "bst.hpp" +#include "audio_res.hpp" #include "aurora/lib/logging.hpp" #include "dusk/mods/loader/loader.hpp" #include "dusk/mods/svc/id_allocator.hpp" @@ -173,7 +174,7 @@ void remove_mod(LoadedMod const& mod) { void frame_end() { if (sound_replacements_dirty) { - sync_audio_replacements(); + wsys::sync_audio_replacements(); } } diff --git a/src/dusk/mods/svc/audio_res/bst.hpp b/src/dusk/mods/svc/audio_res/bst.hpp index 67b6c1aa18..d0619ad80f 100644 --- a/src/dusk/mods/svc/audio_res/bst.hpp +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -6,10 +6,6 @@ #include "JSystem/JAudio2/JAUSoundTable.h" #include "mods/svc/audio_res.h" -namespace dusk::mods { -struct LoadedMod; -} - namespace dusk::mods::svc::audio_res::bst { struct SoundTableReplacementSlot { @@ -64,42 +60,4 @@ std::shared_ptr get_override_for(JAISoundID id); std::shared_ptr get_override_for_se(JAISoundID id); std::shared_ptr get_override_for_stream(JAISoundID id); -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, - uint16_t effect_id, - AudioSoundTableEffectInfo const* info, - AudioSoundTableHandle* out_handle); - -ModResult add_sound_table_effect( - ModContext* ctx, - SoundEffectCategory category_id, - AudioSoundTableEffectInfo const* info, - 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); - -ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); - } diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 7899bb117c..771607dd42 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -1,3 +1,4 @@ +#include "audio_res.hpp" #include "wsys.hpp" #include "../slot_map.hpp" #include "../id_allocator.hpp" diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index 79b66b70e1..be438dcf42 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -104,28 +104,4 @@ using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveRep ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -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, - u16 wave_id, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle); - -ModResult insert_add_wave( - ModContext* ctx, - AudioWaveBank bank, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle, - u16* out_wave_id); - -ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); - } \ No newline at end of file From 4e998b7faa0deea045ed94b46991473591c02b7a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 6 Sep 2026 15:17:28 +0200 Subject: [PATCH 29/36] ImGuiAudio ShowSeqDataBlocks --- src/dusk/imgui/ImGuiAudio.cpp | 41 +++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index ce9290efa5..8cdc462c2a 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -1,14 +1,15 @@ +#include #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" -#include -#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISeMgr.h" +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" #include "JSystem/JAudio2/JASTrack.h" +#include "JSystem/JAudio2/JAUSectionHeap.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/audio/DuskDsp.hpp" @@ -281,6 +282,37 @@ static void ShowAllJAISeqs() { } } +static void ShowSeqDataBlock(JAUSeqDataBlock const& block) { + char buf[64]; + SAFE_SPRINTF(buf, "%p", &block); + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("Sound: %08X", block.field_0x10.id_.composite_.host()); + ImGui::Text("Region: %p %08X", block.region.addr, block.region.size); + } + + ImGui::EndChild(); +} + +static void ShowSeqDataBlocks() { + auto const& heap = *JAUSectionHeap::getInstance(); + auto const& blocks = heap.sectionHeapData_.seqDataBlocks; + + ImGui::Text("Loaded:"); + for (auto link = blocks.mLoadedBlocks.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } + + ImGui::Text("Loading:"); + for (auto link = blocks.field_0xc.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } + + ImGui::Text("Free:"); + for (auto link = blocks.mFreeBlocks.getFirst(); link != nullptr; link = link->getNext()) { + ShowSeqDataBlock(*link->getObject()); + } +} + void dusk::ImGuiMenuTools::ShowAudioDebug() { if (!getSettings().backend.enableAdvancedSettings || !ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F10, m_showAudioDebug)) @@ -322,6 +354,11 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("SeqDataBlocks")) { + ShowSeqDataBlocks(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } } From f1f3b5c64f88c099d621dafae4dc4d335d8ffec6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 7 Sep 2026 02:09:01 +0200 Subject: [PATCH 30/36] Fix WAVE header size parsing --- src/dusk/mods/svc/audio_res/wave.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index 5835fbcc6f..83f991dcd4 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -55,7 +55,7 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: return MOD_UNSUPPORTED; } - if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size()) { + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size() || riffChunk.header.size < 4) { Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); return MOD_INVALID_ARGUMENT; } @@ -63,7 +63,7 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: bool has_read_fmt = false; SampleDataPcm16 pcmData; - auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size); + auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size - 4); while (waveData.size() > sizeof(ChunkHeader)) { auto const chunkHeader = read_unaligned(&waveData[0]); From ec57c3305434a31bcd827ecf9ef26b9adff3248a Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 11 Sep 2026 06:23:02 +0200 Subject: [PATCH 31/36] luau_runtime reorganization + audio_res service via Lua Primary focus was to implement audio_res in Lua. As part of this I ended up re-organizing much of the luau_runtime to be less thrown together. The audio_res service relies on the (newly fetched) LuaBridge3 library to reduce all the boiler plate. All in all, I'm quite happy with how it turned out. I did have to fork LuaBridge3 to fix some bugs though, so that tag is fetched right now: https://github.com/TwilitRealm/LuaBridge3 --- mods/luau_runtime/CMakeLists.txt | 30 ++- mods/luau_runtime/src/lua_bridge_helpers.cpp | 40 ++++ mods/luau_runtime/src/lua_bridge_helpers.hpp | 106 +++++++++ mods/luau_runtime/src/lua_helpers.cpp | 118 ++++++++++ mods/luau_runtime/src/lua_helpers.hpp | 94 ++++++++ mods/luau_runtime/src/runtime.cpp | 126 ++--------- mods/luau_runtime/src/runtime.hpp | 25 +- .../src/services/audio_res/CMakeLists.txt | 7 + .../src/services/audio_res/audio_res.cpp | 53 +++++ .../src/services/audio_res/bst.cpp | 214 ++++++++++++++++++ .../src/services/audio_res/bst.hpp | 65 ++++++ .../src/services/audio_res/wsys.cpp | 138 +++++++++++ .../src/services/audio_res/wsys.hpp | 55 +++++ .../src/{ => services}/bindings.cpp | 7 +- .../src/{ => services}/config.cpp | 9 +- mods/luau_runtime/src/services/config.hpp | 10 + mods/luau_runtime/src/services/services.cpp | 32 +++ mods/luau_runtime/src/services/services.hpp | 21 ++ mods/luau_runtime/src/{ => services}/ui.cpp | 63 +++--- mods/luau_runtime/src/services/ui.hpp | 10 + 20 files changed, 1056 insertions(+), 167 deletions(-) create mode 100644 mods/luau_runtime/src/lua_bridge_helpers.cpp create mode 100644 mods/luau_runtime/src/lua_bridge_helpers.hpp create mode 100644 mods/luau_runtime/src/lua_helpers.cpp create mode 100644 mods/luau_runtime/src/lua_helpers.hpp create mode 100644 mods/luau_runtime/src/services/audio_res/CMakeLists.txt create mode 100644 mods/luau_runtime/src/services/audio_res/audio_res.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/bst.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/bst.hpp create mode 100644 mods/luau_runtime/src/services/audio_res/wsys.cpp create mode 100644 mods/luau_runtime/src/services/audio_res/wsys.hpp rename mods/luau_runtime/src/{ => services}/bindings.cpp (98%) rename mods/luau_runtime/src/{ => services}/config.cpp (98%) create mode 100644 mods/luau_runtime/src/services/config.hpp create mode 100644 mods/luau_runtime/src/services/services.cpp create mode 100644 mods/luau_runtime/src/services/services.hpp rename mods/luau_runtime/src/{ => services}/ui.cpp (96%) create mode 100644 mods/luau_runtime/src/services/ui.hpp diff --git a/mods/luau_runtime/CMakeLists.txt b/mods/luau_runtime/CMakeLists.txt index fa38b64b8a..f7729d3db2 100644 --- a/mods/luau_runtime/CMakeLists.txt +++ b/mods/luau_runtime/CMakeLists.txt @@ -26,15 +26,37 @@ FetchContent_Declare(luau ) FetchContent_MakeAvailable(luau) +FetchContent_Declare(luabridge3 + URL https://github.com/TwilitRealm/LuaBridge3/archive/refs/tags/twilit-v1.zip + URL_HASH SHA256=aa65e6e3446363d03842134182db28e6206e08d366b8efb8f66d26f96a3314be + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +FetchContent_MakeAvailable(luabridge3) + add_mod(luau_runtime SOURCES - src/bindings.cpp - src/config.cpp + src/services/bindings.cpp + src/services/config.cpp + src/services/config.hpp + src/services/services.cpp + src/services/services.hpp + src/services/ui.cpp + src/services/ui.hpp + src/lua_bridge_helpers.cpp + src/lua_bridge_helpers.hpp + src/lua_helpers.cpp + src/lua_helpers.hpp + src/runtime.hpp src/runtime.cpp - src/ui.cpp MOD_JSON mod.json RES_DIR res BUNDLE ) -target_link_libraries(luau_runtime PRIVATE Luau.VM Luau.Compiler) +target_precompile_headers(luau_runtime PRIVATE src/lua_bridge_helpers.hpp src/lua_helpers.hpp) + +# As far as I can tell, Luau already does stack checks. +target_compile_definitions(luau_runtime PRIVATE LUABRIDGE_SAFE_STACK_CHECKS=0) +add_subdirectory(src/services/audio_res) + +target_link_libraries(luau_runtime PRIVATE Luau.VM Luau.Compiler LuaBridge) diff --git a/mods/luau_runtime/src/lua_bridge_helpers.cpp b/mods/luau_runtime/src/lua_bridge_helpers.cpp new file mode 100644 index 0000000000..9895435869 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.cpp @@ -0,0 +1,40 @@ +#include "lua_bridge_helpers.hpp" + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +luabridge::TypeResult> +luabridge::Stack::get(lua_State* L, int) { + auto const& ref = luau_runtime::vm_from_registry(L); + return std::reference_wrapper(ref); +} + +namespace luau_runtime { + +BridgeScriptHandle::BridgeScriptHandle(uint64_t const handle) noexcept : handle(handle) { + +} + +BridgeScriptHandle::~BridgeScriptHandle() = default; + +void BridgeScriptHandle::unregister(lua_State* state, Vm& vm) { + if (handle == 0) { + luaL_error(state, "Stale handle"); + } + + try { + unregister_impl(state, vm); + } catch (std::exception const&) { + // If an exception occurs, disallow the Lua code from attempting to re-run unregister. + // Since we assume the object may be in an undefined state (but hopefully not?) + handle = 0; + throw; + } + + handle = 0; +} + +} diff --git a/mods/luau_runtime/src/lua_bridge_helpers.hpp b/mods/luau_runtime/src/lua_bridge_helpers.hpp new file mode 100644 index 0000000000..579b0bd9c9 --- /dev/null +++ b/mods/luau_runtime/src/lua_bridge_helpers.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" +#include "LuaBridge/LuaBridge.h" +#include "runtime.hpp" + +/** + * Defines (i.e. implements) a LuaBridge3 Stack converter for an enum that is passed by string name. + * + * @param type The type name of the enum to convert. + * @param names The constant field containing the string name <-> enum value mapping. + * @see DECLARE_STRING_ENUM + */ +#define DEFINE_STRING_ENUM(type, names) \ + Result Stack::push(lua_State* L, type value) { \ + return Stack::push(L, ::luau_runtime::lua_helpers::enum_value_to_str(L, value, names)); \ + } \ + \ + TypeResult Stack::get(lua_State* L, int index) { \ + const char* str = lua_tolstring(L, index, nullptr); \ + return ::luau_runtime::lua_helpers::enum_str_to_value(L, str, names); \ + } + +/** + * Declares a LuaBridge3 Stack converter for an enum that is passed by string name. + * + * @param type The type name of the enum to convert. + * @see DEFINE_STRING_ENUM + */ +#define DECLARE_STRING_ENUM(type) \ + template<> \ + struct Stack { \ + [[nodiscard]] static Result push(lua_State* L, type value); \ + [[nodiscard]] static TypeResult get(lua_State* L, int index); \ + } + +/** + * Template specialization to allow luau_runtime::Vm& to be directly acquired + * from a LuaBridge3 function. + */ +template <> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get(lua_State* L, int); +}; + +/** + * Template specialization to allow luau_runtime::Vm const& to be directly acquired + * from a LuaBridge3 function. + */ +template<> +struct luabridge::Stack { + [[nodiscard]] static TypeResult> get(lua_State* L, int); +}; + +namespace luau_runtime { + +/** + * Base type for "handle" types using LuaBridge3. + * + * Inherit from it, then register your derived class with LuaBridge3. + * Provide the base class @ref unregister member function. + */ +class BridgeScriptHandle { +protected: + uint64_t handle; + explicit BridgeScriptHandle(uint64_t handle) noexcept; + virtual void unregister_impl(lua_State* state, Vm& vm) = 0; + +public: + virtual ~BridgeScriptHandle(); + void unregister(lua_State* state, Vm& vm); +}; + +} + +/** + * Convert a @ref luaBridge::LuaRef to an *optional* value. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The type contained in the resulting optional. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + */ +template +std::optional opt(TLuaRef const& value) { + return value.template cast>().value(); +} + +/** + * Convert a @ref luaBridge::LuaRef to a value, with a default fallback if nil. + * + * This uses the default LuaBridge3 handling for std::optional, + * but just makes the type specification and casting a bit less verbose. + * + * @tparam T The resulting type. + * @tparam TLuaRef The type of the lua ref. Let this be inferred, as table items are unnameable. + * @param value Lua ref to convert to the desired type. + * @param default_value The default value if the lua item is nil. + */ +template +T opt_or(TLuaRef const& value, T const& default_value) { + return opt(value).value_or(default_value); +} diff --git a/mods/luau_runtime/src/lua_helpers.cpp b/mods/luau_runtime/src/lua_helpers.cpp new file mode 100644 index 0000000000..4b6c6aa09a --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.cpp @@ -0,0 +1,118 @@ +#include "lua_helpers.hpp" + +#include + +namespace luau_runtime::lua_helpers { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { + lua_getfield(state, table, field); + const bool value = lua_isnil(state, -1) ? fallback : luaL_checkboolean(state, -1) != 0; + lua_pop(state, 1); + return value; +} + +bool to_int64(lua_State* state, int index, int64_t& outValue) { + if (lua_isinteger64(state, index)) { + outValue = lua_tointeger64(state, index, nullptr); + return true; + } + if (!lua_isnumber(state, index)) { + return false; + } + const double value = lua_tonumber(state, index); + constexpr double kMaxSafeInteger = 9007199254740991.0; + if (!std::isfinite(value) || value < -kMaxSafeInteger || value > kMaxSafeInteger || + std::trunc(value) != value) + { + return false; + } + outValue = static_cast(value); + return true; +} + +int64_t check_int64(lua_State* state, int index) { + int64_t value = 0; + if (!to_int64(state, index, value)) { + luaL_argerror(state, index, "integer value expected"); + } + return value; +} + +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback) { + lua_getfield(state, table, field); + const int64_t value = lua_isnil(state, -1) ? fallback : check_int64(state, -1); + lua_pop(state, 1); + return value; +} + +double get_optional_number(lua_State* state, int table, const char* field, double fallback) { + lua_getfield(state, table, field); + const double value = lua_isnil(state, -1) ? fallback : luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +double get_number(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const double value = luaL_checknumber(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback) { + lua_getfield(state, table, field); + const uint32_t value = lua_isnil(state, -1) ? fallback : luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +uint32_t get_uint32(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + const uint32_t value = luaL_checkunsigned(state, -1); + lua_pop(state, 1); + return value; +} + +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback) { + lua_getfield(state, table, field); + const int32_t value = lua_isnil(state, -1) ? fallback : luaL_checkinteger(state, -1); + lua_pop(state, 1); + return value; +} + +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback) { + std::string result; + lua_getfield(state, table, field); + if (!lua_isnil(state, -1)) { + size_t length = 0; + const char* value = luaL_checklstring(state, -1, &length); + result.assign(value, length); + } else { + result = fallback; + } + lua_pop(state, 1); + return result; +} + +int ref_optional_function(lua_State* state, int table, const char* field) { + lua_getfield(state, table, field); + if (lua_isnil(state, -1)) { + lua_pop(state, 1); + return LUA_NOREF; + } + luaL_argexpected(state, lua_isfunction(state, -1), table, "function field"); + const int ref = lua_ref(state, -1); + lua_pop(state, 1); + return ref; +} + +int ref_required_function(lua_State* state, int table, const char* field) { + const int ref = ref_optional_function(state, table, field); + if (ref == LUA_NOREF) { + luaL_error(state, "field '%s' is required", field); + } + return ref; +} + +} diff --git a/mods/luau_runtime/src/lua_helpers.hpp b/mods/luau_runtime/src/lua_helpers.hpp new file mode 100644 index 0000000000..3db3e73831 --- /dev/null +++ b/mods/luau_runtime/src/lua_helpers.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" + +#include +#include + +/* + * Helper functions for API bound between Lua <-> C++ + */ + +namespace luau_runtime::lua_helpers { + +bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); +double get_optional_number(lua_State* state, int table, const char* field, double fallback); +int32_t get_optional_int32(lua_State* state, int table, const char* field, int32_t fallback); +uint32_t get_optional_uint32(lua_State* state, int table, const char* field, uint32_t fallback); +std::string get_optional_string( + lua_State* state, int table, const char* field, std::string_view fallback = {}); + +bool to_int64(lua_State* state, int index, int64_t& outValue); +int64_t check_int64(lua_State* state, int index); +int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback); +int ref_optional_function(lua_State* state, int table, const char* field); +int ref_required_function(lua_State* state, int table, const char* field); + +double get_number(lua_State* state, int table, const char* field); +uint32_t get_uint32(lua_State* state, int table, const char* field); + +/** + * Concept that demands things be an enum. C++ is such a cool language. + */ +template +concept Enum = std::is_enum_v; + +/** + * Specifies a name/value pair for an enum. This is used by helpers to do automatic conversions + * between the native and (string) Lua form. + * @tparam T The enum type + */ +template +struct EnumName { + T value; + std::string_view name; + + constexpr EnumName(T value, std::string_view name) : value(value), name(name) { + } +}; + +/** + * Convert an enum's string name to the actual C++ value. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param str The string value, from Lua. + * @param options The predefined set of possible enum names. + */ +template +T enum_str_to_value(lua_State* state, char const* str, EnumName const(& options)[N]) { + std::string_view strv(str); + for (size_t i = 0; i < N; ++i) { + if (options[i].name == strv) { + return static_cast(i); + } + } + + luaL_errorL(state, "Invalid enum value for %s: '%s'", typeid(T).name(), str); +} + +/** + * Convert an enum's C++ value to the Lua-exposed string. + * + * Raises a Lua error if the string is not mapped. + * + * @tparam T Type of the enum + * @param state The Lua state. + * @param value The C++ enum value. + * @param options The predefined set of possible enum names. + */ +template +std::string_view enum_value_to_str(lua_State* state, T value, EnumName const(& options)[N]) { + for (auto [nameValue, name] : options) { + if (nameValue == value) { + return name; + } + } + + luaL_errorL(state, "Attempted to return invalid enum to Lua!"); +} + +} diff --git a/mods/luau_runtime/src/runtime.cpp b/mods/luau_runtime/src/runtime.cpp index da69541872..2583d33a10 100644 --- a/mods/luau_runtime/src/runtime.cpp +++ b/mods/luau_runtime/src/runtime.cpp @@ -1,9 +1,11 @@ #include "runtime.hpp" +#include "services/services.hpp" #include "Luau/Common.h" #include "luacode.h" #include "mods/runtime.h" #include "mods/service.hpp" +#include "mods/svc/audio_res.h" #include #include @@ -26,10 +28,15 @@ IMPORT_OPTIONAL_SERVICE(ResourceService, svc_resource); IMPORT_OPTIONAL_SERVICE(OverlayService, svc_overlay); IMPORT_OPTIONAL_SERVICE(TextureService, svc_texture); IMPORT_OPTIONAL_SERVICE(UiService, svc_ui); +IMPORT_OPTIONAL_SERVICE(AudioResService, svc_audio_res); namespace luau_runtime { namespace { +// Just here to provide a globally-unique address to use as a registry index. +// Index into the Lua registry to store the Vm*. +int vm_registry_index; + std::unordered_map> s_vms; void* limited_realloc(void* userData, void* pointer, size_t oldSize, size_t newSize) { @@ -153,31 +160,6 @@ std::optional normalize_module_path( return normalized; } -ModuleOpenFn module_factory(std::string_view name) { - if (name == "dusklight.log") { - return open_log; - } - if (name == "dusklight.host") { - return open_host; - } - if (name == "dusklight.resource") { - return open_resource; - } - if (name == "dusklight.overlay") { - return open_overlay; - } - if (name == "dusklight.texture") { - return open_texture; - } - if (name == "dusklight.config") { - return open_config; - } - if (name == "dusklight.ui") { - return open_ui; - } - return nullptr; -} - int module_require(lua_State* state); void install_module_require(lua_State* state, Vm& vm, std::string_view currentPath) { @@ -262,7 +244,7 @@ int module_require(lua_State* state) { ModuleOpenFn factory = nullptr; if (requested.starts_with("dusklight.")) { moduleName = requested; - factory = module_factory(moduleName); + factory = services::module_factory(moduleName); if (factory == nullptr) { luaL_error(state, "unknown module '%s'", moduleName.c_str()); } @@ -313,6 +295,8 @@ ModResult runtime_activate(ModContext*, ModContext* subject, ModError* outError) if (vm->state == nullptr) { return set_error(outError, MOD_ERROR, "Failed to create Luau VM"); } + lua_pushlightuserdata(vm->state, vm.get()); + lua_rawsetp(vm->state, LUA_REGISTRYINDEX, &vm_registry_index); lua_callbacks(vm->state)->userdata = vm.get(); lua_callbacks(vm->state)->interrupt = [](lua_State* state, int gc) { auto* current = static_cast(lua_callbacks(state)->userdata); @@ -405,6 +389,16 @@ Vm& vm_from_upvalue(lua_State* state) { return *vm; } +Vm& vm_from_registry(lua_State* state) { + lua_rawgetp(state, LUA_REGISTRYINDEX, &vm_registry_index); + auto* vm = static_cast(lua_tolightuserdata(state, -1)); + if (vm == nullptr) { + luaL_error(state, "missing Luau runtime context"); + } + lua_pop(state, 1); + return *vm; +} + void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name) { lua_pushlightuserdata(state, &vm); lua_pushcclosure(state, function, name, 1); @@ -443,86 +437,6 @@ void check_result(lua_State* state, ModResult result, const char* operation) { luaL_error(state, "%s failed: %s", operation, resultName); } -bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { - lua_getfield(state, table, field); - const bool value = lua_isnil(state, -1) ? fallback : luaL_checkboolean(state, -1) != 0; - lua_pop(state, 1); - return value; -} - -bool to_int64(lua_State* state, int index, int64_t& outValue) { - if (lua_isinteger64(state, index)) { - outValue = lua_tointeger64(state, index, nullptr); - return true; - } - if (!lua_isnumber(state, index)) { - return false; - } - const double value = lua_tonumber(state, index); - constexpr double kMaxSafeInteger = 9007199254740991.0; - if (!std::isfinite(value) || value < -kMaxSafeInteger || value > kMaxSafeInteger || - std::trunc(value) != value) - { - return false; - } - outValue = static_cast(value); - return true; -} - -int64_t check_int64(lua_State* state, int index) { - int64_t value = 0; - if (!to_int64(state, index, value)) { - luaL_argerror(state, index, "integer value expected"); - } - return value; -} - -int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback) { - lua_getfield(state, table, field); - const int64_t value = lua_isnil(state, -1) ? fallback : check_int64(state, -1); - lua_pop(state, 1); - return value; -} - -double get_optional_number(lua_State* state, int table, const char* field, double fallback) { - lua_getfield(state, table, field); - const double value = lua_isnil(state, -1) ? fallback : luaL_checknumber(state, -1); - lua_pop(state, 1); - return value; -} - -std::string get_optional_string( - lua_State* state, int table, const char* field, std::string fallback) { - lua_getfield(state, table, field); - if (!lua_isnil(state, -1)) { - size_t length = 0; - const char* value = luaL_checklstring(state, -1, &length); - fallback.assign(value, length); - } - lua_pop(state, 1); - return fallback; -} - -int ref_optional_function(lua_State* state, int table, const char* field) { - lua_getfield(state, table, field); - if (lua_isnil(state, -1)) { - lua_pop(state, 1); - return LUA_NOREF; - } - luaL_argexpected(state, lua_isfunction(state, -1), table, "function field"); - const int ref = lua_ref(state, -1); - lua_pop(state, 1); - return ref; -} - -int ref_required_function(lua_State* state, int table, const char* field) { - const int ref = ref_optional_function(state, table, field); - if (ref == LUA_NOREF) { - luaL_error(state, "field '%s' is required", field); - } - return ref; -} - Callback& retain_callback(Vm& vm) { auto callback = std::make_unique(); callback->vm = &vm; diff --git a/mods/luau_runtime/src/runtime.hpp b/mods/luau_runtime/src/runtime.hpp index 2916a817e5..f037875fda 100644 --- a/mods/luau_runtime/src/runtime.hpp +++ b/mods/luau_runtime/src/runtime.hpp @@ -56,6 +56,9 @@ struct Vm { unsigned callDepth = 0; bool deadlineActive = false; + Vm() = default; + Vm(Vm const&) = delete; + Vm(Vm&&) = delete; ~Vm(); }; @@ -82,22 +85,13 @@ struct ScriptHandle { using ModuleOpenFn = int (*)(lua_State* state); Vm& vm_from_upvalue(lua_State* state); +Vm& vm_from_registry(lua_State* state); void push_vm_closure(lua_State* state, Vm& vm, lua_CFunction function, const char* name); void set_function(lua_State* state, Vm& vm, const char* name, lua_CFunction function); [[noreturn]] void service_unavailable(lua_State* state, const char* name); void check_result(lua_State* state, ModResult result, const char* operation); -bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); -bool to_int64(lua_State* state, int index, int64_t& outValue); -int64_t check_int64(lua_State* state, int index); -int64_t get_optional_int(lua_State* state, int table, const char* field, int64_t fallback); -double get_optional_number(lua_State* state, int table, const char* field, double fallback); -std::string get_optional_string( - lua_State* state, int table, const char* field, std::string fallback = {}); -int ref_optional_function(lua_State* state, int table, const char* field); -int ref_required_function(lua_State* state, int table, const char* field); - Callback& retain_callback(Vm& vm); bool call_ref(Vm& vm, int ref, int argumentCount, int resultCount, std::chrono::steady_clock::duration budget, std::string& outError); @@ -110,15 +104,4 @@ ScriptHandle& check_handle(lua_State* state, int index, const char* metatable, H void push_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind, const char* metatable, ConfigVarType configType = CONFIG_VAR_BOOL); -void push_config_value(lua_State* state, const ConfigVarValue& value); -void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind); - -int open_log(lua_State* state); -int open_host(lua_State* state); -int open_resource(lua_State* state); -int open_overlay(lua_State* state); -int open_texture(lua_State* state); -int open_config(lua_State* state); -int open_ui(lua_State* state); - } // namespace luau_runtime diff --git a/mods/luau_runtime/src/services/audio_res/CMakeLists.txt b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt new file mode 100644 index 0000000000..e4a1029377 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(luau_runtime PRIVATE + audio_res.cpp + bst.cpp + bst.hpp + wsys.cpp + wsys.hpp +) diff --git a/mods/luau_runtime/src/services/audio_res/audio_res.cpp b/mods/luau_runtime/src/services/audio_res/audio_res.cpp new file mode 100644 index 0000000000..32db2766ec --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/audio_res.cpp @@ -0,0 +1,53 @@ +#include "../../lua_bridge_helpers.hpp" +#include "../../runtime.hpp" +#include "../services.hpp" +#include "LuaBridge/LuaBridge.h" + +#include "mods/svc/audio_res.h" + +#include "wsys.hpp" +#include "bst.hpp" + +namespace luau_runtime::services { +namespace { + +constexpr auto kServiceName = "AudioResService"; + +} + +int open_audio_res(lua_State* state) { + using namespace audio_res; + + if (svc_audio_res == nullptr) { + service_unavailable(state, kServiceName); + } + + lua_newtable(state); + + luabridge::getNamespaceFromStack(state) + // WSYS + .addVariable("DEFAULT_KEY", AUDIO_RES_DEFAULT_KEY) + .addFunction("default_wave_info", default_wave_info) + .addFunction("replace_wave", replace_wave) + .addFunction("add_wave", add_wave) + .beginClass("AudioWaveHandle") + .addFunction("unregister", &LuaAudioWaveHandle::unregister) + .endClass() + // BST + .addFunction("default_effect_info", default_effect_info) + .addFunction("replace_sound_table_effect", replace_sound_table_effect) + .addFunction("add_sound_table_effect", add_sound_table_effect) + .addFunction("default_stream_info", default_stream_info) + .addFunction("replace_sound_table_stream", replace_sound_table_stream) + .addFunction("add_sound_table_stream", add_sound_table_stream) + .beginClass("AudioSoundTableHandle") + .addFunction("unregister", &LuaSoundTableHandle::unregister) + .endClass(); + + + lua_setreadonly(state, -1, true); + return 1; +} + +} + diff --git a/mods/luau_runtime/src/services/audio_res/bst.cpp b/mods/luau_runtime/src/services/audio_res/bst.cpp new file mode 100644 index 0000000000..b41fcb37e3 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.cpp @@ -0,0 +1,214 @@ +#include "bst.hpp" + +namespace luau_runtime::services::audio_res { + +LuaSoundTableHandle::LuaSoundTableHandle(AudioSoundTableHandle handle) : BridgeScriptHandle(handle) { } + +void LuaSoundTableHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_sound_table(vm.subject, handle); +} + +AudioSoundTableEffectInfo const& default_effect_info() { + return *svc_audio_res->default_effect_info; +} + +LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, uint16_t effect_id, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->replace_sound_table_effect(vm.subject, category_id, effect_id, effect_info, &handle), + "audio_res.replace_sound_table_effect"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_effect( + SoundEffectCategory category_id, + std::optional info, + lua_State* state, + Vm& vm) { + AudioSoundTableEffectInfo const* effect_info = nullptr; + if (info.has_value()) { + effect_info = &*info; + } + + uint16_t out_effect_id; + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->add_sound_table_effect(vm.subject, category_id, effect_info, &handle, &out_effect_id), + "audio_res.add_sound_table_effect"); + + return { out_effect_id, LuaSoundTableHandle(handle) }; +} + +AudioSoundTableStreamInfo const& default_stream_info() { + return *svc_audio_res->default_stream_info; +} + +LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* file_path, + std::optional info, lua_State* state, Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->replace_sound_table_stream(vm.subject, stream_id, file_path, stream_info, &handle), + "audio_res.replace_sound_table_stream"); + + return LuaSoundTableHandle(handle); +} + +std::tuple add_sound_table_stream( + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm) { + AudioSoundTableStreamInfo const* stream_info = nullptr; + if (info.has_value()) { + stream_info = &*info; + } + + uint16_t out_stream_id; + AudioSoundTableHandle handle; + + check_result( + state, + svc_audio_res->add_sound_table_stream(vm.subject, file_path, stream_info, &handle, &out_stream_id), + "audio_res.add_sound_table_stream"); + + return { out_stream_id, LuaSoundTableHandle(handle) }; +} + + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::lua_helpers::EnumName kSoundEffectCategoryNames[] = { + // clang-format off + { SE_CATEGORY_SYSTEM_SE, "system_se"sv }, + { SE_CATEGORY_PLAYER_VOICE, "player_voice"sv }, + { SE_CATEGORY_PLAYER_SE, "player_se"sv }, + { SE_CATEGORY_FOOTNOTE_SE, "footnote_se"sv }, + { SE_CATEGORY_COLLISION_SE, "collision_se"sv }, + { SE_CATEGORY_CHARA_VOICE, "chara_voice"sv }, + { SE_CATEGORY_CHARA_SE, "chara_se"sv }, + { SE_CATEGORY_ENEMY_SE, "enemy_se"sv }, + { SE_CATEGORY_OBJECT_SE, "object_se"sv }, + { SE_CATEGORY_ENV_SE, "env_se"sv }, + // clang-format on +}; + +constexpr luau_runtime::lua_helpers::EnumName kStreamPanNames[] = { + // clang-format off + { STREAM_PAN_CENTER, "center"sv }, + { STREAM_PAN_LEFT, "left"sv }, + { STREAM_PAN_RIGHT, "right"sv }, + // clang-format on +}; + +} // namespace + +DEFINE_STRING_ENUM(SoundEffectCategory, kSoundEffectCategoryNames); +DEFINE_STRING_ENUM(StreamPan, kStreamPanNames); + +TypeResult Stack::get( + lua_State* L, int index) { + + auto const ref = LuaRef::fromStack(L, index); + + return AudioSoundTableEffectInfo { + .priority = ref["priority"], + .volume = ref["volume"], + .pitch = ref["pitch"], + .always_max_priority = ref["always_max_priority"], + .ignore_distance_volume = ref["ignore_distance_volume"], + .ignore_distance_fx_mix = ref["ignore_distance_fx_mix"], + .ignore_pan = ref["ignore_pan"], + .ignore_dolby = ref["ignore_dolby"], + .random_volume = ref["random_volume"], + .random_pitch = ref["random_pitch"], + .doppler_power = ref["doppler_power"], + .volume_dist_class = ref["volume_dist_class"], + .clamp_min_volume = ref["clamp_min_volume"], + .cull_at_max_distance = ref["cull_at_max_distance"], + }; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableEffectInfo& value) { + + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pitch"] = value.pitch; + ref["always_max_priority"] = value.always_max_priority; + ref["ignore_distance_volume"] = value.ignore_distance_volume; + ref["ignore_distance_fx_mix"] = value.ignore_distance_fx_mix; + ref["ignore_pan"] = value.ignore_pan; + ref["ignore_dolby"] = value.ignore_dolby; + ref["random_volume"] = value.random_volume; + ref["random_pitch"] = value.random_pitch; + ref["doppler_power"] = value.doppler_power; + ref["volume_dist_class"] = value.volume_dist_class; + ref["clamp_min_volume"] = value.clamp_min_volume; + ref["cull_at_max_distance"] = value.cull_at_max_distance; + + ref.push(); + + return {}; +} + +TypeResult Stack::get( + lua_State* L, int index) { + + auto const ref = LuaRef::fromStack(L, index); + + std::array, STREAM_MAX_CHILDREN> const pan_parameters = ref["pan_parameters"]; + + auto info = AudioSoundTableStreamInfo { + .priority = ref["priority"], + .volume = ref["volume"], + .stop_on_scene_change = ref["stop_on_scene_change"], + }; + + for (int i = 0; i < STREAM_MAX_CHILDREN; i++) { + info.pan_parameters[i] = pan_parameters[i].value_or(STREAM_PAN_CENTER); + } + + return info; +} + +Result Stack::push( + lua_State* L, const AudioSoundTableStreamInfo& value) { + + LuaRef const ref = newTable(L); + + ref["priority"] = value.priority; + ref["volume"] = value.volume; + ref["pan_parameters"] = value.pan_parameters; + ref["stop_on_scene_change"] = value.stop_on_scene_change; + + ref.push(); + + return {}; +} + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/bst.hpp b/mods/luau_runtime/src/services/audio_res/bst.hpp new file mode 100644 index 0000000000..62af9cffe2 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/bst.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "mods/svc/audio_res.h" +#include "../../lua_helpers.hpp" +#include "../../lua_bridge_helpers.hpp" + +namespace luau_runtime::services::audio_res { + +class LuaSoundTableHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; +public: + explicit LuaSoundTableHandle(AudioSoundTableHandle handle); +}; + +AudioSoundTableEffectInfo const& default_effect_info(); + +LuaSoundTableHandle replace_sound_table_effect( + SoundEffectCategory category_id, + uint16_t effect_id, + std::optional info, + lua_State* state, + Vm& vm); + +std::tuple add_sound_table_effect( + SoundEffectCategory category_id, + std::optional info, + lua_State* state, + Vm& vm); + +AudioSoundTableStreamInfo const& default_stream_info(); + +LuaSoundTableHandle replace_sound_table_stream( + uint16_t stream_id, + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm); + +std::tuple add_sound_table_stream( + char const* file_path, + std::optional info, + lua_State* state, + Vm& vm); + +} + +namespace luabridge { + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableEffectInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableStreamInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(SoundEffectCategory); +DECLARE_STRING_ENUM(StreamPan); + +} diff --git a/mods/luau_runtime/src/services/audio_res/wsys.cpp b/mods/luau_runtime/src/services/audio_res/wsys.cpp new file mode 100644 index 0000000000..eb55feae78 --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.cpp @@ -0,0 +1,138 @@ +#include "wsys.hpp" + +namespace luau_runtime::services::audio_res { + +LuaAudioWaveHandle::LuaAudioWaveHandle(AudioWaveHandle handle) : BridgeScriptHandle(handle) { } + +void LuaAudioWaveHandle::unregister_impl(lua_State*, Vm& vm) { + svc_audio_res->remove_wave(vm.subject, handle); +} + +AudioWaveInfo const& default_wave_info() { + return *svc_audio_res->default_wave_info; +} + +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + + check_result( + state, + svc_audio_res->replace_wave(vm.subject, bank, wave_id, file, wave_info, &handle), + "audio_res.replace_wave"); + + return LuaAudioWaveHandle(handle); +} + +std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm) { + AudioWaveInfo const* wave_info = nullptr; + if (info.has_value()) { + wave_info = &info->info; + } + + AudioWaveHandle handle; + uint16_t out_wave_id; + + check_result( + state, + svc_audio_res->add_wave(vm.subject, bank, file, wave_info, &handle, &out_wave_id), + "audio_res.replace_wave"); + + return {out_wave_id, LuaAudioWaveHandle(handle)}; +} + +} // namespace luau_runtime::services::audio_res + +namespace luabridge { + +Result Stack::push(lua_State* L, const AudioWaveInfo& value) { + LuaRef const ref = newTable(L); + + ref["base_key"] = value.base_key; + ref["loop"] = value.loop; + ref["loop_start_sample"] = value.loop_start_sample; + ref["loop_end_sample"] = value.loop_end_sample; + if (value.raw_wave) { + ref["raw_wave"] = *value.raw_wave; + } + ref.push(); + + return {}; +} + +Result Stack::push(lua_State* L, const AudioRawWave& value) { + LuaRef const ref = newTable(L); + + ref["format"] = value.format; + ref["sample_rate"] = value.sample_rate; + ref["sample_value_last"] = value.sample_value_last; + ref["sample_value_penult"] = value.sample_value_penult; + + ref.push(); + + return {}; +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + return AudioRawWave { + .format = ref["format"], + .sample_rate = ref["sample_rate"], + .sample_value_last = opt_or(ref["sample_value_last"], 0), + .sample_value_penult = opt_or(ref["sample_value_penult"], 0), + }; +} + +using namespace luau_runtime::services::audio_res; + +Result Stack::push(lua_State* L, const OwnedWaveInfo& value) { + return Stack::push(L, value.info); +} + +TypeResult Stack::get(lua_State* L, int index) { + auto const ref = LuaRef::fromStack(L, index); + + AudioWaveInfo info { + .base_key = opt_or(ref["base_key"], AUDIO_RES_DEFAULT_KEY), + .loop = opt_or(ref["loop"], false), + .loop_start_sample = opt_or(ref["loop_start_sample"], 0), + .loop_end_sample = opt_or(ref["loop_end_sample"], std::numeric_limits::max()), + }; + + std::unique_ptr raw; + if (!ref["raw_wave"].isNil()) { + raw = std::make_unique(ref["raw_wave"]); + info.raw_wave = raw.get(); + } + + return OwnedWaveInfo { info, std::move(raw) }; +} + +namespace { + +using namespace std::string_view_literals; + +constexpr luau_runtime::lua_helpers::EnumName kWaveFormatNames[] = { + { AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv }, + { AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv }, + { AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv }, + { AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv }, +}; + +constexpr luau_runtime::lua_helpers::EnumName kWaveBankNames[] = { + { AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv } , + { AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv } , +}; + +} + +DEFINE_STRING_ENUM(AudioWaveFormat, kWaveFormatNames); +DEFINE_STRING_ENUM(AudioWaveBank, kWaveBankNames); + + +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.hpp b/mods/luau_runtime/src/services/audio_res/wsys.hpp new file mode 100644 index 0000000000..be7ab06a1c --- /dev/null +++ b/mods/luau_runtime/src/services/audio_res/wsys.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "lua.h" +#include "lualib.h" +#include "LuaBridge/LuaBridge.h" + +#include "../../runtime.hpp" +#include "../../lua_helpers.hpp" +#include "mods/svc/audio_res.h" + +#include + +namespace luau_runtime::services::audio_res { + +struct OwnedWaveInfo { + AudioWaveInfo info; + std::unique_ptr raw_wave; +}; + +class LuaAudioWaveHandle final : public BridgeScriptHandle { +protected: + void unregister_impl(lua_State*, Vm& vm) override; +public: + explicit LuaAudioWaveHandle(AudioWaveHandle handle); +}; + +AudioWaveInfo const& default_wave_info(); +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm); +std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm); + +} + +namespace luabridge { + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioWaveInfo& value); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const AudioRawWave& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +template<> +struct Stack { + [[nodiscard]] static Result push(lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value); + [[nodiscard]] static TypeResult get(lua_State* L, int index); +}; + +DECLARE_STRING_ENUM(AudioWaveFormat); +DECLARE_STRING_ENUM(AudioWaveBank); + +} diff --git a/mods/luau_runtime/src/bindings.cpp b/mods/luau_runtime/src/services/bindings.cpp similarity index 98% rename from mods/luau_runtime/src/bindings.cpp rename to mods/luau_runtime/src/services/bindings.cpp index 27f4218a83..479e8cfc63 100644 --- a/mods/luau_runtime/src/bindings.cpp +++ b/mods/luau_runtime/src/services/bindings.cpp @@ -1,12 +1,15 @@ -#include "runtime.hpp" +#include "../lua_helpers.hpp" +#include "../runtime.hpp" #include #include #include -namespace luau_runtime { +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; + constexpr char kOverlayMetatable[] = "dusklight.overlay_handle"; constexpr char kTextureMetatable[] = "dusklight.texture_handle"; diff --git a/mods/luau_runtime/src/config.cpp b/mods/luau_runtime/src/services/config.cpp similarity index 98% rename from mods/luau_runtime/src/config.cpp rename to mods/luau_runtime/src/services/config.cpp index b198bdb6e7..ef215b1824 100644 --- a/mods/luau_runtime/src/config.cpp +++ b/mods/luau_runtime/src/services/config.cpp @@ -1,11 +1,16 @@ -#include "runtime.hpp" +#include "config.hpp" +#include "../runtime.hpp" #include #include -namespace luau_runtime { +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; + constexpr char kConfigVarMetatable[] = "dusklight.config_var"; constexpr char kConfigSubscriptionMetatable[] = "dusklight.config_subscription"; diff --git a/mods/luau_runtime/src/services/config.hpp b/mods/luau_runtime/src/services/config.hpp new file mode 100644 index 0000000000..1eb83e0c8f --- /dev/null +++ b/mods/luau_runtime/src/services/config.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "lua.h" +#include "mods/svc/config.h" + +namespace luau_runtime::services { + +void push_config_value(lua_State* state, const ConfigVarValue& value); + +} diff --git a/mods/luau_runtime/src/services/services.cpp b/mods/luau_runtime/src/services/services.cpp new file mode 100644 index 0000000000..1b8425156f --- /dev/null +++ b/mods/luau_runtime/src/services/services.cpp @@ -0,0 +1,32 @@ +#include "services.hpp" + +namespace luau_runtime::services { + +namespace { + +using namespace std::string_view_literals; + +constexpr std::pair kModules[] = { + { "dusklight.log"sv, open_log }, + { "dusklight.host"sv, open_host }, + { "dusklight.resource"sv, open_resource }, + { "dusklight.overlay"sv, open_overlay }, + { "dusklight.texture"sv, open_texture }, + { "dusklight.config"sv, open_config }, + { "dusklight.ui"sv, open_ui }, + { "dusklight.audio_res"sv, open_audio_res }, +}; + +} + +ModuleOpenFn module_factory(std::string_view name) { + for (auto [ modName, ptr ] : kModules) { + if (name == modName) { + return ptr; + } + } + + return nullptr; +} + +} diff --git a/mods/luau_runtime/src/services/services.hpp b/mods/luau_runtime/src/services/services.hpp new file mode 100644 index 0000000000..e387ca2463 --- /dev/null +++ b/mods/luau_runtime/src/services/services.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "../runtime.hpp" + +#include "lua.h" + +namespace luau_runtime::services { + +int open_audio_res(lua_State* state); +int open_log(lua_State* state); +int open_host(lua_State* state); +int open_resource(lua_State* state); +int open_overlay(lua_State* state); +int open_texture(lua_State* state); +int open_config(lua_State* state); +int open_ui(lua_State* state); + +ModuleOpenFn module_factory(std::string_view name); + +} \ No newline at end of file diff --git a/mods/luau_runtime/src/ui.cpp b/mods/luau_runtime/src/services/ui.cpp similarity index 96% rename from mods/luau_runtime/src/ui.cpp rename to mods/luau_runtime/src/services/ui.cpp index 7c3acc1296..07e377d3d1 100644 --- a/mods/luau_runtime/src/ui.cpp +++ b/mods/luau_runtime/src/services/ui.cpp @@ -1,13 +1,20 @@ -#include "runtime.hpp" +#include "ui.hpp" + +#include "../runtime.hpp" #include #include #include #include -namespace luau_runtime { +#include "../lua_helpers.hpp" + +namespace luau_runtime::services { namespace { +using namespace luau_runtime::lua_helpers; +using namespace std::string_view_literals; + constexpr char kUiWindowMetatable[] = "dusklight.ui_window"; constexpr char kUiDialogMetatable[] = "dusklight.ui_dialog"; constexpr char kUiElementMetatable[] = "dusklight.ui_element"; @@ -285,40 +292,32 @@ std::vector list_items(lua_State* state, int table, std::vector kControlKindNames[] = { + { UI_CONTROL_BUTTON, "button"sv }, + { UI_CONTROL_TOGGLE, "toggle"sv }, + { UI_CONTROL_NUMBER, "number"sv }, + { UI_CONTROL_STRING, "string"sv }, + { UI_CONTROL_SELECT, "select"sv }, + { UI_CONTROL_COLOR, "color"sv }, + { UI_CONTROL_GROUP, "group"sv }, + { UI_CONTROL_FILE_PICKER, "file_picker"sv }, +}; + UiControlKind control_kind(lua_State* state, const std::string& kind) { - if (kind == "button") - return UI_CONTROL_BUTTON; - if (kind == "toggle") - return UI_CONTROL_TOGGLE; - if (kind == "number") - return UI_CONTROL_NUMBER; - if (kind == "string") - return UI_CONTROL_STRING; - if (kind == "select") - return UI_CONTROL_SELECT; - if (kind == "color") - return UI_CONTROL_COLOR; - if (kind == "group") - return UI_CONTROL_GROUP; - if (kind == "file_picker") - return UI_CONTROL_FILE_PICKER; - luaL_error(state, "unknown UI control kind '%s'", kind.c_str()); + return enum_str_to_value(state, kind.data(), kControlKindNames); } +constexpr EnumName kStyleScopeNames[] = { + { UI_SCOPE_PRELAUNCH, "prelaunch"sv }, + { UI_SCOPE_WINDOW, "window"sv }, + { UI_SCOPE_MENU_BAR, "menu_bar"sv }, + { UI_SCOPE_OVERLAY, "overlay"sv }, + { UI_SCOPE_TOUCH_CONTROLS, "touch_controls"sv }, + { UI_SCOPE_GRAPHICS_TUNER, "graphics_tuner"sv }, +}; + UiStyleScope style_scope(lua_State* state, const std::string& scope) { - if (scope == "prelaunch") - return UI_SCOPE_PRELAUNCH; - if (scope == "window") - return UI_SCOPE_WINDOW; - if (scope == "menu_bar") - return UI_SCOPE_MENU_BAR; - if (scope == "overlay") - return UI_SCOPE_OVERLAY; - if (scope == "touch_controls") - return UI_SCOPE_TOUCH_CONTROLS; - if (scope == "graphics_tuner") - return UI_SCOPE_GRAPHICS_TUNER; - luaL_error(state, "unknown UI style scope '%s'", scope.c_str()); + return enum_str_to_value(state, scope.data(), kStyleScopeNames); } int pane_add_section(lua_State* state) { diff --git a/mods/luau_runtime/src/services/ui.hpp b/mods/luau_runtime/src/services/ui.hpp new file mode 100644 index 0000000000..c1b8fb532d --- /dev/null +++ b/mods/luau_runtime/src/services/ui.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "../runtime.hpp" +#include "lua.h" + +namespace luau_runtime::services { + +void push_ui_handle(lua_State* state, Vm& vm, uint64_t value, HandleKind kind); + +} From 6b1598a4ac9039095e6ec34744b8528238cfabff Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 11 Sep 2026 07:35:30 +0200 Subject: [PATCH 32/36] Disable Opus by default So I don't have to fix the build. Linkle won't use it anyways. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70648cb198..ee495ecad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,7 @@ option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF) option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT}) option(DUSK_ENABLE_CODE_MODS "Enable code mods" ON) -option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" ON) +option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" OFF) set(DUSK_HAS_FUNCHOOK OFF) From ccd36f87aeddc747459eea2b198fffae8089e1fb Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 11 Sep 2026 08:56:26 +0200 Subject: [PATCH 33/36] Update Luau definition file Also expose STREAM_MAX_CHILDREN --- .../src/services/audio_res/audio_res.cpp | 1 + sdk/luau/dusklight.d.luau | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/mods/luau_runtime/src/services/audio_res/audio_res.cpp b/mods/luau_runtime/src/services/audio_res/audio_res.cpp index 32db2766ec..98fada0c88 100644 --- a/mods/luau_runtime/src/services/audio_res/audio_res.cpp +++ b/mods/luau_runtime/src/services/audio_res/audio_res.cpp @@ -37,6 +37,7 @@ int open_audio_res(lua_State* state) { .addFunction("default_effect_info", default_effect_info) .addFunction("replace_sound_table_effect", replace_sound_table_effect) .addFunction("add_sound_table_effect", add_sound_table_effect) + .addVariable("STREAM_MAX_CHILDREN", STREAM_MAX_CHILDREN) .addFunction("default_stream_info", default_stream_info) .addFunction("replace_sound_table_stream", replace_sound_table_stream) .addFunction("add_sound_table_stream", add_sound_table_stream) diff --git a/sdk/luau/dusklight.d.luau b/sdk/luau/dusklight.d.luau index 619e2f4124..46333d8b90 100644 --- a/sdk/luau/dusklight.d.luau +++ b/sdk/luau/dusklight.d.luau @@ -150,6 +150,85 @@ export type HostModule = { } export type ResourceModule = { load: (relativePath: string) -> string } +-- +-- audio_res +-- + +export type AudioWaveFormat = "adpcm4" | "adpcm2" | "pcm8" | "pcm16" + +export type AudioRawWave = { + format: AudioWaveFormat, + sample_rate: number, + sample_value_last: number | nil, + sample_value_penult: number | nil +} + +export type AudioWaveInfo = { + base_key: number | nil, + loop: boolean | nil, + loop_start_sample: number | nil, + loop_end_sample: number | nil, + raw_wave: AudioRawWave | nil +} + +export type AudioWaveHandle = { + unregister: (self: AudioWaveHandle) -> (), +} + +export type AudioWaveBank = "sound_effects" | "music_samples" + +export type AudioSoundTableEffectInfo = { + priority: number, + volume: number, + pitch: number, + always_max_priority: boolean, + ignore_distance_volume: boolean, + ignore_distance_fx_mix: boolean, + ignore_pan: boolean, + ignore_dolby: boolean, + random_volume: number, + random_pitch: number, + doppler_power: number, + volume_dist_class: number, + clamp_min_volume: boolean, + cull_at_max_distance: boolean, +} + +export type SoundEffectCategory = "system_se" | "player_voice" | "player_se" | "footnote_se" | "collision_se" | "chara_voice" | "chara_se" | "enemy_se" | "object_se" | "env_se" + +export type AudioSoundTableHandle = { + unregister: (self: AudioSoundTableHandle) -> () +} + +export type StreamPan = "center" | "left" | "right" + +export type AudioSoundTableStreamInfo = { + priority: number, + volume: number, + pan_parameters: {StreamPan}, -- max STREAM_MAX_CHILDREN elems + stop_on_scene_change: boolean, +} + +export type AudioResModule = { + -- wsys + DEFAULT_KEY: number, + default_wave_info: () -> AudioWaveInfo, + replace_wave: (bank: AudioWaveBank, wave_id: number, file: string, info: AudioWaveInfo | nil) -> AudioWaveHandle, + add_wave: (bank: AudioWaveBank, file: string, info: AudioWaveInfo | nil) -> (number, AudioWaveHandle), + + -- bst + default_effect_info: () -> AudioSoundTableEffectInfo, + replace_sound_table_effect: (category: SoundEffectCategory, effect_id: number, info: AudioSoundTableEffectInfo | nil) -> AudioSoundTableHandle, + add_sound_table_effect: (category: SoundEffectCategory, info: AudioSoundTableEffectInfo | nil) -> (number, AudioSoundTableHandle), + + STREAM_MAX_CHILDREN: number, + default_stream_info: () -> AudioSoundTableStreamInfo, + replace_sound_table_stream: (stream_id: number, file_path: string, info: AudioSoundTableStreamInfo | nil) -> AudioSoundTableHandle, + add_sound_table_stream: (file_path: string, info: AudioSoundTableStreamInfo | nil) -> (number, AudioSoundTableHandle), +} + + + declare function require(path: "dusklight.log"): LogModule declare function require(path: "dusklight.host"): HostModule declare function require(path: "dusklight.config"): ConfigModule @@ -157,4 +236,5 @@ declare function require(path: "dusklight.resource"): ResourceModule declare function require(path: "dusklight.overlay"): OverlayModule declare function require(path: "dusklight.texture"): TextureModule declare function require(path: "dusklight.ui"): UiModule +declare function require(path: "dusklight.audio_res"): AudioResModule declare function require(path: string): any From 6049b49592ad5cc6cdf2152d2a49f7fe40c4b552 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 11 Sep 2026 23:49:16 +0200 Subject: [PATCH 34/36] No Fun Allowed --- mods/audio_mod/mod.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mods/audio_mod/mod.json b/mods/audio_mod/mod.json index 7151999aa6..61fb60ea73 100644 --- a/mods/audio_mod/mod.json +++ b/mods/audio_mod/mod.json @@ -1,7 +1,7 @@ { "id": "dev.twilitrealm.audio_mod", - "name": "[DEMO] Audio Mod", + "name": "[Demo] Audio Mod", "version": "1.0.0", - "author": "You, the listener", - "description": "An example Dusklight mod" + "author": "Twilit Realm", + "description": "Tests the functionality of the audio_res service, modifying some sounds" } From 03390b807e1c8ac3840f34d06b5dc6dab8d9b430 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 12 Sep 2026 20:15:59 +0200 Subject: [PATCH 35/36] Remove namespaces --- include/helpers/alignment.hpp | 2 +- mods/luau_runtime/src/lua_bridge_helpers.hpp | 4 ++-- mods/luau_runtime/src/lua_helpers.cpp | 2 +- mods/luau_runtime/src/lua_helpers.hpp | 2 +- mods/luau_runtime/src/services/audio_res/bst.cpp | 4 ++-- mods/luau_runtime/src/services/audio_res/wsys.cpp | 4 ++-- mods/luau_runtime/src/services/bindings.cpp | 2 -- mods/luau_runtime/src/services/config.cpp | 2 -- mods/luau_runtime/src/services/ui.cpp | 1 - src/dusk/mods/svc/audio_res/wave.cpp | 2 +- 10 files changed, 10 insertions(+), 15 deletions(-) diff --git a/include/helpers/alignment.hpp b/include/helpers/alignment.hpp index 7ce0ead69a..60fee353e0 100644 --- a/include/helpers/alignment.hpp +++ b/include/helpers/alignment.hpp @@ -1,6 +1,6 @@ #pragma once -namespace dusk::helpers::alignment { +namespace dusk::helpers { /** * Read data from an address that may not be aligned properly. diff --git a/mods/luau_runtime/src/lua_bridge_helpers.hpp b/mods/luau_runtime/src/lua_bridge_helpers.hpp index 579b0bd9c9..bc42b653c6 100644 --- a/mods/luau_runtime/src/lua_bridge_helpers.hpp +++ b/mods/luau_runtime/src/lua_bridge_helpers.hpp @@ -14,12 +14,12 @@ */ #define DEFINE_STRING_ENUM(type, names) \ Result Stack::push(lua_State* L, type value) { \ - return Stack::push(L, ::luau_runtime::lua_helpers::enum_value_to_str(L, value, names)); \ + return Stack::push(L, ::luau_runtime::enum_value_to_str(L, value, names)); \ } \ \ TypeResult Stack::get(lua_State* L, int index) { \ const char* str = lua_tolstring(L, index, nullptr); \ - return ::luau_runtime::lua_helpers::enum_str_to_value(L, str, names); \ + return ::luau_runtime::enum_str_to_value(L, str, names); \ } /** diff --git a/mods/luau_runtime/src/lua_helpers.cpp b/mods/luau_runtime/src/lua_helpers.cpp index 4b6c6aa09a..f99322937b 100644 --- a/mods/luau_runtime/src/lua_helpers.cpp +++ b/mods/luau_runtime/src/lua_helpers.cpp @@ -2,7 +2,7 @@ #include -namespace luau_runtime::lua_helpers { +namespace luau_runtime { bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback) { lua_getfield(state, table, field); diff --git a/mods/luau_runtime/src/lua_helpers.hpp b/mods/luau_runtime/src/lua_helpers.hpp index 3db3e73831..21b5072c07 100644 --- a/mods/luau_runtime/src/lua_helpers.hpp +++ b/mods/luau_runtime/src/lua_helpers.hpp @@ -10,7 +10,7 @@ * Helper functions for API bound between Lua <-> C++ */ -namespace luau_runtime::lua_helpers { +namespace luau_runtime { bool get_optional_bool(lua_State* state, int table, const char* field, bool fallback); double get_optional_number(lua_State* state, int table, const char* field, double fallback); diff --git a/mods/luau_runtime/src/services/audio_res/bst.cpp b/mods/luau_runtime/src/services/audio_res/bst.cpp index b41fcb37e3..87a23b2039 100644 --- a/mods/luau_runtime/src/services/audio_res/bst.cpp +++ b/mods/luau_runtime/src/services/audio_res/bst.cpp @@ -100,7 +100,7 @@ namespace { using namespace std::string_view_literals; -constexpr luau_runtime::lua_helpers::EnumName kSoundEffectCategoryNames[] = { +constexpr luau_runtime::EnumName kSoundEffectCategoryNames[] = { // clang-format off { SE_CATEGORY_SYSTEM_SE, "system_se"sv }, { SE_CATEGORY_PLAYER_VOICE, "player_voice"sv }, @@ -115,7 +115,7 @@ constexpr luau_runtime::lua_helpers::EnumName kSoundEffectC // clang-format on }; -constexpr luau_runtime::lua_helpers::EnumName kStreamPanNames[] = { +constexpr luau_runtime::EnumName kStreamPanNames[] = { // clang-format off { STREAM_PAN_CENTER, "center"sv }, { STREAM_PAN_LEFT, "left"sv }, diff --git a/mods/luau_runtime/src/services/audio_res/wsys.cpp b/mods/luau_runtime/src/services/audio_res/wsys.cpp index eb55feae78..36c6c988aa 100644 --- a/mods/luau_runtime/src/services/audio_res/wsys.cpp +++ b/mods/luau_runtime/src/services/audio_res/wsys.cpp @@ -117,14 +117,14 @@ namespace { using namespace std::string_view_literals; -constexpr luau_runtime::lua_helpers::EnumName kWaveFormatNames[] = { +constexpr luau_runtime::EnumName kWaveFormatNames[] = { { AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv }, { AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv }, { AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv }, { AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv }, }; -constexpr luau_runtime::lua_helpers::EnumName kWaveBankNames[] = { +constexpr luau_runtime::EnumName kWaveBankNames[] = { { AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv } , { AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv } , }; diff --git a/mods/luau_runtime/src/services/bindings.cpp b/mods/luau_runtime/src/services/bindings.cpp index 479e8cfc63..af58b446a3 100644 --- a/mods/luau_runtime/src/services/bindings.cpp +++ b/mods/luau_runtime/src/services/bindings.cpp @@ -8,8 +8,6 @@ namespace luau_runtime::services { namespace { -using namespace luau_runtime::lua_helpers; - constexpr char kOverlayMetatable[] = "dusklight.overlay_handle"; constexpr char kTextureMetatable[] = "dusklight.texture_handle"; diff --git a/mods/luau_runtime/src/services/config.cpp b/mods/luau_runtime/src/services/config.cpp index ef215b1824..5bce363a9e 100644 --- a/mods/luau_runtime/src/services/config.cpp +++ b/mods/luau_runtime/src/services/config.cpp @@ -9,8 +9,6 @@ namespace luau_runtime::services { namespace { -using namespace luau_runtime::lua_helpers; - constexpr char kConfigVarMetatable[] = "dusklight.config_var"; constexpr char kConfigSubscriptionMetatable[] = "dusklight.config_subscription"; diff --git a/mods/luau_runtime/src/services/ui.cpp b/mods/luau_runtime/src/services/ui.cpp index 07e377d3d1..e73da576aa 100644 --- a/mods/luau_runtime/src/services/ui.cpp +++ b/mods/luau_runtime/src/services/ui.cpp @@ -12,7 +12,6 @@ namespace luau_runtime::services { namespace { -using namespace luau_runtime::lua_helpers; using namespace std::string_view_literals; constexpr char kUiWindowMetatable[] = "dusklight.ui_window"; diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index 83f991dcd4..cb1f74752b 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -40,7 +40,7 @@ bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { namespace dusk::mods::svc::audio_res::wsys { ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { - using namespace helpers::alignment; + using namespace helpers; if (fileData.size() < sizeof(RiffChunk)) { return MOD_UNSUPPORTED; From ab6db7d4e647a41b7e4b8ed8ba93e1d0eee5f54d Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 12 Sep 2026 20:33:08 +0200 Subject: [PATCH 36/36] clang-format & cleanup --- include/Z2AudioLib/Z2Audience.h | 6 +- include/helpers/alignment.hpp | 5 +- include/helpers/cast.hpp | 70 ++++++++--------- mods/audio_mod/src/mod.cpp | 23 ++---- mods/luau_runtime/src/lua_bridge_helpers.cpp | 6 +- mods/luau_runtime/src/lua_bridge_helpers.hpp | 44 ++++++----- mods/luau_runtime/src/lua_helpers.cpp | 2 +- mods/luau_runtime/src/lua_helpers.hpp | 11 ++- mods/luau_runtime/src/runtime.cpp | 2 +- .../src/services/audio_res/audio_res.cpp | 10 +-- .../src/services/audio_res/bst.cpp | 57 ++++++-------- .../src/services/audio_res/bst.hpp | 41 ++++------ .../src/services/audio_res/wsys.cpp | 38 +++++----- .../src/services/audio_res/wsys.hpp | 27 ++++--- mods/luau_runtime/src/services/bindings.cpp | 2 +- mods/luau_runtime/src/services/config.cpp | 2 +- mods/luau_runtime/src/services/services.cpp | 22 +++--- mods/luau_runtime/src/services/services.hpp | 2 +- mods/luau_runtime/src/services/ui.cpp | 30 ++++---- src/dusk/mods/svc/audio_res/audio_res.cpp | 11 +-- src/dusk/mods/svc/audio_res/audio_res.hpp | 54 ++++--------- src/dusk/mods/svc/audio_res/bst.cpp | 75 +++++++++---------- src/dusk/mods/svc/audio_res/bst.hpp | 5 +- src/dusk/mods/svc/audio_res/opus.cpp | 40 +++++----- src/dusk/mods/svc/audio_res/wave.cpp | 34 +++++---- src/dusk/mods/svc/audio_res/wsys.cpp | 65 +++++++--------- src/dusk/mods/svc/audio_res/wsys.hpp | 20 ++--- src/dusk/mods/svc/id_allocator.hpp | 13 ++-- 28 files changed, 324 insertions(+), 393 deletions(-) diff --git a/include/Z2AudioLib/Z2Audience.h b/include/Z2AudioLib/Z2Audience.h index 2746dfa889..2f2dc359f9 100644 --- a/include/Z2AudioLib/Z2Audience.h +++ b/include/Z2AudioLib/Z2Audience.h @@ -54,10 +54,6 @@ struct Z2AudioCamera { /* 0x00 */ JGeometry::TPosition3f32 mViewMatrix; /* 0x30 */ JGeometry::TVec3 mVel; /* 0x3C */ JGeometry::TVec3 mPos; - - /** - * - */ /* 0x48 */ JGeometry::TVec3 mLastPos; /* 0x54 */ f32 mFovySin; /* 0x58 */ f32 mVolCenterZ; @@ -281,7 +277,7 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance { } Z2Audience3DSetting* getSetting() { return &mSetting; } - const Z2AudioCamera* getAudioCamera(int camID) const { return &mAudioCamera[camID]; } + const Z2AudioCamera* getAudioCamera(int camID) const { return &mAudioCamera[camID]; } void setUsingOffMicVol(bool value) { mUsingOffMicVol = value; } diff --git a/include/helpers/alignment.hpp b/include/helpers/alignment.hpp index 60fee353e0..54f46b0678 100644 --- a/include/helpers/alignment.hpp +++ b/include/helpers/alignment.hpp @@ -8,11 +8,12 @@ namespace dusk::helpers { * @param ptr Address to read from. * @return The copied value. */ -template requires std::is_trivially_copyable_v +template + requires std::is_trivially_copyable_v [[nodiscard]] constexpr T read_unaligned(u8 const* ptr) { T copy; memcpy(©, ptr, sizeof(T)); return copy; } -} +} // namespace dusk::helpers diff --git a/include/helpers/cast.hpp b/include/helpers/cast.hpp index 206bc1e3f3..ef6d8421a6 100644 --- a/include/helpers/cast.hpp +++ b/include/helpers/cast.hpp @@ -1,48 +1,48 @@ #pragma once #include -#include #include +#include /** * Helper functions for performing casts. */ namespace dusk::helpers::cast { - /** - * Implementation details of dusk::helpers::cast. - */ - namespace _impl { - [[noreturn]] void overrun_high(); - [[noreturn]] void overrun_low(); - } +/** + * Implementation details of dusk::helpers::cast. + */ +namespace _impl { +[[noreturn]] void overrun_high(); +[[noreturn]] void overrun_low(); +} // namespace _impl - template - concept IntCastable = std::is_integral_v && std::is_trivially_copyable_v; +template +concept IntCastable = std::is_integral_v && std::is_trivially_copyable_v; - /** - * Helper type that allows easily casting between integer types, - * that safely aborts if an overflow were to occur. - * Usage: - * @code - * size_t foobar = 20; - * int real = bounded_cast(foobar); - * @endcode - */ - template - struct bounded_cast { - Source src; +/** + * Helper type that allows easily casting between integer types, + * that safely aborts if an overflow were to occur. + * Usage: + * @code + * size_t foobar = 20; + * int real = bounded_cast(foobar); + * @endcode + */ +template +struct bounded_cast { + Source src; - template - [[nodiscard]] constexpr operator Target() const { - if (std::cmp_greater(src, std::numeric_limits::max())) [[unlikely]] { - _impl::overrun_high(); - } - - if (std::cmp_less(src, std::numeric_limits::min())) [[unlikely]] { - _impl::overrun_low(); - } - - return static_cast(src); + template + [[nodiscard]] constexpr operator Target() const { + if (std::cmp_greater(src, std::numeric_limits::max())) [[unlikely]] { + _impl::overrun_high(); } - }; -} + + if (std::cmp_less(src, std::numeric_limits::min())) [[unlikely]] { + _impl::overrun_low(); + } + + return static_cast(src); + } +}; +} // namespace dusk::helpers::cast diff --git a/mods/audio_mod/src/mod.cpp b/mods/audio_mod/src/mod.cpp index 6319310eea..ef6e488437 100644 --- a/mods/audio_mod/src/mod.cpp +++ b/mods/audio_mod/src/mod.cpp @@ -12,28 +12,15 @@ MOD_EXPORT ModResult mod_initialize(ModError*) { svc_log->info(mod_ctx, "template_mod initialized"); AudioWaveHandle handle; svc_audio_res->replace_wave( - mod_ctx, - AUDIO_WAVE_BANK_SOUND_EFFECTS, - 4238, - "res/go.opus", - nullptr, - &handle); + mod_ctx, AUDIO_WAVE_BANK_SOUND_EFFECTS, 4238, "res/go.opus", nullptr, &handle); svc_audio_res->replace_wave( - mod_ctx, - AUDIO_WAVE_BANK_SOUND_EFFECTS, - 4237, - "res/go.opus", - nullptr, - &handle); + mod_ctx, AUDIO_WAVE_BANK_SOUND_EFFECTS, 4237, "res/go.opus", nullptr, &handle); constexpr AudioSoundTableEffectInfo effect(128, 1, 1.5); - svc_audio_res->replace_sound_table_effect( - mod_ctx, - SE_CATEGORY_CHARA_SE, - 0, // MIDNA_APPEAR - &effect, - nullptr); + svc_audio_res->replace_sound_table_effect(mod_ctx, SE_CATEGORY_CHARA_SE, + 0, // MIDNA_APPEAR + &effect, nullptr); return MOD_OK; } diff --git a/mods/luau_runtime/src/lua_bridge_helpers.cpp b/mods/luau_runtime/src/lua_bridge_helpers.cpp index 9895435869..6825fd08f8 100644 --- a/mods/luau_runtime/src/lua_bridge_helpers.cpp +++ b/mods/luau_runtime/src/lua_bridge_helpers.cpp @@ -14,9 +14,7 @@ luabridge::Stack::get(lua_State* L, int) { namespace luau_runtime { -BridgeScriptHandle::BridgeScriptHandle(uint64_t const handle) noexcept : handle(handle) { - -} +BridgeScriptHandle::BridgeScriptHandle(uint64_t const handle) noexcept : handle(handle) {} BridgeScriptHandle::~BridgeScriptHandle() = default; @@ -37,4 +35,4 @@ void BridgeScriptHandle::unregister(lua_State* state, Vm& vm) { handle = 0; } -} +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/lua_bridge_helpers.hpp b/mods/luau_runtime/src/lua_bridge_helpers.hpp index bc42b653c6..2f51274573 100644 --- a/mods/luau_runtime/src/lua_bridge_helpers.hpp +++ b/mods/luau_runtime/src/lua_bridge_helpers.hpp @@ -1,25 +1,29 @@ #pragma once +// clang-format off #include "lua.h" #include "lualib.h" #include "LuaBridge/LuaBridge.h" +// clang-format on #include "runtime.hpp" /** - * Defines (i.e. implements) a LuaBridge3 Stack converter for an enum that is passed by string name. + * Defines (i.e. implements) a LuaBridge3 Stack converter for an enum that is passed by string + * name. * * @param type The type name of the enum to convert. * @param names The constant field containing the string name <-> enum value mapping. * @see DECLARE_STRING_ENUM */ -#define DEFINE_STRING_ENUM(type, names) \ - Result Stack::push(lua_State* L, type value) { \ - return Stack::push(L, ::luau_runtime::enum_value_to_str(L, value, names)); \ - } \ - \ - TypeResult Stack::get(lua_State* L, int index) { \ - const char* str = lua_tolstring(L, index, nullptr); \ - return ::luau_runtime::enum_str_to_value(L, str, names); \ +#define DEFINE_STRING_ENUM(type, names) \ + Result Stack::push(lua_State* L, type value) { \ + return Stack::push( \ + L, ::luau_runtime::enum_value_to_str(L, value, names)); \ + } \ + \ + TypeResult Stack::get(lua_State* L, int index) { \ + const char* str = lua_tolstring(L, index, nullptr); \ + return ::luau_runtime::enum_str_to_value(L, str, names); \ } /** @@ -28,11 +32,11 @@ * @param type The type name of the enum to convert. * @see DEFINE_STRING_ENUM */ -#define DECLARE_STRING_ENUM(type) \ - template<> \ - struct Stack { \ - [[nodiscard]] static Result push(lua_State* L, type value); \ - [[nodiscard]] static TypeResult get(lua_State* L, int index); \ +#define DECLARE_STRING_ENUM(type) \ + template <> \ + struct Stack { \ + [[nodiscard]] static Result push(lua_State* L, type value); \ + [[nodiscard]] static TypeResult get(lua_State* L, int index); \ } /** @@ -41,16 +45,18 @@ */ template <> struct luabridge::Stack { - [[nodiscard]] static TypeResult> get(lua_State* L, int); + [[nodiscard]] static TypeResult> get( + lua_State* L, int); }; /** * Template specialization to allow luau_runtime::Vm const& to be directly acquired * from a LuaBridge3 function. */ -template<> +template <> struct luabridge::Stack { - [[nodiscard]] static TypeResult> get(lua_State* L, int); + [[nodiscard]] static TypeResult> get( + lua_State* L, int); }; namespace luau_runtime { @@ -72,7 +78,7 @@ public: void unregister(lua_State* state, Vm& vm); }; -} +} // namespace luau_runtime /** * Convert a @ref luaBridge::LuaRef to an *optional* value. @@ -100,7 +106,7 @@ std::optional opt(TLuaRef const& value) { * @param value Lua ref to convert to the desired type. * @param default_value The default value if the lua item is nil. */ -template +template T opt_or(TLuaRef const& value, T const& default_value) { return opt(value).value_or(default_value); } diff --git a/mods/luau_runtime/src/lua_helpers.cpp b/mods/luau_runtime/src/lua_helpers.cpp index f99322937b..e09f582945 100644 --- a/mods/luau_runtime/src/lua_helpers.cpp +++ b/mods/luau_runtime/src/lua_helpers.cpp @@ -115,4 +115,4 @@ int ref_required_function(lua_State* state, int table, const char* field) { return ref; } -} +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/lua_helpers.hpp b/mods/luau_runtime/src/lua_helpers.hpp index 21b5072c07..52e3b8210f 100644 --- a/mods/luau_runtime/src/lua_helpers.hpp +++ b/mods/luau_runtime/src/lua_helpers.hpp @@ -31,7 +31,7 @@ uint32_t get_uint32(lua_State* state, int table, const char* field); /** * Concept that demands things be an enum. C++ is such a cool language. */ -template +template concept Enum = std::is_enum_v; /** @@ -44,8 +44,7 @@ struct EnumName { T value; std::string_view name; - constexpr EnumName(T value, std::string_view name) : value(value), name(name) { - } + constexpr EnumName(T value, std::string_view name) : value(value), name(name) {} }; /** @@ -59,7 +58,7 @@ struct EnumName { * @param options The predefined set of possible enum names. */ template -T enum_str_to_value(lua_State* state, char const* str, EnumName const(& options)[N]) { +T enum_str_to_value(lua_State* state, char const* str, EnumName const (&options)[N]) { std::string_view strv(str); for (size_t i = 0; i < N; ++i) { if (options[i].name == strv) { @@ -81,7 +80,7 @@ T enum_str_to_value(lua_State* state, char const* str, EnumName const(& optio * @param options The predefined set of possible enum names. */ template -std::string_view enum_value_to_str(lua_State* state, T value, EnumName const(& options)[N]) { +std::string_view enum_value_to_str(lua_State* state, T value, EnumName const (&options)[N]) { for (auto [nameValue, name] : options) { if (nameValue == value) { return name; @@ -91,4 +90,4 @@ std::string_view enum_value_to_str(lua_State* state, T value, EnumName const( luaL_errorL(state, "Attempted to return invalid enum to Lua!"); } -} +} // namespace luau_runtime diff --git a/mods/luau_runtime/src/runtime.cpp b/mods/luau_runtime/src/runtime.cpp index 664bb1d9b1..bb029e4645 100644 --- a/mods/luau_runtime/src/runtime.cpp +++ b/mods/luau_runtime/src/runtime.cpp @@ -327,7 +327,7 @@ ModResult runtime_activate(ModContext*, ModContext* subject, ModError* outError) lua_pushlightuserdata(vm->state, vm.get()); lua_rawsetp(vm->state, LUA_REGISTRYINDEX, &vm_registry_index); lua_callbacks(vm->state)->userdata = vm.get(); -#if NDEBUG // Annoying for debuggers +#if NDEBUG // Annoying for debuggers lua_callbacks(vm->state)->interrupt = [](lua_State* state, int gc) { auto* current = static_cast(lua_callbacks(state)->userdata); if (gc < 0 && current != nullptr && current->deadlineActive && diff --git a/mods/luau_runtime/src/services/audio_res/audio_res.cpp b/mods/luau_runtime/src/services/audio_res/audio_res.cpp index 98fada0c88..315f6c904c 100644 --- a/mods/luau_runtime/src/services/audio_res/audio_res.cpp +++ b/mods/luau_runtime/src/services/audio_res/audio_res.cpp @@ -5,8 +5,8 @@ #include "mods/svc/audio_res.h" -#include "wsys.hpp" #include "bst.hpp" +#include "wsys.hpp" namespace luau_runtime::services { namespace { @@ -31,7 +31,7 @@ int open_audio_res(lua_State* state) { .addFunction("replace_wave", replace_wave) .addFunction("add_wave", add_wave) .beginClass("AudioWaveHandle") - .addFunction("unregister", &LuaAudioWaveHandle::unregister) + .addFunction("unregister", &LuaAudioWaveHandle::unregister) .endClass() // BST .addFunction("default_effect_info", default_effect_info) @@ -42,13 +42,11 @@ int open_audio_res(lua_State* state) { .addFunction("replace_sound_table_stream", replace_sound_table_stream) .addFunction("add_sound_table_stream", add_sound_table_stream) .beginClass("AudioSoundTableHandle") - .addFunction("unregister", &LuaSoundTableHandle::unregister) + .addFunction("unregister", &LuaSoundTableHandle::unregister) .endClass(); - lua_setreadonly(state, -1, true); return 1; } -} - +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/audio_res/bst.cpp b/mods/luau_runtime/src/services/audio_res/bst.cpp index 87a23b2039..fe670e4a9f 100644 --- a/mods/luau_runtime/src/services/audio_res/bst.cpp +++ b/mods/luau_runtime/src/services/audio_res/bst.cpp @@ -2,7 +2,8 @@ namespace luau_runtime::services::audio_res { -LuaSoundTableHandle::LuaSoundTableHandle(AudioSoundTableHandle handle) : BridgeScriptHandle(handle) { } +LuaSoundTableHandle::LuaSoundTableHandle(AudioSoundTableHandle handle) + : BridgeScriptHandle(handle) {} void LuaSoundTableHandle::unregister_impl(lua_State*, Vm& vm) { svc_audio_res->remove_sound_table(vm.subject, handle); @@ -21,19 +22,16 @@ LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, AudioSoundTableHandle handle; - check_result( - state, - svc_audio_res->replace_sound_table_effect(vm.subject, category_id, effect_id, effect_info, &handle), + check_result(state, + svc_audio_res->replace_sound_table_effect( + vm.subject, category_id, effect_id, effect_info, &handle), "audio_res.replace_sound_table_effect"); return LuaSoundTableHandle(handle); } -std::tuple add_sound_table_effect( - SoundEffectCategory category_id, - std::optional info, - lua_State* state, - Vm& vm) { +std::tuple add_sound_table_effect(SoundEffectCategory category_id, + std::optional info, lua_State* state, Vm& vm) { AudioSoundTableEffectInfo const* effect_info = nullptr; if (info.has_value()) { effect_info = &*info; @@ -42,12 +40,12 @@ std::tuple add_sound_table_effect( uint16_t out_effect_id; AudioSoundTableHandle handle; - check_result( - state, - svc_audio_res->add_sound_table_effect(vm.subject, category_id, effect_info, &handle, &out_effect_id), + check_result(state, + svc_audio_res->add_sound_table_effect( + vm.subject, category_id, effect_info, &handle, &out_effect_id), "audio_res.add_sound_table_effect"); - return { out_effect_id, LuaSoundTableHandle(handle) }; + return {out_effect_id, LuaSoundTableHandle(handle)}; } AudioSoundTableStreamInfo const& default_stream_info() { @@ -63,19 +61,16 @@ LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* f AudioSoundTableHandle handle; - check_result( - state, - svc_audio_res->replace_sound_table_stream(vm.subject, stream_id, file_path, stream_info, &handle), + check_result(state, + svc_audio_res->replace_sound_table_stream( + vm.subject, stream_id, file_path, stream_info, &handle), "audio_res.replace_sound_table_stream"); return LuaSoundTableHandle(handle); } -std::tuple add_sound_table_stream( - char const* file_path, - std::optional info, - lua_State* state, - Vm& vm) { +std::tuple add_sound_table_stream(char const* file_path, + std::optional info, lua_State* state, Vm& vm) { AudioSoundTableStreamInfo const* stream_info = nullptr; if (info.has_value()) { stream_info = &*info; @@ -84,15 +79,14 @@ std::tuple add_sound_table_stream( uint16_t out_stream_id; AudioSoundTableHandle handle; - check_result( - state, - svc_audio_res->add_sound_table_stream(vm.subject, file_path, stream_info, &handle, &out_stream_id), + check_result(state, + svc_audio_res->add_sound_table_stream( + vm.subject, file_path, stream_info, &handle, &out_stream_id), "audio_res.add_sound_table_stream"); - return { out_stream_id, LuaSoundTableHandle(handle) }; + return {out_stream_id, LuaSoundTableHandle(handle)}; } - } // namespace luau_runtime::services::audio_res namespace luabridge { @@ -130,10 +124,9 @@ DEFINE_STRING_ENUM(StreamPan, kStreamPanNames); TypeResult Stack::get( lua_State* L, int index) { - auto const ref = LuaRef::fromStack(L, index); - return AudioSoundTableEffectInfo { + return AudioSoundTableEffectInfo{ .priority = ref["priority"], .volume = ref["volume"], .pitch = ref["pitch"], @@ -153,7 +146,6 @@ TypeResult Stack::get( Result Stack::push( lua_State* L, const AudioSoundTableEffectInfo& value) { - LuaRef const ref = newTable(L); ref["priority"] = value.priority; @@ -178,12 +170,12 @@ Result Stack::push( TypeResult Stack::get( lua_State* L, int index) { - auto const ref = LuaRef::fromStack(L, index); - std::array, STREAM_MAX_CHILDREN> const pan_parameters = ref["pan_parameters"]; + std::array, STREAM_MAX_CHILDREN> const pan_parameters = + ref["pan_parameters"]; - auto info = AudioSoundTableStreamInfo { + auto info = AudioSoundTableStreamInfo{ .priority = ref["priority"], .volume = ref["volume"], .stop_on_scene_change = ref["stop_on_scene_change"], @@ -198,7 +190,6 @@ TypeResult Stack::get( Result Stack::push( lua_State* L, const AudioSoundTableStreamInfo& value) { - LuaRef const ref = newTable(L); ref["priority"] = value.priority; diff --git a/mods/luau_runtime/src/services/audio_res/bst.hpp b/mods/luau_runtime/src/services/audio_res/bst.hpp index 62af9cffe2..a4d7cba591 100644 --- a/mods/luau_runtime/src/services/audio_res/bst.hpp +++ b/mods/luau_runtime/src/services/audio_res/bst.hpp @@ -1,59 +1,46 @@ #pragma once -#include "mods/svc/audio_res.h" -#include "../../lua_helpers.hpp" #include "../../lua_bridge_helpers.hpp" +#include "../../lua_helpers.hpp" +#include "mods/svc/audio_res.h" namespace luau_runtime::services::audio_res { class LuaSoundTableHandle final : public BridgeScriptHandle { protected: void unregister_impl(lua_State*, Vm& vm) override; + public: explicit LuaSoundTableHandle(AudioSoundTableHandle handle); }; AudioSoundTableEffectInfo const& default_effect_info(); -LuaSoundTableHandle replace_sound_table_effect( - SoundEffectCategory category_id, - uint16_t effect_id, - std::optional info, - lua_State* state, - Vm& vm); +LuaSoundTableHandle replace_sound_table_effect(SoundEffectCategory category_id, uint16_t effect_id, + std::optional info, lua_State* state, Vm& vm); -std::tuple add_sound_table_effect( - SoundEffectCategory category_id, - std::optional info, - lua_State* state, - Vm& vm); +std::tuple add_sound_table_effect(SoundEffectCategory category_id, + std::optional info, lua_State* state, Vm& vm); AudioSoundTableStreamInfo const& default_stream_info(); -LuaSoundTableHandle replace_sound_table_stream( - uint16_t stream_id, - char const* file_path, - std::optional info, - lua_State* state, - Vm& vm); +LuaSoundTableHandle replace_sound_table_stream(uint16_t stream_id, char const* file_path, + std::optional info, lua_State* state, Vm& vm); std::tuple add_sound_table_stream( - char const* file_path, - std::optional info, - lua_State* state, - Vm& vm); + char const* file_path, std::optional info, lua_State* state, Vm& vm); -} +} // namespace luau_runtime::services::audio_res namespace luabridge { -template<> +template <> struct Stack { [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableEffectInfo& value); [[nodiscard]] static TypeResult get(lua_State* L, int index); }; -template<> +template <> struct Stack { [[nodiscard]] static Result push(lua_State* L, const AudioSoundTableStreamInfo& value); [[nodiscard]] static TypeResult get(lua_State* L, int index); @@ -62,4 +49,4 @@ struct Stack { DECLARE_STRING_ENUM(SoundEffectCategory); DECLARE_STRING_ENUM(StreamPan); -} +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.cpp b/mods/luau_runtime/src/services/audio_res/wsys.cpp index 36c6c988aa..cf4bc19039 100644 --- a/mods/luau_runtime/src/services/audio_res/wsys.cpp +++ b/mods/luau_runtime/src/services/audio_res/wsys.cpp @@ -2,7 +2,7 @@ namespace luau_runtime::services::audio_res { -LuaAudioWaveHandle::LuaAudioWaveHandle(AudioWaveHandle handle) : BridgeScriptHandle(handle) { } +LuaAudioWaveHandle::LuaAudioWaveHandle(AudioWaveHandle handle) : BridgeScriptHandle(handle) {} void LuaAudioWaveHandle::unregister_impl(lua_State*, Vm& vm) { svc_audio_res->remove_wave(vm.subject, handle); @@ -12,7 +12,8 @@ AudioWaveInfo const& default_wave_info() { return *svc_audio_res->default_wave_info; } -LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm) { +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, + std::optional info, lua_State* state, Vm& vm) { AudioWaveInfo const* wave_info = nullptr; if (info.has_value()) { wave_info = &info->info; @@ -20,15 +21,15 @@ LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const AudioWaveHandle handle; - check_result( - state, + check_result(state, svc_audio_res->replace_wave(vm.subject, bank, wave_id, file, wave_info, &handle), "audio_res.replace_wave"); return LuaAudioWaveHandle(handle); } -std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm) { +std::tuple add_wave(AudioWaveBank bank, char const* file, + std::optional info, lua_State* state, Vm& vm) { AudioWaveInfo const* wave_info = nullptr; if (info.has_value()) { wave_info = &info->info; @@ -37,8 +38,7 @@ std::tuple add_wave(AudioWaveBank bank, char const AudioWaveHandle handle; uint16_t out_wave_id; - check_result( - state, + check_result(state, svc_audio_res->add_wave(vm.subject, bank, file, wave_info, &handle, &out_wave_id), "audio_res.replace_wave"); @@ -80,7 +80,7 @@ Result Stack::push(lua_State* L, const AudioRawWave& value) { TypeResult Stack::get(lua_State* L, int index) { auto const ref = LuaRef::fromStack(L, index); - return AudioRawWave { + return AudioRawWave{ .format = ref["format"], .sample_rate = ref["sample_rate"], .sample_value_last = opt_or(ref["sample_value_last"], 0), @@ -97,11 +97,12 @@ Result Stack::push(lua_State* L, const OwnedWaveInfo& value) { TypeResult Stack::get(lua_State* L, int index) { auto const ref = LuaRef::fromStack(L, index); - AudioWaveInfo info { + AudioWaveInfo info{ .base_key = opt_or(ref["base_key"], AUDIO_RES_DEFAULT_KEY), .loop = opt_or(ref["loop"], false), .loop_start_sample = opt_or(ref["loop_start_sample"], 0), - .loop_end_sample = opt_or(ref["loop_end_sample"], std::numeric_limits::max()), + .loop_end_sample = + opt_or(ref["loop_end_sample"], std::numeric_limits::max()), }; std::unique_ptr raw; @@ -110,7 +111,7 @@ TypeResult Stack::get(lua_State* L, int index) { info.raw_wave = raw.get(); } - return OwnedWaveInfo { info, std::move(raw) }; + return OwnedWaveInfo{info, std::move(raw)}; } namespace { @@ -118,21 +119,20 @@ namespace { using namespace std::string_view_literals; constexpr luau_runtime::EnumName kWaveFormatNames[] = { - { AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv }, - { AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv }, - { AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv }, - { AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv }, + {AUDIO_WAVE_FORMAT_ADPCM4, "adpcm4"sv}, + {AUDIO_WAVE_FORMAT_ADPCM2, "adpcm2"sv}, + {AUDIO_WAVE_FORMAT_PCM8, "pcm8"sv}, + {AUDIO_WAVE_FORMAT_PCM16, "pcm16"sv}, }; constexpr luau_runtime::EnumName kWaveBankNames[] = { - { AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv } , - { AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv } , + {AUDIO_WAVE_BANK_SOUND_EFFECTS, "sound_effects"sv}, + {AUDIO_WAVE_BANK_MUSIC_SAMPLES, "music_samples"sv}, }; -} +} // namespace DEFINE_STRING_ENUM(AudioWaveFormat, kWaveFormatNames); DEFINE_STRING_ENUM(AudioWaveBank, kWaveBankNames); - } // namespace luabridge diff --git a/mods/luau_runtime/src/services/audio_res/wsys.hpp b/mods/luau_runtime/src/services/audio_res/wsys.hpp index be7ab06a1c..0d35a9b266 100644 --- a/mods/luau_runtime/src/services/audio_res/wsys.hpp +++ b/mods/luau_runtime/src/services/audio_res/wsys.hpp @@ -1,11 +1,11 @@ #pragma once +#include "LuaBridge/LuaBridge.h" #include "lua.h" #include "lualib.h" -#include "LuaBridge/LuaBridge.h" -#include "../../runtime.hpp" #include "../../lua_helpers.hpp" +#include "../../runtime.hpp" #include "mods/svc/audio_res.h" #include @@ -20,36 +20,41 @@ struct OwnedWaveInfo { class LuaAudioWaveHandle final : public BridgeScriptHandle { protected: void unregister_impl(lua_State*, Vm& vm) override; + public: explicit LuaAudioWaveHandle(AudioWaveHandle handle); }; AudioWaveInfo const& default_wave_info(); -LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, std::optional info, lua_State* state, Vm& vm); -std::tuple add_wave(AudioWaveBank bank, char const* file, std::optional info, lua_State* state, Vm& vm); +LuaAudioWaveHandle replace_wave(AudioWaveBank bank, uint16_t wave_id, char const* file, + std::optional info, lua_State* state, Vm& vm); +std::tuple add_wave(AudioWaveBank bank, char const* file, + std::optional info, lua_State* state, Vm& vm); -} +} // namespace luau_runtime::services::audio_res namespace luabridge { -template<> +template <> struct Stack { [[nodiscard]] static Result push(lua_State* L, const AudioWaveInfo& value); }; -template<> +template <> struct Stack { [[nodiscard]] static Result push(lua_State* L, const AudioRawWave& value); [[nodiscard]] static TypeResult get(lua_State* L, int index); }; -template<> +template <> struct Stack { - [[nodiscard]] static Result push(lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value); - [[nodiscard]] static TypeResult get(lua_State* L, int index); + [[nodiscard]] static Result push( + lua_State* L, const luau_runtime::services::audio_res::OwnedWaveInfo& value); + [[nodiscard]] static TypeResult get( + lua_State* L, int index); }; DECLARE_STRING_ENUM(AudioWaveFormat); DECLARE_STRING_ENUM(AudioWaveBank); -} +} // namespace luabridge diff --git a/mods/luau_runtime/src/services/bindings.cpp b/mods/luau_runtime/src/services/bindings.cpp index af58b446a3..967adc32aa 100644 --- a/mods/luau_runtime/src/services/bindings.cpp +++ b/mods/luau_runtime/src/services/bindings.cpp @@ -338,4 +338,4 @@ int open_texture(lua_State* state) { return 1; } -} // namespace luau_runtime +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/config.cpp b/mods/luau_runtime/src/services/config.cpp index 5bce363a9e..28d6939e4e 100644 --- a/mods/luau_runtime/src/services/config.cpp +++ b/mods/luau_runtime/src/services/config.cpp @@ -243,4 +243,4 @@ int open_config(lua_State* state) { return 1; } -} // namespace luau_runtime +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/services.cpp b/mods/luau_runtime/src/services/services.cpp index 1b8425156f..1933aa4d30 100644 --- a/mods/luau_runtime/src/services/services.cpp +++ b/mods/luau_runtime/src/services/services.cpp @@ -7,20 +7,20 @@ namespace { using namespace std::string_view_literals; constexpr std::pair kModules[] = { - { "dusklight.log"sv, open_log }, - { "dusklight.host"sv, open_host }, - { "dusklight.resource"sv, open_resource }, - { "dusklight.overlay"sv, open_overlay }, - { "dusklight.texture"sv, open_texture }, - { "dusklight.config"sv, open_config }, - { "dusklight.ui"sv, open_ui }, - { "dusklight.audio_res"sv, open_audio_res }, + {"dusklight.log"sv, open_log}, + {"dusklight.host"sv, open_host}, + {"dusklight.resource"sv, open_resource}, + {"dusklight.overlay"sv, open_overlay}, + {"dusklight.texture"sv, open_texture}, + {"dusklight.config"sv, open_config}, + {"dusklight.ui"sv, open_ui}, + {"dusklight.audio_res"sv, open_audio_res}, }; -} +} // namespace ModuleOpenFn module_factory(std::string_view name) { - for (auto [ modName, ptr ] : kModules) { + for (auto [modName, ptr] : kModules) { if (name == modName) { return ptr; } @@ -29,4 +29,4 @@ ModuleOpenFn module_factory(std::string_view name) { return nullptr; } -} +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/services.hpp b/mods/luau_runtime/src/services/services.hpp index e387ca2463..71660cfa69 100644 --- a/mods/luau_runtime/src/services/services.hpp +++ b/mods/luau_runtime/src/services/services.hpp @@ -18,4 +18,4 @@ int open_ui(lua_State* state); ModuleOpenFn module_factory(std::string_view name); -} \ No newline at end of file +} // namespace luau_runtime::services diff --git a/mods/luau_runtime/src/services/ui.cpp b/mods/luau_runtime/src/services/ui.cpp index e73da576aa..221551b574 100644 --- a/mods/luau_runtime/src/services/ui.cpp +++ b/mods/luau_runtime/src/services/ui.cpp @@ -292,14 +292,14 @@ std::vector list_items(lua_State* state, int table, std::vector kControlKindNames[] = { - { UI_CONTROL_BUTTON, "button"sv }, - { UI_CONTROL_TOGGLE, "toggle"sv }, - { UI_CONTROL_NUMBER, "number"sv }, - { UI_CONTROL_STRING, "string"sv }, - { UI_CONTROL_SELECT, "select"sv }, - { UI_CONTROL_COLOR, "color"sv }, - { UI_CONTROL_GROUP, "group"sv }, - { UI_CONTROL_FILE_PICKER, "file_picker"sv }, + {UI_CONTROL_BUTTON, "button"sv}, + {UI_CONTROL_TOGGLE, "toggle"sv}, + {UI_CONTROL_NUMBER, "number"sv}, + {UI_CONTROL_STRING, "string"sv}, + {UI_CONTROL_SELECT, "select"sv}, + {UI_CONTROL_COLOR, "color"sv}, + {UI_CONTROL_GROUP, "group"sv}, + {UI_CONTROL_FILE_PICKER, "file_picker"sv}, }; UiControlKind control_kind(lua_State* state, const std::string& kind) { @@ -307,12 +307,12 @@ UiControlKind control_kind(lua_State* state, const std::string& kind) { } constexpr EnumName kStyleScopeNames[] = { - { UI_SCOPE_PRELAUNCH, "prelaunch"sv }, - { UI_SCOPE_WINDOW, "window"sv }, - { UI_SCOPE_MENU_BAR, "menu_bar"sv }, - { UI_SCOPE_OVERLAY, "overlay"sv }, - { UI_SCOPE_TOUCH_CONTROLS, "touch_controls"sv }, - { UI_SCOPE_GRAPHICS_TUNER, "graphics_tuner"sv }, + {UI_SCOPE_PRELAUNCH, "prelaunch"sv}, + {UI_SCOPE_WINDOW, "window"sv}, + {UI_SCOPE_MENU_BAR, "menu_bar"sv}, + {UI_SCOPE_OVERLAY, "overlay"sv}, + {UI_SCOPE_TOUCH_CONTROLS, "touch_controls"sv}, + {UI_SCOPE_GRAPHICS_TUNER, "graphics_tuner"sv}, }; UiStyleScope style_scope(lua_State* state, const std::string& scope) { @@ -851,4 +851,4 @@ int open_ui(lua_State* state) { return 1; } -} // namespace luau_runtime +} // namespace luau_runtime::services diff --git a/src/dusk/mods/svc/audio_res/audio_res.cpp b/src/dusk/mods/svc/audio_res/audio_res.cpp index 33a9b7be62..3735408106 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.cpp +++ b/src/dusk/mods/svc/audio_res/audio_res.cpp @@ -42,18 +42,15 @@ void sync_audio_replacements() { bst::sync_audio_replacements(); } -} +} // namespace } // namespace audio_res -constinit const ServiceModule g_audioResModule{ - .id = AUDIO_RES_SERVICE_ID, +constinit const ServiceModule g_audioResModule{.id = AUDIO_RES_SERVICE_ID, .majorVersion = AUDIO_RES_SERVICE_MAJOR, .minorVersion = AUDIO_RES_SERVICE_MINOR, .service = &audio_res::s_audioResService, .modDetached = audio_res::mod_detached, .lifecycleApplied = audio_res::sync_audio_replacements, - .frameEnd = audio_res::frame_end - }; -}; - + .frameEnd = audio_res::frame_end}; +}; // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/audio_res/audio_res.hpp b/src/dusk/mods/svc/audio_res/audio_res.hpp index 767f1e2d6f..6e7b04e47b 100644 --- a/src/dusk/mods/svc/audio_res/audio_res.hpp +++ b/src/dusk/mods/svc/audio_res/audio_res.hpp @@ -15,25 +15,15 @@ void sync_audio_replacements(); extern AudioWaveInfo const default_wave_info; -ModResult insert_replace_wave( - ModContext* ctx, - AudioWaveBank bank, - u16 wave_id, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle); +ModResult insert_replace_wave(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle); -ModResult insert_add_wave( - ModContext* ctx, - AudioWaveBank bank, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle, - u16* out_wave_id); +ModResult insert_add_wave(ModContext* ctx, AudioWaveBank bank, char const* file_name, + AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle, u16* out_wave_id); ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle); -} +} // namespace wsys namespace bst { @@ -43,38 +33,24 @@ void sync_audio_replacements(); extern AudioSoundTableEffectInfo const default_effect_info; -ModResult replace_sound_table_effect( - ModContext* ctx, - SoundEffectCategory category_id, - uint16_t effect_id, - AudioSoundTableEffectInfo const* info, - AudioSoundTableHandle* out_handle); +ModResult replace_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + uint16_t effect_id, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle); -ModResult add_sound_table_effect( - ModContext* ctx, - SoundEffectCategory category_id, - AudioSoundTableEffectInfo const* info, - AudioSoundTableHandle* out_handle, +ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_id, + AudioSoundTableEffectInfo const* info, 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 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, +ModResult add_sound_table_stream(ModContext* ctx, char const* file_path, + AudioSoundTableStreamInfo const* info, AudioSoundTableHandle* out_handle, uint16_t* out_stream_id); ModResult remove_sound_table(ModContext* ctx, AudioSoundTableHandle handle); -} +} // namespace bst -} +} // namespace dusk::mods::svc::audio_res diff --git a/src/dusk/mods/svc/audio_res/bst.cpp b/src/dusk/mods/svc/audio_res/bst.cpp index 28a78f577d..917697bed7 100644 --- a/src/dusk/mods/svc/audio_res/bst.cpp +++ b/src/dusk/mods/svc/audio_res/bst.cpp @@ -17,16 +17,16 @@ aurora::Module Log("dusk::mods::svc::audio_res"); SlotMap> sound_replacements; std::array sound_effect_id_allocator = { - PlainIdAllocator(0x1000), // SYSTEM_SE - PlainIdAllocator(0x1000), // PLAYER_VOICE - PlainIdAllocator(0x1000), // PLAYER_SE - PlainIdAllocator(0x1000), // FOOTNOTE_SE - PlainIdAllocator(0x1000), // COLLISION_SE - PlainIdAllocator(0x1000), // CHARA_VOICE - PlainIdAllocator(0x1000), // CHARA_SE - PlainIdAllocator(0x1000), // ENEMY_SE - PlainIdAllocator(0x1000), // OBJECT_SE - PlainIdAllocator(0x1000), // ENV_SE + PlainIdAllocator(0x1000), // SYSTEM_SE + PlainIdAllocator(0x1000), // PLAYER_VOICE + PlainIdAllocator(0x1000), // PLAYER_SE + PlainIdAllocator(0x1000), // FOOTNOTE_SE + PlainIdAllocator(0x1000), // COLLISION_SE + PlainIdAllocator(0x1000), // CHARA_VOICE + PlainIdAllocator(0x1000), // CHARA_SE + PlainIdAllocator(0x1000), // ENEMY_SE + PlainIdAllocator(0x1000), // OBJECT_SE + PlainIdAllocator(0x1000), // ENV_SE }; PlainIdAllocator stream_id_allocator(0x1000); @@ -40,7 +40,8 @@ uint8_t volume_to_item(float volume) { return static_cast(volume * 127); } -absl::flat_hash_map> active_se_replacements; +absl::flat_hash_map> + active_se_replacements; absl::flat_hash_map> active_stream_replacements; std::mutex active_replacements_mutex; @@ -74,9 +75,11 @@ SoundEffectReplacementSlot::SoundEffectReplacementSlot( sw_bit |= (info.doppler_power << SOUND_SW_DOPPLER_POWER_OFFSET) & SOUND_SW_DOPPLER_POWER_MASK; if (info.volume_dist_class < 8) { - sw_bit |= (info.volume_dist_class << SOUND_SW_VOL_DIST_BIT_OFFSET) & SOUND_SW_VOL_DIST_BIT_MASK; + sw_bit |= + (info.volume_dist_class << SOUND_SW_VOL_DIST_BIT_OFFSET) & SOUND_SW_VOL_DIST_BIT_MASK; } else { - sw_bit |= ((info.volume_dist_class - 8) << SOUND_SW_VOL_DIST_BIT_2_OFFSET) & SOUND_SW_VOL_DIST_BIT_2_MASK; + sw_bit |= ((info.volume_dist_class - 8) << SOUND_SW_VOL_DIST_BIT_2_OFFSET) & + SOUND_SW_VOL_DIST_BIT_2_MASK; } if (info.clamp_min_volume) @@ -140,8 +143,7 @@ std::shared_ptr get_override_for_se(JAISoundID id) { std::lock_guard lock(active_replacements_mutex); SoundEffectKey const key( - static_cast(id.id_.info.type.parts.groupID), - id.id_.info.waveID); + static_cast(id.id_.info.type.parts.groupID), id.id_.info.waveID); auto const entry = active_se_replacements.find(key); if (entry == active_se_replacements.end()) { @@ -181,13 +183,14 @@ void frame_end() { void sync_audio_replacements() { sound_replacements_dirty = false; - absl::flat_hash_map> new_se_replacements; + absl::flat_hash_map> + new_se_replacements; absl::flat_hash_map> new_stream_replacements; for (auto const& mod : ModLoader::instance().active_mods()) { sound_replacements.for_each([&](auto, auto const& entry) { if (entry.owner != &mod) { - return; + return; } std::shared_ptr const& value = entry.value; @@ -212,7 +215,8 @@ void sync_audio_replacements() { } static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCategory category_id, - uint16_t effect_id, bool mod_defined, AudioSoundTableEffectInfo const* info, AudioSoundTableHandle* out_handle) { + uint16_t effect_id, bool mod_defined, AudioSoundTableEffectInfo const* info, + AudioSoundTableHandle* out_handle) { if (out_handle != nullptr) { *out_handle = 0; } @@ -226,7 +230,8 @@ static ModResult insert_sound_table_effect_core(ModContext* ctx, SoundEffectCate info = &default_effect_info; } - auto slot = std::make_shared(mod_defined, effect_id, category_id, *info); + auto slot = + std::make_shared(mod_defined, effect_id, category_id, *info); sound_replacements_dirty = true; auto const handle = sound_replacements.emplace(*mod, std::move(slot)); @@ -247,7 +252,6 @@ 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) { - if (out_effect_id == nullptr || !validate_category(category_id)) { return MOD_INVALID_ARGUMENT; } @@ -257,7 +261,8 @@ ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_i auto& allocator = sound_effect_id_allocator[category_id]; auto const effect_id = allocator.alloc(); - auto const result = insert_sound_table_effect_core(ctx, category_id, effect_id, true, info, out_handle); + auto const result = + insert_sound_table_effect_core(ctx, category_id, effect_id, true, info, out_handle); if (result != MOD_OK) { allocator.free(effect_id); } @@ -267,12 +272,8 @@ ModResult add_sound_table_effect(ModContext* ctx, SoundEffectCategory category_i return result; } -static ModResult insert_sound_table_stream_core( - ModContext* ctx, - uint16_t stream_id, - bool mod_defined, - char const* file_path, - AudioSoundTableStreamInfo const* info, +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) { *out_handle = 0; @@ -298,30 +299,24 @@ static ModResult insert_sound_table_stream_core( return MOD_OK; } -AudioSoundTableStreamInfo const default_stream_info( - 128, - 1, - { - STREAM_PAN_LEFT, - STREAM_PAN_RIGHT - }); +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, +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, file_path, info, out_handle); } -ModResult add_sound_table_stream(ModContext* ctx, - char const* file_path, AudioSoundTableStreamInfo const* info, - AudioSoundTableHandle* out_handle, uint16_t* out_stream_id) { +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; } auto const stream_id = stream_id_allocator.alloc(); - auto const result = insert_sound_table_stream_core(ctx, stream_id, true, file_path, 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 d0619ad80f..0db6bd7c1f 100644 --- a/src/dusk/mods/svc/audio_res/bst.hpp +++ b/src/dusk/mods/svc/audio_res/bst.hpp @@ -35,7 +35,8 @@ struct StreamReplacementSlot final : SoundTableReplacementSlot { std::string file_path; bool stop_on_scene_change; - StreamReplacementSlot(bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info); + StreamReplacementSlot( + bool mod_defined, u16 id, char const* file_path, const AudioSoundTableStreamInfo& info); ~StreamReplacementSlot() override = default; @@ -60,4 +61,4 @@ std::shared_ptr get_override_for(JAISoundID id); std::shared_ptr get_override_for_se(JAISoundID id); std::shared_ptr get_override_for_stream(JAISoundID id); -} +} // namespace dusk::mods::svc::audio_res::bst diff --git a/src/dusk/mods/svc/audio_res/opus.cpp b/src/dusk/mods/svc/audio_res/opus.cpp index f885a6e5c6..afa346cfba 100644 --- a/src/dusk/mods/svc/audio_res/opus.cpp +++ b/src/dusk/mods/svc/audio_res/opus.cpp @@ -1,10 +1,10 @@ #if DUSK_OPUS -#include "wsys.hpp" #include "aurora/lib/logging.hpp" #include "dusk/mod_loader.hpp" #include "helpers/cast.hpp" #include "opusfile.h" +#include "wsys.hpp" namespace { @@ -13,19 +13,16 @@ aurora::Module Log("dusk::mods::svc::audio_res::wsys::opus"); struct OpusHandle { OggOpusFile* file; - ~OpusHandle() { - op_free(file); - } + ~OpusHandle() { op_free(file); } - operator OggOpusFile*() const { - return file; - } + operator OggOpusFile*() const { return file; } }; -} +} // namespace namespace dusk::mods::svc::audio_res::wsys { -ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { +ModResult load_opus( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { OpusHead head; const int result = op_test(&head, fileData.data(), fileData.size()); if (result != 0) { @@ -33,26 +30,30 @@ ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std: } if (head.channel_count != 1) { - Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", mod.metadata.id, slot.bundle_path, head.channel_count); + Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", + mod.metadata.id, slot.bundle_path, head.channel_count); return MOD_INVALID_ARGUMENT; } if (head.stream_count != 1) { - Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, slot.bundle_path); + Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, + slot.bundle_path); return MOD_INVALID_ARGUMENT; } int error; auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error); if (error != 0) { - Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, slot.bundle_path, error); + Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, + slot.bundle_path, error); return MOD_ERROR; } const OpusHandle handle(handle_raw); auto const length = op_pcm_total(handle, -1); if (length < 0) { - Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, slot.bundle_path); + Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, + slot.bundle_path); return MOD_ERROR; } @@ -62,13 +63,12 @@ ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std: std::span span_pcm(pcm_buffer.data); while (!span_pcm.empty()) { - auto read_result = op_read( - handle, - span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), - nullptr); + auto read_result = + op_read(handle, span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()), nullptr); if (read_result <= 0) { - Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, slot.bundle_path, read_result); + Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, + slot.bundle_path, read_result); return MOD_ERROR; } @@ -82,12 +82,12 @@ ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std: pcm_buffer.be_swap(); slot.data = std::make_shared(std::move(pcm_buffer)); - slot.sample_rate = 48000; // Opus always decodes at 48 kHz. + slot.sample_rate = 48000; // Opus always decodes at 48 kHz. slot.sample_count = length; slot.format = AUDIO_WAVE_FORMAT_PCM16; return MOD_OK; } -} +} // namespace dusk::mods::svc::audio_res::wsys #endif diff --git a/src/dusk/mods/svc/audio_res/wave.cpp b/src/dusk/mods/svc/audio_res/wave.cpp index cb1f74752b..7ab8ac8773 100644 --- a/src/dusk/mods/svc/audio_res/wave.cpp +++ b/src/dusk/mods/svc/audio_res/wave.cpp @@ -1,7 +1,7 @@ -#include "wsys.hpp" #include "aurora/lib/logging.hpp" #include "dusk/mod_loader.hpp" #include "helpers/alignment.hpp" +#include "wsys.hpp" namespace { @@ -13,8 +13,8 @@ struct ChunkHeader { }; struct RiffChunk { - ChunkHeader header; // magic "RIFF" - char id[4]; // "WAVE" + ChunkHeader header; // magic "RIFF" + char id[4]; // "WAVE" // Data after chunk. }; @@ -35,11 +35,12 @@ bool check_four_cc(char const (&field)[4], char const (&expected)[5]) { return memcmp(field, expected, 4) == 0; } -} +} // namespace namespace dusk::mods::svc::audio_res::wsys { -ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { +ModResult load_wav( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData) { using namespace helpers; if (fileData.size() < sizeof(RiffChunk)) { @@ -55,7 +56,8 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: return MOD_UNSUPPORTED; } - if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size() || riffChunk.header.size < 4) { + if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size() || riffChunk.header.size < 4) + { Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path); return MOD_INVALID_ARGUMENT; } @@ -69,29 +71,34 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: if (check_four_cc(chunkHeader.magic, "fmt ")) { if (has_read_fmt) { - Log.error("[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); + Log.error( + "[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path); return MOD_INVALID_ARGUMENT; } if (waveData.size() < sizeof(FmtPcmChunkData)) { - Log.error("[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); + Log.error( + "[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path); return MOD_INVALID_ARGUMENT; } auto const fmtChunk = read_unaligned(&waveData[0]); if (fmtChunk.nChannels != 1) { - Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, slot.bundle_path); + Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, + slot.bundle_path); return MOD_INVALID_ARGUMENT; } if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) { - Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, + slot.bundle_path); return MOD_INVALID_ARGUMENT; } if (fmtChunk.wBitsPerSample != 16) { - Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path); + Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, + slot.bundle_path); return MOD_INVALID_ARGUMENT; } @@ -100,7 +107,8 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: } else if (check_four_cc(chunkHeader.magic, "data")) { if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) { - Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, slot.bundle_path); + Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, + slot.bundle_path); return MOD_INVALID_ARGUMENT; } @@ -142,4 +150,4 @@ ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std:: return MOD_OK; } -} +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/audio_res/wsys.cpp b/src/dusk/mods/svc/audio_res/wsys.cpp index 2ce050c72e..689df9936f 100644 --- a/src/dusk/mods/svc/audio_res/wsys.cpp +++ b/src/dusk/mods/svc/audio_res/wsys.cpp @@ -1,7 +1,7 @@ -#include "audio_res.hpp" #include "wsys.hpp" -#include "../internal.hpp" #include "../id_allocator.hpp" +#include "../internal.hpp" +#include "audio_res.hpp" #include "aurora/lib/logging.hpp" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/mods/loader/loader.hpp" @@ -30,10 +30,12 @@ PlainIdAllocator sound_effect_id_allocator(5'000); PlainIdAllocator music_sample_id_allocator(1'000); PlainIdAllocator& id_allocator_for_bank(AudioWaveBank const bank) { - return bank == AUDIO_WAVE_BANK_SOUND_EFFECTS ? 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) { +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) { @@ -51,7 +53,8 @@ bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t } if (actual_size % bytes_per_block != 0) { - Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); + Log.error( + "[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path); return false; } @@ -116,7 +119,7 @@ JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) { 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.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); @@ -142,13 +145,8 @@ bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) { return true; } -ModResult insert_replace_wave_core( - ModContext* ctx, - AudioWaveBank bank, - u16 wave_id, - bool mod_defined, - char const* file_name, - AudioWaveInfo const* wave_info, +ModResult insert_replace_wave_core(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + bool mod_defined, char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle) { if (out_handle != nullptr) { *out_handle = 0; @@ -206,13 +204,13 @@ ModResult insert_replace_wave_core( return MOD_OK; } - -} +} // namespace 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); +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); @@ -226,25 +224,13 @@ ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) { 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) { - +ModResult insert_replace_wave(ModContext* ctx, AudioWaveBank bank, u16 wave_id, + char const* file_name, AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle) { return insert_replace_wave_core(ctx, bank, wave_id, false, file_name, wave_info, out_handle); } -ModResult insert_add_wave( - ModContext* ctx, - AudioWaveBank bank, - char const* file_name, - AudioWaveInfo const* wave_info, - AudioWaveHandle* out_handle, - u16* out_wave_id) { - +ModResult insert_add_wave(ModContext* ctx, AudioWaveBank bank, char const* file_name, + AudioWaveInfo const* wave_info, AudioWaveHandle* out_handle, u16* out_wave_id) { if (out_wave_id == nullptr) { return MOD_INVALID_ARGUMENT; } @@ -258,7 +244,8 @@ ModResult insert_add_wave( auto& allocator = id_allocator_for_bank(bank); auto const new_wave_id = allocator.alloc(); - auto const result = insert_replace_wave_core(ctx, bank, new_wave_id, true, file_name, wave_info, out_handle); + auto const result = + insert_replace_wave_core(ctx, bank, new_wave_id, true, file_name, wave_info, out_handle); if (result != MOD_OK) { allocator.free(new_wave_id); } @@ -280,13 +267,12 @@ void sync_audio_replacements() { for (auto const& mod : ModLoader::instance().active_mods()) { s_waveReplacements.for_each([&](auto, auto entry) { - if (entry.owner != &mod) { - return; - } + if (entry.owner != &mod) { + return; + } auto const wave_info = wave_info_from_slot(entry.value); - new_map.emplace( - AudioWaveKey(entry.value.bank, entry.value.wave_id), + new_map.emplace(AudioWaveKey(entry.value.bank, entry.value.wave_id), AudioWaveReplacementValue(wave_info, entry.value.data)); }); } @@ -330,5 +316,4 @@ void SampleDataPcm16::be_swap() { } } - -} \ No newline at end of file +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/audio_res/wsys.hpp b/src/dusk/mods/svc/audio_res/wsys.hpp index be438dcf42..d609d04206 100644 --- a/src/dusk/mods/svc/audio_res/wsys.hpp +++ b/src/dusk/mods/svc/audio_res/wsys.hpp @@ -15,9 +15,7 @@ namespace dusk::mods::svc::audio_res::wsys { struct SampleDataBuf { virtual ~SampleDataBuf() = default; [[nodiscard]] virtual std::span get_data() const = 0; - [[nodiscard]] size_t size() const { - return get_data().size(); - } + [[nodiscard]] size_t size() const { return get_data().size(); } }; // Making a separate type for this rather than just using a vector @@ -60,12 +58,13 @@ struct AudioWaveKey { } [[nodiscard]] bool operator==(const AudioWaveKey& other) const { - return other.bank == bank && other.wave_id == wave_id; + return other.bank == bank && other.wave_id == wave_id; } }; struct AudioWaveReplacementValue : JASWaveHandle { - AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) : wave_info(wave_info), data(std::move(data)) {}; + AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr data) + : wave_info(wave_info), data(std::move(data)) {}; ~AudioWaveReplacementValue() override; [[nodiscard]] const JASWaveInfo* getWaveInfo() const override; [[nodiscard]] intptr_t getWavePtr() const override; @@ -99,9 +98,12 @@ struct RuntimeWaveReplacementSlot { std::shared_ptr data; }; -using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +using ContainerLoadFunction = ModResult (*)( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_wav( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); +ModResult load_opus( + LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span fileData); -} \ No newline at end of file +} // namespace dusk::mods::svc::audio_res::wsys diff --git a/src/dusk/mods/svc/id_allocator.hpp b/src/dusk/mods/svc/id_allocator.hpp index 49eecf6f1c..2ee1118028 100644 --- a/src/dusk/mods/svc/id_allocator.hpp +++ b/src/dusk/mods/svc/id_allocator.hpp @@ -1,23 +1,22 @@ #pragma once -#include #include +#include namespace dusk::mods::svc { [[noreturn]] void id_allocator_exhausted(); -template requires std::is_integral_v +template + requires std::is_integral_v class PlainIdAllocator { std::vector reusable; T alloc_next; T alloc_max; public: - constexpr explicit PlainIdAllocator(T first, T max=std::numeric_limits::max()) - : alloc_next(first), alloc_max(max) { - - } + constexpr explicit PlainIdAllocator(T first, T max = std::numeric_limits::max()) + : alloc_next(first), alloc_max(max) {} T alloc() { if (reusable.empty()) { @@ -40,4 +39,4 @@ public: } }; -} \ No newline at end of file +} // namespace dusk::mods::svc