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) { }