Merge pull request #2241 from TwilitRealm/26-07-25-audio-replacements

Mod API: audio_res service for audio replacements
This commit is contained in:
TakaRikka
2026-09-12 14:26:00 -07:00
committed by GitHub
112 changed files with 4734 additions and 765 deletions
+26
View File
@@ -92,6 +92,8 @@ 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" OFF)
set(DUSK_HAS_FUNCHOOK OFF)
if (DUSK_ENABLE_CODE_MODS AND (NOT APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Darwin"))
@@ -189,6 +191,25 @@ FetchContent_Declare(picosha2
)
set(_fetch_content_deps miniz picosha2)
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_HAS_FUNCHOOK)
message(STATUS "dusklight: Fetching funchook")
# cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
@@ -261,6 +282,10 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p
if (DUSK_HAS_FUNCHOOK)
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 (WIN32)
list(APPEND GAME_LIBS Ws2_32)
@@ -517,6 +542,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
add_subdirectory(mods/custom_actor_demo)
add_subdirectory(mods/cosmetics)
add_subdirectory(mods/randomizer)
add_subdirectory(mods/audio_mod)
endif ()
if (APPLE)
+58
View File
@@ -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
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

+26
View File
@@ -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> {
T* offset : u32 [[inline]];
};
struct JAUSeqCollectionTable {
u32 mEntryCount;
u32 mBmsOffset[mEntryCount];
};
struct JAUSeqCollectionData {
type::Magic<"SC"> mMagic;
u16 mNumSoundCategories;
u32 mSectionSize;
Offset<JAUSeqCollectionTable> mTableOffsets[mNumSoundCategories];
};
JAUSeqCollectionData data @ 0x0;
+71
View File
@@ -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> {
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<TGroup> mGroupOffsets[mNumGroups];
};
struct Root {
u32 mSectionNumber;
Offset<TSection> mSectionOffsets[mSectionNumber];
};
struct THeader {
type::Magic<"BST "> mMagic;
padding[8];
Root* mRoot : u32;
};
THeader header @ 0x0;
+228
View File
@@ -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> {
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<TWave> 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<TWaveArchive> 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<TCtrlWave> 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<TCtrlScene> 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 originally does not use this value itself, but Dusklight does rely on it.
*/
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!");
+241
View File
@@ -0,0 +1,241 @@
# 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.<br/>**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. 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.
*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.* For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat)
### `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 (01), left (10), or right (11).
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.
+11
View File
@@ -1507,6 +1507,14 @@ set(DUSK_FILES
src/dusk/mods/manifest.cpp
src/dusk/mods/manifest.hpp
src/dusk/mods/svc/actor.cpp
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
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
@@ -1522,6 +1530,8 @@ set(DUSK_FILES
src/dusk/mods/svc/websocket.cpp
src/dusk/mods/svc/item.cpp
src/dusk/mods/svc/item.hpp
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
@@ -1653,4 +1663,5 @@ set(DUSK_FILES
src/helpers/endian.cpp
src/helpers/offset_ptr.cpp
src/helpers/string.cpp
src/helpers/cast.cpp
)
+85 -45
View File
@@ -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,10 @@ struct Z2AudioCamera {
f32 getCamDist() const { return mCamDist; }
/* 0x00 */ JGeometry::TPosition3f32 field_0x0;
/* 0x00 */ JGeometry::TPosition3f32 mViewMatrix;
/* 0x30 */ JGeometry::TVec3<f32> mVel;
/* 0x3C */ JGeometry::TVec3<f32> mPos;
/* 0x48 */ JGeometry::TVec3<f32> field_0x48;
/* 0x48 */ JGeometry::TVec3<f32> mLastPos;
/* 0x54 */ f32 mFovySin;
/* 0x58 */ f32 mVolCenterZ;
/* 0x5C */ f32 mTargetVolume;
@@ -83,14 +85,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 +104,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<f32> field_0x00;
/* 0x0C */ f32 field_0xC;
/* 0x10 */ f32 field_0x10;
/* 0x00 */ JGeometry::TVec3<f32> 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 +210,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 +239,8 @@ struct Z2Audible : public JAIAudible, public JASPoolAllocObject<Z2Audible> {
/* 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<Z2Audience> {
@@ -223,7 +262,8 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance<Z2Audience> {
virtual ~Z2Audience();
virtual JAIAudible* newAudible(const JGeometry::TVec3<f32>& pos, JAISoundID soundID,
const JGeometry::TVec3<f32>*, u32);
const JGeometry::TVec3<f32>*, 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);
@@ -237,7 +277,7 @@ struct Z2Audience : public JAIAudience, public JASGlobalInstance<Z2Audience> {
}
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; }
+14 -8
View File
@@ -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<Z2SoundInfo> {
public:
Z2SoundInfo() : JAISoundInfo(true), JAUSoundInfo(true), JASGlobalInstance<Z2SoundInfo>(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 {
+1 -1
View File
@@ -41,7 +41,7 @@ public:
/* 0x004 */ JAISeMgr seMgr_;
/* 0x728 */ JAISeqMgr seqMgr_;
/* 0x79C */ JAIStreamMgr streamMgr_;
/* 0x80C */ JAISoundID soundID_;
/* 0x80C */ JAISoundID bgmMuter;
}; // Size: 0x810
#if VERSION != VERSION_SHIELD_DEBUG
+19
View File
@@ -0,0 +1,19 @@
#pragma once
namespace dusk::helpers {
/**
* 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 <typename T>
requires std::is_trivially_copyable_v<T>
[[nodiscard]] constexpr T read_unaligned(u8 const* ptr) {
T copy;
memcpy(&copy, ptr, sizeof(T));
return copy;
}
} // namespace dusk::helpers
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <limits>
#include <span>
#include <utility>
/**
* 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();
} // namespace _impl
template <typename T>
concept IntCastable = std::is_integral_v<T> && std::is_trivially_copyable_v<T>;
/**
* 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 <IntCastable Source>
struct bounded_cast {
Source src;
template <IntCastable Target>
[[nodiscard]] constexpr operator Target() const {
if (std::cmp_greater(src, std::numeric_limits<Target>::max())) [[unlikely]] {
_impl::overrun_high();
}
if (std::cmp_less(src, std::numeric_limits<Target>::min())) [[unlikely]] {
_impl::overrun_low();
}
return static_cast<Target>(src);
}
};
} // namespace dusk::helpers::cast
+1
View File
@@ -285,6 +285,7 @@ inline void be_swap(Mtx& val) {
}
}
#define LE(T) T
#define BE(T) BE<T>
#define BE_HOST(T) (T.host())
#else
@@ -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<f32> const&, JAISoundID,
JGeometry::TVec3<f32> const*, u32) = 0;
JGeometry::TVec3<f32> 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;
+1 -1
View File
@@ -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<f32>* posPtr, JAIAudience* audience);
void JAISeMgr_startID_(JAISoundID id, const JGeometry::TVec3<f32>* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr<SoundEffectReplacementSlot> replacement));
bool prepare_getSeqData_();
void prepare_();
@@ -104,7 +104,7 @@ public:
JAISe* newSe_(int category, u32 priority);
void calc();
void mixOut();
bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3<f32>* posPtr);
bool startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry::TVec3<f32>* posPtr IF_DUSK_ARG(std::shared_ptr<dusk::mods::svc::audio_res::bst::SoundEffectReplacementSlot> replacement));
int getNumActiveSe() const;
/* 0x004 */ JAISoundActivity mSoundActivity;
@@ -6,7 +6,16 @@
#include "JSystem/JUtility/JUTAssert.h"
#include "global.h"
#include "helpers/endian.h"
#include <cstdint>
#if TARGET_PC
#include <memory>
namespace dusk::mods::svc::audio_res::bst {
struct SoundTableReplacementSlot;
struct SoundEffectReplacementSlot;
struct StreamReplacementSlot;
}
#endif
class JAISound;
@@ -101,7 +110,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 +129,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;
@@ -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<f32>* posPtr, JAIAudience* audience);
void start_JAISound_(JAISoundID id, const JGeometry::TVec3<f32>* posPtr, JAIAudience* audience IF_DUSK_ARG(std::shared_ptr<SoundTableReplacementSlot> replacement));
bool acceptsNewAudible() const;
void newAudible(const JGeometry::TVec3<f32>&, JGeometry::TVec3<f32> 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) {
@@ -309,7 +329,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 +366,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();
}
@@ -380,6 +400,9 @@ public:
/* 0x34 */ u32 priority_;
/* 0x38 */ s32 count_;
/* 0x3C */ JAISoundParams params_;
#if TARGET_PC
std::shared_ptr<SoundTableReplacementSlot> replacement;
#endif
}; // Size: 0x98
STATIC_ASSERT(sizeof(JAISound) == 0x98);

Some files were not shown because too many files have changed in this diff Show More