mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-21 05:43:42 -07:00
Backport 2ship streamed audio (#5457)
Some checks are pending
Some checks are pending
* Bring over changes from 2ship # Conflicts: # .github/workflows/apt-deps.txt # soh/CMakeLists.txt # soh/soh/resource/importer/AudioSampleFactory.h # soh/soh/resource/importer/AudioSequenceFactory.cpp # soh/soh/resource/importer/AudioSequenceFactory.h # soh/soh/resource/importer/AudioSoundFontFactory.h * Update xml format * Format and fix mixer for Windows * Fixes for new LUS * Good ole clang-format
This commit is contained in:
parent
9e686ae6f6
commit
e15f8d395b
47 changed files with 8478 additions and 19613 deletions
|
@ -283,8 +283,17 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|||
set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY ${MSVC_RUNTIME_LIBRARY_STR})
|
||||
endif()
|
||||
################################################################################
|
||||
# Compile definitions
|
||||
# Find/download Dr Libs (For custom audio)
|
||||
################################################################################
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
dr_libs
|
||||
GIT_REPOSITORY https://github.com/mackron/dr_libs.git
|
||||
GIT_TAG da35f9d6c7374a95353fd1df1d394d44ab66cf01
|
||||
)
|
||||
FetchContent_MakeAvailable(dr_libs)
|
||||
|
||||
find_package(SDL2)
|
||||
set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS})
|
||||
|
||||
|
@ -303,6 +312,10 @@ if (ESPEAK)
|
|||
add_compile_definitions(ESPEAK=1)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Compile definitions
|
||||
################################################################################
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE assets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/
|
||||
|
@ -335,6 +348,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE assets
|
|||
${SDL2-INCLUDE}
|
||||
${SDL2-NET-INCLUDE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/assets/
|
||||
${dr_libs_SOURCE_DIR}
|
||||
.
|
||||
)
|
||||
|
||||
|
@ -586,6 +600,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|||
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
-pthread
|
||||
#-fsanitize=address
|
||||
-Wl,-export-dynamic
|
||||
)
|
||||
endif()
|
||||
|
@ -626,6 +641,15 @@ endif()
|
|||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
find_package(glfw3 REQUIRED)
|
||||
find_package(Ogg CONFIG REQUIRED)
|
||||
link_libraries(Ogg::ogg)
|
||||
|
||||
find_package(Vorbis CONFIG REQUIRED)
|
||||
link_libraries(Vorbis::vorbisfile)
|
||||
find_package(Opus CONFIG REQUIRED)
|
||||
link_libraries(Opus::opus)
|
||||
find_package(OpusFile CONFIG REQUIRED)
|
||||
link_libraries(OpusFile::opusfile CONFIG REQUIRED)
|
||||
if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64")
|
||||
set(ADDITIONAL_LIBRARY_DEPENDENCIES
|
||||
"libultraship;"
|
||||
|
@ -639,6 +663,12 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|||
"imm32;"
|
||||
"version;"
|
||||
"setupapi"
|
||||
"Ogg::ogg"
|
||||
"Opus::opus"
|
||||
"Vorbis::vorbis"
|
||||
"Vorbis::vorbisenc"
|
||||
"Vorbis::vorbisfile"
|
||||
"OpusFile::opusfile"
|
||||
)
|
||||
elseif("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "Win32")
|
||||
set(ADDITIONAL_LIBRARY_DEPENDENCIES
|
||||
|
@ -679,10 +709,21 @@ else()
|
|||
find_package(SDL2)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(Ogg REQUIRED)
|
||||
find_package(Vorbis REQUIRED)
|
||||
find_package(Opus REQUIRED)
|
||||
find_package(OpusFile REQUIRED)
|
||||
set(ADDITIONAL_LIBRARY_DEPENDENCIES
|
||||
"libultraship;"
|
||||
"ZAPDLib;"
|
||||
SDL2::SDL2
|
||||
"Ogg::ogg"
|
||||
"Vorbis::vorbis"
|
||||
"Vorbis::vorbisenc"
|
||||
"Vorbis::vorbisfile"
|
||||
"Opus::opus"
|
||||
"Opusfile::Opusfile"
|
||||
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net>"
|
||||
${CMAKE_DL_LIBS}
|
||||
Threads::Threads
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,10 @@
|
|||
#ifndef Z64_AUDIO_H
|
||||
#define Z64_AUDIO_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <endianness.h>
|
||||
|
||||
#define MK_CMD(b0,b1,b2,b3) ((((b0) & 0xFF) << 0x18) | (((b1) & 0xFF) << 0x10) | (((b2) & 0xFF) << 0x8) | (((b3) & 0xFF) << 0))
|
||||
|
@ -24,8 +28,8 @@
|
|||
|
||||
//#define MAX_SEQUENCES 0x800
|
||||
extern size_t sequenceMapSize;
|
||||
|
||||
extern char* fontMap[256];
|
||||
extern size_t fontMapSize;
|
||||
extern char** fontMap;
|
||||
|
||||
#define MAX_AUTHENTIC_SEQID 110
|
||||
|
||||
|
@ -54,7 +58,8 @@ typedef enum {
|
|||
/* 2 */ CODEC_S16_INMEMORY,
|
||||
/* 3 */ CODEC_SMALL_ADPCM,
|
||||
/* 4 */ CODEC_REVERB,
|
||||
/* 5 */ CODEC_S16
|
||||
/* 5 */ CODEC_S16,
|
||||
/* 6 */ CODEC_OPUS,
|
||||
} SampleCodec;
|
||||
|
||||
typedef enum {
|
||||
|
@ -117,13 +122,14 @@ typedef struct {
|
|||
/* 0x2 */ s16 arg;
|
||||
} AdsrEnvelope; // size = 0x4
|
||||
|
||||
typedef struct {
|
||||
/* 0x00 */ uintptr_t start;
|
||||
/* 0x04 */ uintptr_t end;
|
||||
/* 0x08 */ u32 count;
|
||||
/* 0x0C */ char unk_0C[0x4];
|
||||
/* 0x10 */ s16 state[16]; // only exists if count != 0. 8-byte aligned
|
||||
} AdpcmLoop; // size = 0x30 (or 0x10)
|
||||
typedef struct AdpcmLoop {
|
||||
/* 0x00 */ u32 start;
|
||||
/* 0x04 */ u32 loopEnd; // numSamples position into the sample where the loop ends
|
||||
/* 0x08 */ u32 count; // The number of times the loop is played before the sound completes. Setting count to -1
|
||||
// indicates that the loop should play indefinitely.
|
||||
/* 0x0C */ u32 sampleEnd; // total number of s16-samples in the sample audio clip
|
||||
/* 0x10 */ s16 predictorState[16]; // only exists if count != 0. 8-byte aligned
|
||||
} AdpcmLoop; // size = 0x30 (or 0x10)
|
||||
|
||||
typedef struct {
|
||||
/* 0x00 */ s32 order;
|
||||
|
@ -131,24 +137,23 @@ typedef struct {
|
|||
/* 0x08 */ s16* book; // size 8 * order * npredictors. 8-byte aligned
|
||||
} AdpcmBook; // size >= 0x8
|
||||
|
||||
typedef struct
|
||||
{
|
||||
typedef struct SoundFontSample {
|
||||
union {
|
||||
struct {
|
||||
/* 0x00 */ u32 codec : 4;
|
||||
/* 0x00 */ u32 medium : 2;
|
||||
/* 0x00 */ u32 unk_bit26 : 1;
|
||||
/* 0x00 */ u32 unk_bit25 : 1; // this has been named isRelocated in zret
|
||||
/* 0x01 */ u32 size : 24;
|
||||
///* 0x0 */ u32 unk_0 : 1;
|
||||
/* 0x0 */ u32 codec : 4; // The state of compression or decompression, See `SampleCodec`
|
||||
/* 0x0 */ u32 medium : 2; // Medium where sample is currently stored. See `SampleMedium`
|
||||
/* 0x0 */ u32 unk_bit26 : 1;
|
||||
/* 0x0 */ u32 isRelocated : 1; // Has the sample header been relocated (offsets to pointers)
|
||||
|
||||
};
|
||||
u32 asU32;
|
||||
};
|
||||
|
||||
/* 0x04 */ u8* sampleAddr;
|
||||
/* 0x08 */ AdpcmLoop* loop;
|
||||
/* 0x0C */ AdpcmBook* book;
|
||||
u32 sampleRateMagicValue; // For wav samples only...
|
||||
s32 sampleRate; // For wav samples only...
|
||||
/* 0x1 */ u32 size; // Size of the sample
|
||||
u32 fileSize;
|
||||
/* 0x4 */ u8* sampleAddr; // Raw sample data. Offset from the start of the sample bank or absolute address to either rom or ram
|
||||
/* 0x8 */ AdpcmLoop* loop; // Adpcm loop parameters used by the sample. Offset from the start of the sound font / pointer to ram
|
||||
/* 0xC */ AdpcmBook* book; // Adpcm book parameters used by the sample. Offset from the start of the sound font / pointer to ram
|
||||
} SoundFontSample; // size = 0x10
|
||||
|
||||
typedef struct {
|
||||
|
@ -465,6 +470,8 @@ typedef struct {
|
|||
/* 0x00F0 */ s16 dummyResampleState[0x10];
|
||||
} NoteSynthesisBuffers; // size = 0x110
|
||||
|
||||
struct OggOpusFile;
|
||||
|
||||
typedef struct {
|
||||
/* 0x00 */ u8 restart;
|
||||
/* 0x01 */ u8 sampleDmaIndex;
|
||||
|
@ -483,6 +490,7 @@ typedef struct {
|
|||
/* 0x1A */ u8 unk_1A;
|
||||
/* 0x1C */ u16 unk_1C;
|
||||
/* 0x1E */ u16 unk_1E;
|
||||
struct OggOpusFile* opusFile; // Only for streamed opus audio
|
||||
} NoteSynthesisState; // size = 0x20
|
||||
|
||||
typedef struct {
|
||||
|
@ -917,7 +925,7 @@ typedef struct {
|
|||
/* 0x3420 */ AudioPoolSplit3 persistentCommonPoolSplit;
|
||||
/* 0x342C */ AudioPoolSplit3 temporaryCommonPoolSplit;
|
||||
/* 0x3438 */ u8 sampleFontLoadStatus[0x30];
|
||||
/* 0x3468 */ u8 fontLoadStatus[0x30];
|
||||
/* 0x3468 */ u8* fontLoadStatus;
|
||||
/* 0x3498 */ u8* seqLoadStatus;
|
||||
/* 0x3518 */ volatile u8 resetStatus;
|
||||
/* 0x3519 */ u8 audioResetSpecIdToLoad;
|
||||
|
@ -1119,10 +1127,6 @@ typedef struct {
|
|||
uint8_t fonts[16];
|
||||
} SequenceData;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void Audio_SetGameVolume(int player_id, f32 volume);
|
||||
float Audio_GetGameVolume(int player_id);
|
||||
|
||||
|
|
|
@ -411,12 +411,19 @@ void OTRGlobals::Initialize() {
|
|||
static_cast<uint32_t>(SOH::ResourceType::SOH_Text), 0);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSampleV2>(), RESOURCE_FORMAT_BINARY,
|
||||
"AudioSample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 2);
|
||||
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSampleV0>(), RESOURCE_FORMAT_XML,
|
||||
"Sample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 0);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSoundFontV2>(),
|
||||
RESOURCE_FORMAT_BINARY, "AudioSoundFont",
|
||||
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 2);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSoundFontV0>(), RESOURCE_FORMAT_XML,
|
||||
"SoundFont", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 0);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSequenceV2>(),
|
||||
RESOURCE_FORMAT_BINARY, "AudioSequence",
|
||||
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 2);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSequenceV0>(), RESOURCE_FORMAT_XML,
|
||||
"Sequence", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 0);
|
||||
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryBackgroundV0>(), RESOURCE_FORMAT_BINARY,
|
||||
"Background", static_cast<uint32_t>(SOH::ResourceType::SOH_Background), 0);
|
||||
|
||||
|
@ -602,6 +609,12 @@ extern "C" void OTRAudio_Init() {
|
|||
}
|
||||
}
|
||||
|
||||
extern "C" char** sequenceMap;
|
||||
extern "C" size_t sequenceMapSize;
|
||||
|
||||
extern "C" char** fontMap;
|
||||
extern "C" size_t fontMapSize;
|
||||
|
||||
extern "C" void OTRAudio_Exit() {
|
||||
// Tell the audio thread to stop
|
||||
{
|
||||
|
@ -612,6 +625,19 @@ extern "C" void OTRAudio_Exit() {
|
|||
|
||||
// Wait until the audio thread quit
|
||||
audio.thread.join();
|
||||
#if 0
|
||||
for (size_t i = 0; i < sequenceMapSize; i++) {
|
||||
free(sequenceMap[i]);
|
||||
}
|
||||
free(sequenceMap);
|
||||
|
||||
for (size_t i = 0; i < fontMapSize; i++) {
|
||||
free(fontMap[i]);
|
||||
}
|
||||
free(fontMap);
|
||||
free(gAudioContext.seqLoadStatus);
|
||||
free(gAudioContext.fontLoadStatus);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void VanillaItemTable_Init() {
|
||||
|
@ -1071,6 +1097,10 @@ void DetectOTRVersion(std::string fileName, bool isMQ) {
|
|||
}
|
||||
}
|
||||
|
||||
extern "C" void Messagebox_ShowErrorBox(char* title, char* body) {
|
||||
Extractor::ShowErrorBox(title, body);
|
||||
}
|
||||
|
||||
bool IsSubpath(const std::filesystem::path& path, const std::filesystem::path& base) {
|
||||
auto rel = std::filesystem::relative(path, base);
|
||||
return !rel.empty() && rel.native()[0] != '.';
|
||||
|
|
|
@ -171,6 +171,7 @@ void CheckTracker_RecalculateAvailableChecks();
|
|||
|
||||
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
|
||||
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);
|
||||
void Messagebox_ShowErrorBox(char* title, char* body);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -429,11 +429,16 @@ extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path) {
|
|||
return *sequence;
|
||||
}
|
||||
|
||||
extern "C" SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path) {
|
||||
SequenceData* sequence = (SequenceData*)ResourceGetDataByName(path);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
extern "C" SoundFontSample* ResourceMgr_LoadAudioSample(const char* path) {
|
||||
return (SoundFontSample*)ResourceGetDataByName(path);
|
||||
}
|
||||
|
||||
extern "C" SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path) {
|
||||
extern "C" SoundFont* ResourceMgr_LoadAudioSoundFontByName(const char* path) {
|
||||
return (SoundFont*)ResourceGetDataByName(path);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,8 +51,9 @@ void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
|
|||
char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path);
|
||||
Vtx* ResourceMgr_LoadVtxByCRC(uint64_t crc);
|
||||
Vtx* ResourceMgr_LoadVtxByName(char* path);
|
||||
SoundFont* ResourceMgr_LoadAudioSoundFont(const char* path);
|
||||
SoundFont* ResourceMgr_LoadAudioSoundFontByName(const char* path);
|
||||
SequenceData ResourceMgr_LoadSeqByName(const char* path);
|
||||
SequenceData* ResourceMgr_LoadSeqPtrByName(const char* path);
|
||||
SoundFontSample* ResourceMgr_LoadAudioSample(const char* path);
|
||||
CollisionHeader* ResourceMgr_LoadColByName(const char* path);
|
||||
bool ResourceMgr_IsAltAssetsEnabled();
|
||||
|
|
265
soh/soh/mixer.c
265
soh/soh/mixer.c
|
@ -1,9 +1,11 @@
|
|||
//! This file is always optimized by a rule in the CMakeList. This is done because the SIMD functions are very large
|
||||
//! when unoptimized and clang does not allow optimizing a single function.
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mixer.h"
|
||||
|
||||
#ifndef __clang__
|
||||
#pragma GCC optimize("unroll-loops")
|
||||
#endif
|
||||
|
@ -66,6 +68,9 @@ static int16_t resample_table[64][4] = {
|
|||
{ 0xffdf, 0x0d46, 0x66ad, 0x0c39 }
|
||||
};
|
||||
|
||||
static void aMixImplSSE2(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr);
|
||||
static void aMixImplNEON(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr);
|
||||
|
||||
static inline int16_t clamp16(int32_t v) {
|
||||
if (v < -0x8000) {
|
||||
return -0x8000;
|
||||
|
@ -99,6 +104,33 @@ void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbyte
|
|||
#endif
|
||||
}
|
||||
|
||||
#include <opus/opus.h>
|
||||
#include <opusfile.h>
|
||||
|
||||
void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OggOpusFile** decState, int32_t pos,
|
||||
uint32_t size) {
|
||||
int readSamples = 0;
|
||||
if (*decState == NULL) {
|
||||
*decState = op_open_memory(source_addr, size, NULL);
|
||||
}
|
||||
op_pcm_seek(*decState, pos);
|
||||
int ret = op_read(*decState, BUF_S16(dest_addr), nbytes / 2, NULL);
|
||||
if (ret < 0) {
|
||||
return;
|
||||
}
|
||||
readSamples += ret;
|
||||
while (readSamples < nbytes / 2) {
|
||||
ret = op_read(*decState, BUF_S16(dest_addr + readSamples * 2), (nbytes - readSamples * 2) / 2, NULL);
|
||||
if (ret == 0)
|
||||
break;
|
||||
readSamples += ret;
|
||||
}
|
||||
}
|
||||
|
||||
void aOPUSFree(struct OggOpusFile* opusFile) {
|
||||
op_free(opusFile);
|
||||
}
|
||||
|
||||
void aSaveBufferImpl(uint16_t source_addr, int16_t* dest_addr, uint16_t nbytes) {
|
||||
memcpy(dest_addr, BUF_S16(source_addr), ROUND_DOWN_16(nbytes));
|
||||
}
|
||||
|
@ -296,7 +328,7 @@ void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb, bool
|
|||
} while (n > 0);
|
||||
}
|
||||
|
||||
void aMixImpl(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
static void aMixImplRef(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
|
||||
int16_t* in = BUF_S16(in_addr);
|
||||
int16_t* out = BUF_S16(out_addr);
|
||||
|
@ -323,6 +355,16 @@ void aMixImpl(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr)
|
|||
}
|
||||
}
|
||||
|
||||
void aMixImpl(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
#if defined(__SSE2__) || defined(_M_AMD64)
|
||||
aMixImplSSE2(count, gain, in_addr, out_addr);
|
||||
#elif defined(__ARM_NEON)
|
||||
aMixImplNEON(count, gain, in_addr, out_addr);
|
||||
#else
|
||||
aMixImplRef(count, gain, in_addr, out_addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void aS8DecImpl(uint8_t flags, ADPCM_STATE state) {
|
||||
uint8_t* in = BUF_U8(rspa.in);
|
||||
int16_t* out = BUF_S16(rspa.out);
|
||||
|
@ -555,3 +597,222 @@ void aUnkCmd19Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_add
|
|||
nbytes -= 32 * sizeof(int16_t);
|
||||
} while (nbytes > 0);
|
||||
}
|
||||
|
||||
// From here on there are SIMD implementations of the various mixer functions.
|
||||
// A note about FORCE_OPTIMIZE...
|
||||
// Compilers don't handle SIMD code well when not optimizing. It is unlikely that this code will need to be debugged
|
||||
// outside of specific audio issues. We can assume it should always be optimized.
|
||||
|
||||
// SIMD operations expect aligned data
|
||||
#include "align_asset_macro.h"
|
||||
|
||||
#if defined(__SSE2__) || defined(_M_AMD64)
|
||||
#include <immintrin.h>
|
||||
|
||||
static const ALIGN_ASSET(16) int16_t x7fff[8] = {
|
||||
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
|
||||
};
|
||||
static const ALIGN_ASSET(16) int32_t x4000[4] = {
|
||||
0x4000,
|
||||
0x4000,
|
||||
0x4000,
|
||||
0x4000,
|
||||
};
|
||||
|
||||
static void aMixImplSSE2(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
|
||||
int16_t* in = BUF_S16(in_addr);
|
||||
int16_t* out = BUF_S16(out_addr);
|
||||
int i;
|
||||
int32_t sample;
|
||||
if (gain == -0x8000) {
|
||||
while (nbytes > 0) {
|
||||
for (unsigned int i = 0; i < 2; i++) {
|
||||
__m128i outVec = _mm_loadu_si128((__m128i*)out);
|
||||
__m128i inVec = _mm_loadu_si128((__m128i*)in);
|
||||
__m128i subsVec = _mm_subs_epi16(outVec, inVec);
|
||||
_mm_storeu_si128(out, subsVec);
|
||||
nbytes -= 8 * sizeof(int16_t);
|
||||
in += 8;
|
||||
out += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load constants into vectors from aligned memory.
|
||||
__m128i x7fffVec = _mm_load_si128((__m128i*)x7fff);
|
||||
__m128i x4000Vec = _mm_load_si128((__m128i*)x4000);
|
||||
__m128i gainVec = _mm_set1_epi16(gain);
|
||||
while (nbytes > 0) {
|
||||
for (unsigned int i = 0; i < 2; i++) {
|
||||
// Load input and output data into vectors
|
||||
__m128i outVec = _mm_loadu_si128((__m128i*)out);
|
||||
__m128i inVec = _mm_loadu_si128((__m128i*)in);
|
||||
// Multiply `out` by `0x7FFF` producing 32 bit results, and store the upper and lower bits in each vector.
|
||||
// Equivalent to `out[0..8] * 0x7FFF`
|
||||
__m128i outx7fffLoVec = _mm_mullo_epi16(outVec, x7fffVec);
|
||||
__m128i outx7fffHiVec = _mm_mulhi_epi16(outVec, x7fffVec);
|
||||
// Same as above but for in and gain. Equivalent to `in[0..8] * gain`
|
||||
__m128i inxGainLoVec = _mm_mullo_epi16(inVec, gainVec);
|
||||
__m128i inxGainHiVec = _mm_mulhi_epi16(inVec, gainVec);
|
||||
|
||||
// Interleave the lo and hi bits into one 32 bit value for each vector element.
|
||||
// So now we have 4 full elements in each vector instead of 8 half elements.
|
||||
outx7fffLoVec = _mm_unpacklo_epi16(outx7fffLoVec, outx7fffHiVec);
|
||||
outx7fffHiVec = _mm_unpackhi_epi16(outx7fffLoVec, outx7fffHiVec);
|
||||
inxGainLoVec = _mm_unpacklo_epi16(inxGainLoVec, inxGainHiVec);
|
||||
inxGainHiVec = _mm_unpackhi_epi16(inxGainLoVec, inxGainHiVec);
|
||||
|
||||
// Now we have 4 32 bit elements. Continue the calculaton per the reference implementation.
|
||||
// We already did out + 0x7fff and in * gain.
|
||||
// *out * 0x7fff + *in++ * gain is the final result of these two calculations.
|
||||
__m128i addLoVec = _mm_add_epi32(outx7fffLoVec, inxGainLoVec);
|
||||
__m128i addHiVec = _mm_add_epi32(outx7fffHiVec, inxGainHiVec);
|
||||
// Add 0x4000 to each element
|
||||
addLoVec = _mm_add_epi32(addLoVec, x4000Vec);
|
||||
addHiVec = _mm_add_epi32(addHiVec, x4000Vec);
|
||||
// Shift each element over by 15
|
||||
__m128i shiftedLoVec = _mm_srai_epi32(addLoVec, 15);
|
||||
__m128i shiftedHiVec = _mm_srai_epi32(addHiVec, 15);
|
||||
// Convert each 32 bit element to 16 bit with saturation (clamp) and store in `outVec`
|
||||
outVec = _mm_packs_epi32(shiftedLoVec, shiftedHiVec);
|
||||
// Write the final vector back to memory
|
||||
// The final calculation is ((out[0..8] * 0x7fff + in[0..8] * gain) + 0x4000) >> 15;
|
||||
_mm_storeu_si128((__m128i*)out, outVec);
|
||||
|
||||
in += 8;
|
||||
out += 8;
|
||||
nbytes -= 8 * sizeof(int16_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(__ARM_NEON)
|
||||
#include <arm_neon.h>
|
||||
static const int32_t x4000Arr[4] = { 0x4000, 0x4000, 0x4000, 0x4000 };
|
||||
void aMixImplNEON(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
|
||||
int16_t* in = BUF_S16(in_addr);
|
||||
int16_t* out = BUF_S16(out_addr);
|
||||
int i;
|
||||
int32_t sample;
|
||||
|
||||
if (gain == -0x8000) {
|
||||
while (nbytes > 0) {
|
||||
for (unsigned int i = 0; i < 2; i++) {
|
||||
int16x8_t outVec = vld1q_s16(out);
|
||||
int16x8_t inVec = vld1q_s16(in);
|
||||
int16x8_t subVec = vqsubq_s16(outVec, inVec);
|
||||
vst1q_s16(out, subVec);
|
||||
nbytes -= 8 * sizeof(int16_t);
|
||||
out += 8;
|
||||
in += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
int16x8_t gainVec = vdupq_n_s16(gain);
|
||||
int32x4_t x4000Vec = vld1q_s32(x4000Arr);
|
||||
while (nbytes > 0) {
|
||||
for (unsigned int i = 0; i < 2; i++) {
|
||||
// for (i = 0; i < 16; i++) {
|
||||
int16x8_t outVec = vld1q_s16(out);
|
||||
int16x8_t inVec = vld1q_s16(in);
|
||||
int16x4_t outLoVec = vget_low_s16(outVec);
|
||||
int16x8_t outLoVec2 = vcombine_s16(outLoVec, outLoVec);
|
||||
int16x4_t inLoVec = vget_low_s16(inVec);
|
||||
int16x8_t inLoVec2 = vcombine_s16(inLoVec, inLoVec);
|
||||
int32x4_t outX7fffHiVec = vmull_high_n_s16(outVec, 0x7FFF);
|
||||
int32x4_t outX7fffLoVec = vmull_high_n_s16(outLoVec2, 0x7FFF);
|
||||
|
||||
int32x4_t inGainLoVec = vmull_high_s16(inLoVec2, gainVec);
|
||||
int32x4_t inGainHiVec = vmull_high_s16(inVec, gainVec);
|
||||
int32x4_t addVecLo = vaddq_s32(outX7fffLoVec, inGainLoVec);
|
||||
int32x4_t addVecHi = vaddq_s32(outX7fffHiVec, inGainHiVec);
|
||||
addVecHi = vaddq_s32(addVecHi, x4000Vec);
|
||||
addVecLo = vaddq_s32(addVecLo, x4000Vec);
|
||||
int32x4_t shiftVecHi = vshrq_n_s32(addVecHi, 15);
|
||||
int32x4_t shiftVecLo = vshrq_n_s32(addVecLo, 15);
|
||||
int16x4_t shiftedNarrowHiVec = vqmovn_s32(shiftVecHi);
|
||||
int16x4_t shiftedNarrowLoVec = vqmovn_s32(shiftVecLo);
|
||||
vst1_s16(out, shiftedNarrowLoVec);
|
||||
out += 4;
|
||||
vst1_s16(out, shiftedNarrowHiVec);
|
||||
// int16x8_t finalVec = vcombine_s16(shiftedNarrowLoVec, shiftedNarrowHiVec);
|
||||
// vst1q_s16(out, finalVec);
|
||||
out += 4;
|
||||
in += 8;
|
||||
|
||||
nbytes -= 8 * sizeof(int16_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static const ALIGN_ASSET(32) int16_t x7fff[16] = { 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,};
|
||||
static const ALIGN_ASSET(32) int32_t x4000[8] = { 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000};
|
||||
|
||||
#pragma GCC target("avx2")
|
||||
// AVX2 version of the SSE2 implementation above. AVX2 wasn't released until 2014 and I don't have a good way of checking for it at compile time.
|
||||
void aMixImpl256(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
|
||||
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
|
||||
int16_t* in = BUF_S16(in_addr);
|
||||
int16_t* out = BUF_S16(out_addr);
|
||||
int i;
|
||||
int32_t sample;
|
||||
if (gain == -0x8000) {
|
||||
while (nbytes > 0) {
|
||||
__m256i outVec =_mm256_loadu_si256((__m256*)out);
|
||||
__m256i inVec =_mm256_loadu_si256((__m256i*)in);
|
||||
__m256i subsVec =_mm256_subs_epi16(outVec, inVec);
|
||||
_mm256_storeu_si256(out, subsVec);
|
||||
in += 16;
|
||||
out += 16;
|
||||
nbytes -= 16 * sizeof(int16_t);
|
||||
}
|
||||
}
|
||||
// Load constants into vectors from aligned memory.
|
||||
__m256i x7fffVec = _mm256_load_si256((__m256i*)x7fff);
|
||||
__m256i x4000Vec = _mm256_load_si256((__m256i*)x4000);
|
||||
__m256i gainVec = _mm256_set1_epi16(gain);
|
||||
while (nbytes > 0) {
|
||||
// Load input and output data into vectors
|
||||
__m256i outVec = _mm256_loadu_si256((__m256i*)out);
|
||||
__m256i inVec = _mm256_loadu_si256((__m256i*)in);
|
||||
// Multiply `out` by `0x7FFF` producing 32 bit results, and store the upper and lower bits in each vector.
|
||||
// Equivalent to `out[0..16] * 0x7FFF`
|
||||
__m256i outx7fffLoVec = _mm256_mullo_epi16(outVec, x7fffVec);
|
||||
__m256i outx7fffHiVec = _mm256_mulhi_epi16(outVec, x7fffVec);
|
||||
// Same as above but for in and gain. Equivalent to `in[0..16] * gain`
|
||||
__m256i inxGainLoVec = _mm256_mullo_epi16(inVec, gainVec);
|
||||
__m256i inxGainHiVec = _mm256_mulhi_epi16(inVec, gainVec);
|
||||
|
||||
// Interleave the lo and hi bits into one 32 bit value for each vector element.
|
||||
// So now we have 8 full elements in each vector instead of 16 half elements.
|
||||
outx7fffLoVec = _mm256_unpacklo_epi16(outx7fffLoVec, outx7fffHiVec);
|
||||
outx7fffHiVec = _mm256_unpackhi_epi16(outx7fffLoVec, outx7fffHiVec);
|
||||
inxGainLoVec = _mm256_unpacklo_epi16(inxGainLoVec, inxGainHiVec);
|
||||
inxGainHiVec = _mm256_unpackhi_epi16(inxGainLoVec, inxGainHiVec);
|
||||
|
||||
// Now we have 8 32 bit elements. Continue the calculaton per the reference implementation.
|
||||
// We already did out + 0x7fff and in * gain.
|
||||
// *out * 0x7fff + *in++ * gain is the final result of these two calculations.
|
||||
__m256i addLoVec = _mm256_add_epi32(outx7fffLoVec, inxGainLoVec);
|
||||
__m256i addHiVec = _mm256_add_epi32(outx7fffHiVec, inxGainHiVec);
|
||||
// Add 0x4000 to each element
|
||||
addLoVec = _mm256_add_epi32(addLoVec, x4000Vec);
|
||||
addHiVec = _mm256_add_epi32(addHiVec, x4000Vec);
|
||||
// Shift each element over by 15
|
||||
__m256i shiftedLoVec = _mm256_srai_epi32(addLoVec, 15);
|
||||
__m256i shiftedHiVec = _mm256_srai_epi32(addHiVec, 15);
|
||||
// Convert each 32 bit element to 16 bit with saturation (clamp) and store in `outVec`
|
||||
outVec = _mm256_packs_epi32(shiftedLoVec, shiftedHiVec);
|
||||
// Write the final vector back to memory
|
||||
// The final calculation is ((out[0..16] * 0x7fff + in[0..16] * gain) + 0x4000) >> 15;
|
||||
_mm256_storeu_si256((__m256i*)out, outVec);
|
||||
|
||||
in += 16;
|
||||
out += 16;
|
||||
nbytes -= 16 * sizeof(int16_t);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -57,6 +57,11 @@ void aHiLoGainImpl(uint8_t g, uint16_t count, uint16_t addr);
|
|||
void aUnkCmd3Impl(uint16_t a, uint16_t b, uint16_t c);
|
||||
void aUnkCmd19Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_addr);
|
||||
|
||||
struct OggOpusFile;
|
||||
|
||||
void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OggOpusFile** decState, int32_t pos,
|
||||
uint32_t size);
|
||||
|
||||
#define aSegment(pkt, s, b) \
|
||||
do { \
|
||||
} while (0)
|
||||
|
|
|
@ -1,6 +1,193 @@
|
|||
#include "soh/resource/importer/AudioSampleFactory.h"
|
||||
#include "soh/resource/importer/AudioSoundFontFactory.h"
|
||||
#include "soh/resource/type/AudioSample.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "z64.h"
|
||||
#include "z64audio.h"
|
||||
#include "Context.h"
|
||||
#include "resource/archive/Archive.h"
|
||||
#include "resource/ResourceManager.h"
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include <dr_wav.h>
|
||||
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include <dr_mp3.h>
|
||||
|
||||
#define DR_FLAC_IMPLEMENTATION
|
||||
#include <dr_flac.h>
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
#include <vorbis/codec.h>
|
||||
#include "vorbis/vorbisfile.h"
|
||||
#include <tinyxml2.h>
|
||||
|
||||
struct OggFileData {
|
||||
void* data;
|
||||
size_t pos;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
typedef enum class OggType {
|
||||
None = -1,
|
||||
Vorbis,
|
||||
Opus,
|
||||
} OggType;
|
||||
|
||||
static size_t VorbisReadCallback(void* out, size_t size, size_t elems, void* src) {
|
||||
OggFileData* data = static_cast<OggFileData*>(src);
|
||||
size_t toRead = size * elems;
|
||||
|
||||
if (toRead > data->size - data->pos) {
|
||||
toRead = data->size - data->pos;
|
||||
}
|
||||
|
||||
memcpy(out, static_cast<uint8_t*>(data->data) + data->pos, toRead);
|
||||
data->pos += toRead;
|
||||
|
||||
return toRead / size;
|
||||
}
|
||||
|
||||
static int VorbisSeekCallback(void* src, ogg_int64_t pos, int whence) {
|
||||
OggFileData* data = static_cast<OggFileData*>(src);
|
||||
size_t newPos;
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
newPos = pos;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
newPos = data->pos + pos;
|
||||
break;
|
||||
case SEEK_END:
|
||||
newPos = data->size + pos;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
if (newPos > data->size) {
|
||||
return -1;
|
||||
}
|
||||
data->pos = newPos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VorbisCloseCallback([[maybe_unused]] void* src) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long VorbisTellCallback(void* src) {
|
||||
OggFileData* data = static_cast<OggFileData*>(src);
|
||||
return data->pos;
|
||||
}
|
||||
|
||||
static const ov_callbacks vorbisCallbacks = {
|
||||
VorbisReadCallback,
|
||||
VorbisSeekCallback,
|
||||
VorbisCloseCallback,
|
||||
VorbisTellCallback,
|
||||
};
|
||||
|
||||
static OggType GetOggType(OggFileData* data) {
|
||||
ogg_sync_state oy;
|
||||
ogg_stream_state os;
|
||||
ogg_page og;
|
||||
ogg_packet op;
|
||||
OggType type;
|
||||
// The first page as the header information, containing, among other things, what kind of data this ogg holds.
|
||||
ogg_sync_init(&oy);
|
||||
char* buffer = ogg_sync_buffer(&oy, 4096);
|
||||
VorbisReadCallback(buffer, 4096, 1, data);
|
||||
ogg_sync_wrote(&oy, 4096);
|
||||
|
||||
ogg_sync_pageout(&oy, &og);
|
||||
ogg_stream_init(&os, ogg_page_serialno(&og));
|
||||
ogg_stream_pagein(&os, &og);
|
||||
ogg_stream_packetout(&os, &op);
|
||||
|
||||
// Can't use strmp because op.packet isn't a null terminated string.
|
||||
if (memcmp((char*)op.packet, "\x01vorbis", 7) == 0) {
|
||||
type = OggType::Vorbis;
|
||||
} else if (memcmp((char*)op.packet, "OpusHead", 8) == 0) {
|
||||
type = OggType::Opus;
|
||||
} else {
|
||||
type = OggType::None;
|
||||
}
|
||||
ogg_stream_clear(&os);
|
||||
ogg_sync_clear(&oy);
|
||||
return type;
|
||||
}
|
||||
|
||||
static void Mp3DecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile) {
|
||||
drmp3 mp3;
|
||||
drwav_uint64 numFrames;
|
||||
drmp3_bool32 ret =
|
||||
drmp3_init_memory(&mp3, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
|
||||
numFrames = drmp3_get_pcm_frame_count(&mp3);
|
||||
drwav_uint64 channels = mp3.channels;
|
||||
drwav_uint64 sampleRate = mp3.sampleRate;
|
||||
|
||||
audioSample->sample.sampleAddr = new uint8_t[numFrames * channels * 2];
|
||||
drmp3_read_pcm_frames_s16(&mp3, numFrames, (int16_t*)audioSample->sample.sampleAddr);
|
||||
}
|
||||
|
||||
static void FlacDecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile) {
|
||||
drflac* flac = drflac_open_memory(sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
|
||||
drflac_uint64 numFrames = flac->totalPCMFrameCount;
|
||||
audioSample->sample.sampleAddr = new uint8_t[numFrames * flac->channels * 2];
|
||||
drflac_read_pcm_frames_s16(flac, numFrames, (int16_t*)audioSample->sample.sampleAddr);
|
||||
drflac_close(flac);
|
||||
}
|
||||
|
||||
static void OggDecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) {
|
||||
OggVorbis_File vf;
|
||||
char dataBuff[4096];
|
||||
long read = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
OggFileData fileData = {
|
||||
.data = sampleFile->Buffer.get()->data(),
|
||||
.pos = 0,
|
||||
.size = sampleFile->Buffer.get()->size(),
|
||||
};
|
||||
switch (GetOggType(&fileData)) {
|
||||
case OggType::Vorbis: {
|
||||
// Getting the type advanced the position. We are going to use a different library to decode the file which
|
||||
// assumes the file starts at 0
|
||||
fileData.pos = 0;
|
||||
int ret = ov_open_callbacks(&fileData, &vf, nullptr, 0, vorbisCallbacks);
|
||||
|
||||
vorbis_info* vi = ov_info(&vf, -1);
|
||||
|
||||
uint64_t numFrames = ov_pcm_total(&vf, -1);
|
||||
uint64_t sampleRate = vi->rate;
|
||||
uint64_t numChannels = vi->channels;
|
||||
int bitStream = 0;
|
||||
size_t toRead = numFrames * numChannels * 2;
|
||||
audioSample->sample.sampleAddr = new uint8_t[toRead];
|
||||
do {
|
||||
read = ov_read(&vf, dataBuff, 4096, 0, 2, 1, &bitStream);
|
||||
memcpy(audioSample->sample.sampleAddr + pos, dataBuff, read);
|
||||
pos += read;
|
||||
} while (read != 0);
|
||||
ov_clear(&vf);
|
||||
break;
|
||||
}
|
||||
case OggType::Opus: {
|
||||
// OPUS encoded data is decoded by the audio driver.
|
||||
audioSample->sample.codec = CODEC_OPUS;
|
||||
audioSample->sample.sampleAddr = new uint8_t[sampleFile->Buffer.get()->size()];
|
||||
memcpy(audioSample->sample.sampleAddr, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size());
|
||||
break;
|
||||
}
|
||||
case OggType::None: {
|
||||
char buff[2048];
|
||||
snprintf(buff, 2048, "Ogg file %s is not Vorbis or OPUS", initData->Path.c_str());
|
||||
throw std::runtime_error(buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace SOH {
|
||||
std::shared_ptr<Ship::IResource>
|
||||
|
@ -16,108 +203,154 @@ ResourceFactoryBinaryAudioSampleV2::ReadResource(std::shared_ptr<Ship::File> fil
|
|||
audioSample->sample.codec = reader->ReadUByte();
|
||||
audioSample->sample.medium = reader->ReadUByte();
|
||||
audioSample->sample.unk_bit26 = reader->ReadUByte();
|
||||
audioSample->sample.unk_bit25 = reader->ReadUByte();
|
||||
audioSample->sample.isRelocated = reader->ReadUByte();
|
||||
audioSample->sample.size = reader->ReadUInt32();
|
||||
|
||||
audioSample->audioSampleData.reserve(audioSample->sample.size);
|
||||
audioSample->sample.sampleAddr = new uint8_t[audioSample->sample.size];
|
||||
for (uint32_t i = 0; i < audioSample->sample.size; i++) {
|
||||
audioSample->audioSampleData.push_back(reader->ReadUByte());
|
||||
audioSample->sample.sampleAddr[i] = reader->ReadUByte();
|
||||
}
|
||||
audioSample->sample.sampleAddr = audioSample->audioSampleData.data();
|
||||
|
||||
audioSample->loop.start = reader->ReadUInt32();
|
||||
audioSample->loop.end = reader->ReadUInt32();
|
||||
audioSample->loop.count = reader->ReadUInt32();
|
||||
|
||||
audioSample->loopStateCount = reader->ReadUInt32();
|
||||
// This always seems to be 16. Can it be removed in V3?
|
||||
uint32_t loopStateCount = reader->ReadUInt32();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
audioSample->loop.state[i] = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < audioSample->loopStateCount; i++) {
|
||||
for (uint32_t i = 0; i < loopStateCount; i++) {
|
||||
audioSample->loop.state[i] = reader->ReadInt16();
|
||||
}
|
||||
audioSample->sample.loop = &audioSample->loop;
|
||||
|
||||
audioSample->book.order = reader->ReadInt32();
|
||||
audioSample->book.npredictors = reader->ReadInt32();
|
||||
audioSample->bookDataCount = reader->ReadUInt32();
|
||||
uint32_t bookDataCount = reader->ReadUInt32();
|
||||
|
||||
audioSample->bookData.reserve(audioSample->bookDataCount);
|
||||
for (uint32_t i = 0; i < audioSample->bookDataCount; i++) {
|
||||
audioSample->bookData.push_back(reader->ReadInt16());
|
||||
audioSample->book.book = new int16_t[bookDataCount];
|
||||
|
||||
for (uint32_t i = 0; i < bookDataCount; i++) {
|
||||
audioSample->book.book[i] = reader->ReadInt16();
|
||||
}
|
||||
audioSample->book.book = audioSample->bookData.data();
|
||||
audioSample->sample.book = &audioSample->book;
|
||||
|
||||
return audioSample;
|
||||
}
|
||||
} // namespace SOH
|
||||
|
||||
/*
|
||||
in ResourceMgr_LoadAudioSample we used to have
|
||||
--------------
|
||||
if (cachedCustomSFs.find(path) != cachedCustomSFs.end())
|
||||
return cachedCustomSFs[path];
|
||||
|
||||
SoundFontSample* cSample = ReadCustomSample(path);
|
||||
|
||||
if (cSample != nullptr)
|
||||
return cSample;
|
||||
--------------
|
||||
before the rest of the standard sample reading, this is the ReadCustomSample code we used to have
|
||||
|
||||
extern "C" SoundFontSample* ReadCustomSample(const char* path) {
|
||||
|
||||
if (!ExtensionCache.contains(path))
|
||||
std::shared_ptr<Ship::IResource>
|
||||
ResourceFactoryXMLAudioSampleV0::ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) {
|
||||
if (!FileHasValidFormatAndReader(file, initData)) {
|
||||
return nullptr;
|
||||
|
||||
ExtensionEntry entry = ExtensionCache[path];
|
||||
|
||||
auto sampleRaw = Ship::Context::GetInstance()->GetResourceManager()->LoadFile(entry.path);
|
||||
uint32_t* strem = (uint32_t*)sampleRaw->Buffer.get();
|
||||
uint8_t* strem2 = (uint8_t*)strem;
|
||||
|
||||
SoundFontSample* sampleC = new SoundFontSample;
|
||||
|
||||
if (entry.ext == "wav") {
|
||||
drwav_uint32 channels;
|
||||
drwav_uint32 sampleRate;
|
||||
drwav_uint64 totalPcm;
|
||||
drmp3_int16* pcmData =
|
||||
drwav_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &channels, &sampleRate, &totalPcm,
|
||||
NULL); sampleC->size = totalPcm; sampleC->sampleAddr = (uint8_t*)pcmData; sampleC->codec = CODEC_S16;
|
||||
|
||||
sampleC->loop = new AdpcmLoop;
|
||||
sampleC->loop->start = 0;
|
||||
sampleC->loop->end = sampleC->size - 1;
|
||||
sampleC->loop->count = 0;
|
||||
sampleC->sampleRateMagicValue = 'RIFF';
|
||||
sampleC->sampleRate = sampleRate;
|
||||
|
||||
cachedCustomSFs[path] = sampleC;
|
||||
return sampleC;
|
||||
} else if (entry.ext == "mp3") {
|
||||
drmp3_config mp3Info;
|
||||
drmp3_uint64 totalPcm;
|
||||
drmp3_int16* pcmData =
|
||||
drmp3_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &mp3Info, &totalPcm, NULL);
|
||||
|
||||
sampleC->size = totalPcm * mp3Info.channels * sizeof(short);
|
||||
sampleC->sampleAddr = (uint8_t*)pcmData;
|
||||
sampleC->codec = CODEC_S16;
|
||||
|
||||
sampleC->loop = new AdpcmLoop;
|
||||
sampleC->loop->start = 0;
|
||||
sampleC->loop->end = sampleC->size;
|
||||
sampleC->loop->count = 0;
|
||||
sampleC->sampleRateMagicValue = 'RIFF';
|
||||
sampleC->sampleRate = mp3Info.sampleRate;
|
||||
|
||||
cachedCustomSFs[path] = sampleC;
|
||||
return sampleC;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
auto audioSample = std::make_shared<AudioSample>(initData);
|
||||
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
|
||||
const char* customFormatStr = child->Attribute("CustomFormat");
|
||||
memset(&audioSample->sample, 0, sizeof(audioSample->sample));
|
||||
audioSample->sample.isRelocated = 0;
|
||||
audioSample->sample.codec = CodecStrToInt(child->Attribute("Codec"), initData->Path.c_str());
|
||||
audioSample->sample.medium =
|
||||
ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium"), initData->Path.c_str());
|
||||
audioSample->sample.unk_bit26 = child->IntAttribute("bit26");
|
||||
|
||||
tinyxml2::XMLElement* loopRoot = child->FirstChildElement("ADPCMLoop");
|
||||
if (loopRoot != nullptr) {
|
||||
size_t i = 0;
|
||||
audioSample->loop.start = loopRoot->UnsignedAttribute("Start");
|
||||
audioSample->loop.end = loopRoot->UnsignedAttribute("End");
|
||||
audioSample->loop.count = loopRoot->UnsignedAttribute("Count");
|
||||
tinyxml2::XMLElement* predictor = loopRoot->FirstChildElement("Predictor");
|
||||
while (predictor != nullptr) {
|
||||
audioSample->loop.state[i++] = predictor->IntAttribute("State");
|
||||
predictor = predictor->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* bookRoot = child->FirstChildElement("ADPCMBook");
|
||||
if (bookRoot != nullptr) {
|
||||
size_t i = 0;
|
||||
audioSample->book.npredictors = bookRoot->IntAttribute("Npredictors");
|
||||
audioSample->book.order = bookRoot->IntAttribute("Order");
|
||||
tinyxml2::XMLElement* book = bookRoot->FirstChildElement("Book");
|
||||
size_t numBooks = audioSample->book.npredictors * audioSample->book.order * 8;
|
||||
audioSample->book.book = new int16_t[numBooks];
|
||||
while (book != nullptr) {
|
||||
audioSample->book.book[i++] = book->IntAttribute("Page");
|
||||
book = book->NextSiblingElement();
|
||||
}
|
||||
audioSample->sample.book = &audioSample->book;
|
||||
}
|
||||
|
||||
audioSample->sample.loop = &audioSample->loop;
|
||||
size_t size = child->Int64Attribute("Size");
|
||||
audioSample->sample.size = size;
|
||||
|
||||
const char* path = child->Attribute("Path");
|
||||
|
||||
auto sampleFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path);
|
||||
audioSample->sample.fileSize = sampleFile->Buffer.get()->size();
|
||||
if (customFormatStr != nullptr) {
|
||||
// Compressed files can take a really long time to decode (~250ms per).
|
||||
// This worked when we tested it (09/04/2024) (Works on my machine)
|
||||
if (strcmp(customFormatStr, "wav") == 0) {
|
||||
drwav wav;
|
||||
drwav_uint64 numFrames;
|
||||
|
||||
drwav_bool32 ret =
|
||||
drwav_init_memory(&wav, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
|
||||
|
||||
drwav_get_length_in_pcm_frames(&wav, &numFrames);
|
||||
|
||||
audioSample->tuning = (wav.sampleRate * wav.channels) / 32000.0f;
|
||||
audioSample->sample.sampleAddr = new uint8_t[numFrames * wav.channels * 2];
|
||||
|
||||
drwav_read_pcm_frames_s16(&wav, numFrames, (int16_t*)audioSample->sample.sampleAddr);
|
||||
return audioSample;
|
||||
} else if (strcmp(customFormatStr, "mp3") == 0) {
|
||||
std::thread fileDecoderThread = std::thread(Mp3DecoderWorker, audioSample, sampleFile);
|
||||
fileDecoderThread.detach();
|
||||
return audioSample;
|
||||
} else if (strcmp(customFormatStr, "ogg") == 0) {
|
||||
std::thread fileDecoderThread = std::thread(OggDecoderWorker, audioSample, sampleFile, initData);
|
||||
fileDecoderThread.detach();
|
||||
return audioSample;
|
||||
} else if (strcmp(customFormatStr, "flac") == 0) {
|
||||
std::thread fileDecoderThread = std::thread(FlacDecoderWorker, audioSample, sampleFile);
|
||||
fileDecoderThread.detach();
|
||||
return audioSample;
|
||||
}
|
||||
}
|
||||
// Not a normal streamed sample. Fallback to the original ADPCM sample to be decoded by the audio engine.
|
||||
audioSample->sample.sampleAddr = new uint8_t[size];
|
||||
// Can't use memcpy due to endianness issues.
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
audioSample->sample.sampleAddr[i] = sampleFile->Buffer.get()->data()[i];
|
||||
}
|
||||
|
||||
return audioSample;
|
||||
}
|
||||
|
||||
*/
|
||||
uint8_t ResourceFactoryXMLAudioSampleV0::CodecStrToInt(const char* str, const char* file) {
|
||||
if (strcmp("ADPCM", str) == 0) {
|
||||
return CODEC_ADPCM;
|
||||
} else if (strcmp("S8", str) == 0) {
|
||||
return CODEC_S8;
|
||||
} else if (strcmp("S16MEM", str) == 0) {
|
||||
return CODEC_S16_INMEMORY;
|
||||
} else if (strcmp("ADPCMSMALL", str) == 0) {
|
||||
return CODEC_SMALL_ADPCM;
|
||||
} else if (strcmp("REVERB", str) == 0) {
|
||||
return CODEC_REVERB;
|
||||
} else if (strcmp("S16", str) == 0) {
|
||||
return CODEC_S16;
|
||||
} else {
|
||||
char buff[2048];
|
||||
snprintf(buff, 2048, "Invalid codec in %s. Got %s, expected ADPCM, S8, S16MEM, ADPCMSMALL, REVERB, S16.", file,
|
||||
str);
|
||||
throw std::runtime_error(buff);
|
||||
}
|
||||
}
|
||||
} // namespace SOH
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Resource.h"
|
||||
#include "ResourceFactoryBinary.h"
|
||||
#include "ResourceFactoryXML.h"
|
||||
|
||||
namespace SOH {
|
||||
class ResourceFactoryBinaryAudioSampleV2 final : public Ship::ResourceFactoryBinary {
|
||||
|
@ -9,4 +10,14 @@ class ResourceFactoryBinaryAudioSampleV2 final : public Ship::ResourceFactoryBin
|
|||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
};
|
||||
|
||||
class ResourceFactoryXMLAudioSampleV0 final : public Ship::ResourceFactoryXML {
|
||||
public:
|
||||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
|
||||
private:
|
||||
static uint8_t CodecStrToInt(const char* str, const char* file);
|
||||
};
|
||||
|
||||
} // namespace SOH
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
#include "soh/resource/importer/AudioSequenceFactory.h"
|
||||
#include "soh/resource/importer/AudioSoundFontFactory.h"
|
||||
#include "soh/resource/type/AudioSequence.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "resource/ResourceManager.h"
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "Context.h"
|
||||
#include "resource/archive/Archive.h"
|
||||
#include "BinaryWriter.h"
|
||||
#include <type_traits>
|
||||
|
||||
namespace SOH {
|
||||
std::shared_ptr<Ship::IResource>
|
||||
|
@ -13,12 +21,11 @@ ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr<Ship::File> f
|
|||
auto audioSequence = std::make_shared<AudioSequence>(initData);
|
||||
auto reader = std::get<std::shared_ptr<Ship::BinaryReader>>(file->Reader);
|
||||
|
||||
audioSequence->sequence.seqDataSize = reader->ReadInt32();
|
||||
audioSequence->sequenceData.reserve(audioSequence->sequence.seqDataSize);
|
||||
audioSequence->sequence.seqDataSize = reader->ReadUInt32();
|
||||
audioSequence->sequence.seqData = new char[audioSequence->sequence.seqDataSize];
|
||||
for (int32_t i = 0; i < audioSequence->sequence.seqDataSize; i++) {
|
||||
audioSequence->sequenceData.push_back(reader->ReadChar());
|
||||
audioSequence->sequence.seqData[i] = reader->ReadChar();
|
||||
}
|
||||
audioSequence->sequence.seqData = audioSequence->sequenceData.data();
|
||||
|
||||
audioSequence->sequence.seqNumber = reader->ReadUByte();
|
||||
audioSequence->sequence.medium = reader->ReadUByte();
|
||||
|
@ -34,4 +41,355 @@ ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr<Ship::File> f
|
|||
|
||||
return audioSequence;
|
||||
}
|
||||
} // namespace SOH
|
||||
|
||||
template <typename T> static void WriteInsnOneArg(Ship::BinaryWriter* writer, uint8_t opcode, T arg) {
|
||||
static_assert(std::is_fundamental<T>::value);
|
||||
writer->Write(opcode);
|
||||
writer->Write(arg);
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
static void WriteInsnTwoArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2) {
|
||||
static_assert(std::is_fundamental<T1>::value && std::is_fundamental<T2>::value);
|
||||
writer->Write(opcode);
|
||||
writer->Write(arg1);
|
||||
writer->Write(arg2);
|
||||
}
|
||||
|
||||
template <typename T1, typename T2, typename T3>
|
||||
static void WriteInsnThreeArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2, T3 arg3) {
|
||||
static_assert(std::is_fundamental<T1>::value && std::is_fundamental<T2>::value);
|
||||
writer->Write(opcode);
|
||||
writer->Write(arg1);
|
||||
writer->Write(arg2);
|
||||
writer->Write(arg3);
|
||||
}
|
||||
|
||||
static void WriteInsnNoArg(Ship::BinaryWriter* writer, uint8_t opcode) {
|
||||
writer->Write(opcode);
|
||||
}
|
||||
|
||||
static void WriteLegato(Ship::BinaryWriter* writer) {
|
||||
WriteInsnNoArg(writer, 0xC4);
|
||||
}
|
||||
|
||||
static void WriteNoLegato(Ship::BinaryWriter* writer) {
|
||||
WriteInsnNoArg(writer, 0xC5);
|
||||
}
|
||||
|
||||
static void WriteMuteBhv(Ship::BinaryWriter* writer, uint8_t arg) {
|
||||
WriteInsnOneArg(writer, 0xD3, arg);
|
||||
}
|
||||
|
||||
static void WriteMuteScale(Ship::BinaryWriter* writer, uint8_t arg) {
|
||||
WriteInsnOneArg(writer, 0xD5, arg);
|
||||
}
|
||||
|
||||
static void WriteInitchan(Ship::BinaryWriter* writer, uint16_t channels) {
|
||||
WriteInsnOneArg(writer, 0xD7, channels);
|
||||
}
|
||||
|
||||
static void WriteLdchan(Ship::BinaryWriter* writer, uint8_t channel, uint16_t offset) {
|
||||
WriteInsnOneArg(writer, 0x90 | channel, offset);
|
||||
}
|
||||
|
||||
static void WriteVolSHeader(Ship::BinaryWriter* writer, uint8_t vol) {
|
||||
WriteInsnOneArg(writer, 0xDB, vol);
|
||||
}
|
||||
|
||||
static void WriteVolCHeader(Ship::BinaryWriter* writer, uint8_t vol) {
|
||||
WriteInsnOneArg(writer, 0xDF, vol);
|
||||
}
|
||||
|
||||
static void WriteTempo(Ship::BinaryWriter* writer, uint8_t tempo) {
|
||||
WriteInsnOneArg(writer, 0xDD, tempo);
|
||||
}
|
||||
|
||||
static void WriteJump(Ship::BinaryWriter* writer, uint16_t offset) {
|
||||
WriteInsnOneArg(writer, 0xFB, offset);
|
||||
}
|
||||
|
||||
static void WriteDisablecan(Ship::BinaryWriter* writer, uint16_t channels) {
|
||||
WriteInsnOneArg(writer, 0xD6, channels);
|
||||
}
|
||||
|
||||
static void WriteNoshort(Ship::BinaryWriter* writer) {
|
||||
WriteInsnNoArg(writer, 0xC4);
|
||||
}
|
||||
|
||||
static void WriteLdlayer(Ship::BinaryWriter* writer, uint8_t layer, uint16_t offset) {
|
||||
WriteInsnOneArg(writer, 0x88 | layer, offset);
|
||||
}
|
||||
|
||||
static void WritePan(Ship::BinaryWriter* writer, uint8_t pan) {
|
||||
WriteInsnOneArg(writer, 0xDD, pan);
|
||||
}
|
||||
|
||||
static void WriteBend(Ship::BinaryWriter* writer, uint8_t bend) {
|
||||
WriteInsnOneArg(writer, 0xD3, bend);
|
||||
}
|
||||
|
||||
static void WriteInstrument(Ship::BinaryWriter* writer, uint8_t instrument) {
|
||||
WriteInsnOneArg(writer, 0xC1, instrument);
|
||||
}
|
||||
|
||||
static void WriteTranspose(Ship::BinaryWriter* writer, int8_t transpose) {
|
||||
WriteInsnOneArg(writer, 0xC2, transpose);
|
||||
}
|
||||
|
||||
static void WriteDelay(Ship::BinaryWriter* writer, uint16_t delay) {
|
||||
if (delay > 0x7F) {
|
||||
WriteInsnOneArg(writer, 0xFD, static_cast<uint16_t>(delay | 0x8000));
|
||||
} else {
|
||||
WriteInsnOneArg(writer, 0xFD, static_cast<uint8_t>(delay));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> static void WriteLDelay(Ship::BinaryWriter* writer, T delay) {
|
||||
WriteInsnOneArg(writer, 0xC0, delay);
|
||||
}
|
||||
|
||||
template <typename T> static void WriteNotedv(Ship::BinaryWriter* writer, uint8_t note, T delay, uint8_t velocity) {
|
||||
WriteInsnTwoArg(writer, note, delay, velocity);
|
||||
}
|
||||
|
||||
static void WriteNotedvg(Ship::BinaryWriter* writer, uint8_t note, uint16_t delay, uint8_t velocity, uint8_t gateTime) {
|
||||
if (delay > 0x7F) {
|
||||
WriteInsnThreeArg(writer, note, static_cast<uint16_t>(delay | 0x8000), velocity, gateTime);
|
||||
} else {
|
||||
WriteInsnThreeArg(writer, note, static_cast<uint8_t>(delay), velocity, gateTime);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteMonoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) {
|
||||
uint16_t channelStart;
|
||||
uint16_t channelPlaceholderOff;
|
||||
uint16_t loopPoint;
|
||||
uint16_t layerPlaceholderOff;
|
||||
uint16_t layerStart;
|
||||
if (looped) {
|
||||
delay = 0x7FFF;
|
||||
}
|
||||
// Write seq header
|
||||
|
||||
// These two values are always the same in OOT and MM
|
||||
WriteMuteBhv(writer, 0x20);
|
||||
WriteMuteScale(writer, 0x32);
|
||||
|
||||
// We only have one channel
|
||||
WriteInitchan(writer, 0b11);
|
||||
// Store the current position so we can write the address of the channel when we are ready.
|
||||
channelPlaceholderOff = writer->GetBaseAddress();
|
||||
// Store the current position so we can loop here after the song ends.
|
||||
loopPoint = writer->GetBaseAddress();
|
||||
WriteLdchan(writer, 0, 0); // Fill in the actual address later
|
||||
|
||||
WriteVolSHeader(writer, 127); // Max volume
|
||||
WriteTempo(writer, tempo);
|
||||
|
||||
WriteDelay(writer, delay);
|
||||
if (looped) {
|
||||
WriteJump(writer, loopPoint);
|
||||
}
|
||||
WriteDisablecan(writer, 0b11);
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
// Fill in the ldchan from before
|
||||
channelStart = writer->GetBaseAddress();
|
||||
writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start);
|
||||
WriteLdchan(writer, 0, channelStart);
|
||||
writer->Seek(channelStart, Ship::SeekOffsetType::Start);
|
||||
|
||||
// Channel header
|
||||
layerPlaceholderOff = writer->GetBaseAddress();
|
||||
WriteNoshort(writer);
|
||||
WriteLdlayer(writer, 0, 0);
|
||||
WritePan(writer, 64);
|
||||
WriteVolCHeader(writer, 127); // Max volume
|
||||
WriteBend(writer, 0);
|
||||
WriteInstrument(writer, 0);
|
||||
WriteDelay(writer, delay);
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
layerStart = writer->GetBaseAddress();
|
||||
writer->Seek(layerPlaceholderOff, Ship::SeekOffsetType::Start);
|
||||
WriteLdlayer(writer, 0, layerStart);
|
||||
writer->Seek(layerStart, Ship::SeekOffsetType::Start);
|
||||
|
||||
// Note layer
|
||||
WriteLegato(writer);
|
||||
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
}
|
||||
|
||||
static void WriteStereoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) {
|
||||
uint16_t lChannelStart;
|
||||
uint16_t rChannelStart;
|
||||
uint16_t channelPlaceholderOff;
|
||||
uint16_t loopPoint;
|
||||
uint16_t lLayerPlaceholderOff;
|
||||
uint16_t rLayerPlaceholderOff;
|
||||
uint16_t lLayerOffset;
|
||||
uint16_t rLayerOffset;
|
||||
|
||||
uint16_t layerStart;
|
||||
// Write seq header
|
||||
if (looped) {
|
||||
delay = 0x7FFF;
|
||||
}
|
||||
// These two values are always the same in OOT and MM
|
||||
WriteMuteBhv(writer, 0x20);
|
||||
WriteMuteScale(writer, 0x32);
|
||||
|
||||
// We only have one channel
|
||||
WriteInitchan(writer, 0b11);
|
||||
// Store the current position so we can write the address of the channel when we are ready.
|
||||
channelPlaceholderOff = writer->GetBaseAddress();
|
||||
// Store the current position so we can loop here after the song ends.
|
||||
loopPoint = writer->GetBaseAddress();
|
||||
// Left note channel
|
||||
WriteLdchan(writer, 0, 0); // Fill in the actual address later
|
||||
// Right note channel
|
||||
WriteLdchan(writer, 1, 0); // Fill in the actual address later
|
||||
|
||||
WriteVolSHeader(writer, 127); // Max volume
|
||||
WriteTempo(writer, tempo);
|
||||
|
||||
WriteDelay(writer, delay);
|
||||
if (looped) {
|
||||
WriteJump(writer, loopPoint);
|
||||
}
|
||||
WriteDisablecan(writer, 0b11);
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
lChannelStart = writer->GetBaseAddress();
|
||||
// Left Channel header
|
||||
WriteNoshort(writer);
|
||||
lLayerPlaceholderOff = writer->GetBaseAddress();
|
||||
WriteLdlayer(writer, 0, 0);
|
||||
WritePan(writer, 0);
|
||||
WriteVolCHeader(writer, 127); // Max volume
|
||||
WriteBend(writer, 0);
|
||||
WriteInstrument(writer, 0);
|
||||
WriteDelay(writer, delay);
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
rChannelStart = writer->GetBaseAddress();
|
||||
// Right Channel header
|
||||
WriteNoshort(writer);
|
||||
rLayerPlaceholderOff = writer->GetBaseAddress();
|
||||
WriteLdlayer(writer, 1, 0);
|
||||
WritePan(writer, 127);
|
||||
WriteVolCHeader(writer, 127); // Max volume
|
||||
WriteBend(writer, 0);
|
||||
WriteInstrument(writer, 1);
|
||||
WriteDelay(writer, delay);
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
uint16_t placeHolder = writer->GetBaseAddress();
|
||||
writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start);
|
||||
WriteLdchan(writer, 0, lChannelStart);
|
||||
WriteLdchan(writer, 1, rChannelStart);
|
||||
writer->Seek(placeHolder, Ship::SeekOffsetType::Start);
|
||||
|
||||
// Left Note layer
|
||||
lLayerOffset = writer->GetBaseAddress();
|
||||
WriteLegato(writer);
|
||||
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
// Right Note layer
|
||||
rLayerOffset = writer->GetBaseAddress();
|
||||
WriteLegato(writer);
|
||||
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
|
||||
writer->Write(static_cast<uint8_t>(0xFF));
|
||||
|
||||
writer->Seek(lLayerPlaceholderOff, Ship::SeekOffsetType::Start);
|
||||
WriteLdlayer(writer, 0, lLayerOffset);
|
||||
writer->Seek(rLayerPlaceholderOff, Ship::SeekOffsetType::Start);
|
||||
WriteLdlayer(writer, 1, rLayerOffset);
|
||||
}
|
||||
|
||||
std::shared_ptr<Ship::IResource>
|
||||
ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) {
|
||||
if (!FileHasValidFormatAndReader(file, initData)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto sequence = std::make_shared<AudioSequence>(initData);
|
||||
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
|
||||
unsigned int i = 0;
|
||||
|
||||
sequence->sequence.medium =
|
||||
ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium"), initData->Path.c_str());
|
||||
sequence->sequence.cachePolicy =
|
||||
ResourceFactoryXMLSoundFontV0::CachePolicyToInt(child->Attribute("CachePolicy"), initData->Path.c_str());
|
||||
sequence->sequence.seqDataSize = child->IntAttribute("Size");
|
||||
sequence->sequence.seqNumber = child->IntAttribute("Index");
|
||||
bool streamed = child->BoolAttribute("Streamed");
|
||||
|
||||
memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts));
|
||||
|
||||
tinyxml2::XMLElement* fontsElement = child->FirstChildElement();
|
||||
tinyxml2::XMLElement* fontElement = fontsElement->FirstChildElement();
|
||||
while (fontElement != nullptr) {
|
||||
sequence->sequence.fonts[i] = fontElement->IntAttribute("FontIdx");
|
||||
fontElement = fontElement->NextSiblingElement();
|
||||
i++;
|
||||
}
|
||||
sequence->sequence.numFonts = i;
|
||||
|
||||
const char* path = child->Attribute("Path");
|
||||
std::shared_ptr<Ship::File> seqFile;
|
||||
if (path != nullptr) {
|
||||
seqFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path);
|
||||
}
|
||||
|
||||
if (!streamed) {
|
||||
sequence->sequence.seqDataSize = seqFile->Buffer.get()->size();
|
||||
sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()];
|
||||
memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size());
|
||||
} else {
|
||||
// setting numFonts to -1 tells the game's audio engine the sound font to used is CRC64 encoded in the font
|
||||
// indicies.
|
||||
sequence->sequence.numFonts = -1;
|
||||
if (path != nullptr) {
|
||||
sequence->sequence.seqDataSize = seqFile->Buffer.get()->size();
|
||||
sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()];
|
||||
memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size());
|
||||
} else {
|
||||
unsigned int length = child->UnsignedAttribute("Length");
|
||||
bool looped = child->BoolAttribute("Looped", true);
|
||||
bool stereo = child->BoolAttribute("Stereo", false);
|
||||
Ship::BinaryWriter writer = Ship::BinaryWriter();
|
||||
writer.SetEndianness(Ship::Endianness::Big);
|
||||
|
||||
// 1 second worth of ticks can be found by using `ticks = 60 / (bpm * 48)`
|
||||
// Get the number of ticks per second and then divide the length by this number to get the number of ticks
|
||||
// for the song.
|
||||
constexpr uint8_t TEMPO = 1;
|
||||
constexpr float TEMPO_F = TEMPO;
|
||||
// Use floats for this first calculation so we can round up
|
||||
float delayF = length / (60.0f / (TEMPO_F * 48.0f));
|
||||
// Convert to u16. This way this value is encoded changes depending on the value.
|
||||
// It can be at most 0xFFFF so store it in a u16 for now.
|
||||
uint16_t delay;
|
||||
if (delayF >= 65535.0f) {
|
||||
delay = 0x7FFF;
|
||||
} else {
|
||||
delay = delayF;
|
||||
}
|
||||
if (stereo) {
|
||||
WriteStereoSingleSeq(&writer, delay, TEMPO, looped);
|
||||
} else {
|
||||
WriteMonoSingleSeq(&writer, delay, TEMPO, looped);
|
||||
}
|
||||
sequence->sequence.seqDataSize = writer.ToVector().size();
|
||||
sequence->sequence.seqData = new char[sequence->sequence.seqDataSize];
|
||||
memcpy(sequence->sequence.seqData, writer.ToVector().data(), sequence->sequence.seqDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
} // namespace SOH
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Resource.h"
|
||||
#include "ResourceFactoryBinary.h"
|
||||
#include "ResourceFactoryXML.h"
|
||||
|
||||
namespace SOH {
|
||||
class ResourceFactoryBinaryAudioSequenceV2 final : public Ship::ResourceFactoryBinary {
|
||||
|
@ -9,4 +10,11 @@ class ResourceFactoryBinaryAudioSequenceV2 final : public Ship::ResourceFactoryB
|
|||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
};
|
||||
|
||||
class ResourceFactoryXMLAudioSequenceV0 final : public Ship::ResourceFactoryXML {
|
||||
public:
|
||||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
};
|
||||
|
||||
} // namespace SOH
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#include "soh/resource/importer/AudioSoundFontFactory.h"
|
||||
#include "soh/resource/type/AudioSoundFont.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "libultraship/libultraship.h"
|
||||
#include <tinyxml2.h>
|
||||
#include <z64.h>
|
||||
#include "z64audio.h"
|
||||
#include "Context.h"
|
||||
#include "resource/archive/Archive.h"
|
||||
#include "resource/ResourceManager.h"
|
||||
|
||||
namespace SOH {
|
||||
std::shared_ptr<Ship::IResource>
|
||||
|
@ -35,117 +39,115 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
|
|||
audioSoundFont->soundFont.numSfx = soundEffectCount;
|
||||
|
||||
// 🥁 DRUMS 🥁
|
||||
audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums);
|
||||
// audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums);
|
||||
audioSoundFont->drumAddresses.reserve(audioSoundFont->soundFont.numDrums);
|
||||
for (uint32_t i = 0; i < audioSoundFont->soundFont.numDrums; i++) {
|
||||
Drum drum;
|
||||
drum.releaseRate = reader->ReadUByte();
|
||||
drum.pan = reader->ReadUByte();
|
||||
drum.loaded = reader->ReadUByte();
|
||||
drum.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont
|
||||
Drum* drum = new Drum;
|
||||
drum->releaseRate = reader->ReadUByte();
|
||||
drum->pan = reader->ReadUByte();
|
||||
drum->loaded = reader->ReadUByte();
|
||||
drum->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName
|
||||
|
||||
uint32_t envelopeCount = reader->ReadUInt32();
|
||||
audioSoundFont->drumEnvelopeCounts.push_back(envelopeCount);
|
||||
std::vector<AdsrEnvelope> drumEnvelopes;
|
||||
drumEnvelopes.reserve(audioSoundFont->drumEnvelopeCounts[i]);
|
||||
for (uint32_t j = 0; j < audioSoundFont->drumEnvelopeCounts.back(); j++) {
|
||||
AdsrEnvelope env;
|
||||
|
||||
drum->envelope = new AdsrEnvelope[envelopeCount];
|
||||
for (uint32_t j = 0; j < envelopeCount; j++) {
|
||||
int16_t delay = reader->ReadInt16();
|
||||
int16_t arg = reader->ReadInt16();
|
||||
|
||||
env.delay = BE16SWAP(delay);
|
||||
env.arg = BE16SWAP(arg);
|
||||
|
||||
drumEnvelopes.push_back(env);
|
||||
drum->envelope[j].delay = BE16SWAP(delay);
|
||||
drum->envelope[j].arg = BE16SWAP(arg);
|
||||
}
|
||||
audioSoundFont->drumEnvelopeArrays.push_back(drumEnvelopes);
|
||||
drum.envelope = audioSoundFont->drumEnvelopeArrays.back().data();
|
||||
|
||||
bool hasSample = reader->ReadInt8();
|
||||
std::string sampleFileName = reader->ReadString();
|
||||
drum.sound.tuning = reader->ReadFloat();
|
||||
drum->sound.tuning = reader->ReadFloat();
|
||||
|
||||
if (sampleFileName.empty()) {
|
||||
drum.sound.sample = nullptr;
|
||||
drum->sound.sample = nullptr;
|
||||
} else {
|
||||
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
|
||||
drum.sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
drum->sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
}
|
||||
|
||||
audioSoundFont->drums.push_back(drum);
|
||||
audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back());
|
||||
// audioSoundFont->drums.push_back(drum);
|
||||
// BENTODO clean this up in V3.
|
||||
if (drum->sound.sample == nullptr) {
|
||||
delete[] drum->envelope;
|
||||
delete drum;
|
||||
audioSoundFont->drumAddresses.push_back(nullptr);
|
||||
} else {
|
||||
audioSoundFont->drumAddresses.push_back(drum);
|
||||
}
|
||||
}
|
||||
audioSoundFont->soundFont.drums = audioSoundFont->drumAddresses.data();
|
||||
|
||||
// 🎺🎻🎷🎸🎹 INSTRUMENTS 🎹🎸🎷🎻🎺
|
||||
audioSoundFont->instruments.reserve(audioSoundFont->soundFont.numInstruments);
|
||||
for (uint32_t i = 0; i < audioSoundFont->soundFont.numInstruments; i++) {
|
||||
Instrument instrument;
|
||||
Instrument* instrument = new Instrument;
|
||||
|
||||
uint8_t isValidEntry = reader->ReadUByte();
|
||||
instrument.loaded = reader->ReadUByte();
|
||||
instrument.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont
|
||||
instrument->loaded = reader->ReadUByte();
|
||||
instrument->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName
|
||||
|
||||
instrument.normalRangeLo = reader->ReadUByte();
|
||||
instrument.normalRangeHi = reader->ReadUByte();
|
||||
instrument.releaseRate = reader->ReadUByte();
|
||||
instrument->normalRangeLo = reader->ReadUByte();
|
||||
instrument->normalRangeHi = reader->ReadUByte();
|
||||
instrument->releaseRate = reader->ReadUByte();
|
||||
|
||||
uint32_t envelopeCount = reader->ReadInt32();
|
||||
audioSoundFont->instrumentEnvelopeCounts.push_back(envelopeCount);
|
||||
std::vector<AdsrEnvelope> instrumentEnvelopes;
|
||||
for (uint32_t j = 0; j < audioSoundFont->instrumentEnvelopeCounts.back(); j++) {
|
||||
AdsrEnvelope env;
|
||||
instrument->envelope = new AdsrEnvelope[envelopeCount];
|
||||
|
||||
for (uint32_t j = 0; j < envelopeCount; j++) {
|
||||
int16_t delay = reader->ReadInt16();
|
||||
int16_t arg = reader->ReadInt16();
|
||||
|
||||
env.delay = BE16SWAP(delay);
|
||||
env.arg = BE16SWAP(arg);
|
||||
|
||||
instrumentEnvelopes.push_back(env);
|
||||
instrument->envelope[j].delay = BE16SWAP(delay);
|
||||
instrument->envelope[j].arg = BE16SWAP(arg);
|
||||
}
|
||||
audioSoundFont->instrumentEnvelopeArrays.push_back(instrumentEnvelopes);
|
||||
instrument.envelope = audioSoundFont->instrumentEnvelopeArrays.back().data();
|
||||
|
||||
bool hasLowNoteSoundFontEntry = reader->ReadInt8();
|
||||
if (hasLowNoteSoundFontEntry) {
|
||||
bool hasSampleRef = reader->ReadInt8();
|
||||
std::string sampleFileName = reader->ReadString();
|
||||
instrument.lowNotesSound.tuning = reader->ReadFloat();
|
||||
instrument->lowNotesSound.tuning = reader->ReadFloat();
|
||||
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
|
||||
instrument.lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
instrument->lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
} else {
|
||||
instrument.lowNotesSound.sample = nullptr;
|
||||
instrument.lowNotesSound.tuning = 0;
|
||||
instrument->lowNotesSound.sample = nullptr;
|
||||
instrument->lowNotesSound.tuning = 0;
|
||||
}
|
||||
|
||||
bool hasNormalNoteSoundFontEntry = reader->ReadInt8();
|
||||
if (hasNormalNoteSoundFontEntry) {
|
||||
// BENTODO remove in V3
|
||||
bool hasSampleRef = reader->ReadInt8();
|
||||
std::string sampleFileName = reader->ReadString();
|
||||
instrument.normalNotesSound.tuning = reader->ReadFloat();
|
||||
instrument->normalNotesSound.tuning = reader->ReadFloat();
|
||||
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
|
||||
instrument.normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
instrument->normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
} else {
|
||||
instrument.normalNotesSound.sample = nullptr;
|
||||
instrument.normalNotesSound.tuning = 0;
|
||||
instrument->normalNotesSound.sample = nullptr;
|
||||
instrument->normalNotesSound.tuning = 0;
|
||||
}
|
||||
|
||||
bool hasHighNoteSoundFontEntry = reader->ReadInt8();
|
||||
if (hasHighNoteSoundFontEntry) {
|
||||
bool hasSampleRef = reader->ReadInt8();
|
||||
std::string sampleFileName = reader->ReadString();
|
||||
instrument.highNotesSound.tuning = reader->ReadFloat();
|
||||
instrument->highNotesSound.tuning = reader->ReadFloat();
|
||||
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
|
||||
instrument.highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
instrument->highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
} else {
|
||||
instrument.highNotesSound.sample = nullptr;
|
||||
instrument.highNotesSound.tuning = 0;
|
||||
instrument->highNotesSound.sample = nullptr;
|
||||
instrument->highNotesSound.tuning = 0;
|
||||
}
|
||||
|
||||
audioSoundFont->instruments.push_back(instrument);
|
||||
audioSoundFont->instrumentAddresses.push_back(isValidEntry ? &audioSoundFont->instruments.back() : nullptr);
|
||||
if (isValidEntry) {
|
||||
audioSoundFont->instrumentAddresses.push_back(instrument);
|
||||
} else {
|
||||
delete[] instrument->envelope;
|
||||
delete instrument;
|
||||
audioSoundFont->instrumentAddresses.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
audioSoundFont->soundFont.instruments = audioSoundFont->instrumentAddresses.data();
|
||||
|
||||
|
@ -169,4 +171,302 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
|
|||
|
||||
return audioSoundFont;
|
||||
}
|
||||
|
||||
int8_t ResourceFactoryXMLSoundFontV0::MediumStrToInt(const char* str, const char* file) {
|
||||
if (!strcmp("Ram", str)) {
|
||||
return MEDIUM_RAM;
|
||||
} else if (!strcmp("Unk", str)) {
|
||||
return MEDIUM_UNK;
|
||||
} else if (!strcmp("Cart", str)) {
|
||||
return MEDIUM_CART;
|
||||
} else if (!strcmp("Disk", str)) {
|
||||
return MEDIUM_DISK_DRIVE;
|
||||
// 4 is skipped
|
||||
} else {
|
||||
char buff[2048];
|
||||
snprintf(buff, 2048, "Bad medium value in %s. Got %s, expected Ram, Unk, Cart, or Disk.", file, str);
|
||||
throw std::runtime_error(buff);
|
||||
}
|
||||
}
|
||||
|
||||
int8_t ResourceFactoryXMLSoundFontV0::CachePolicyToInt(const char* str, const char* file) {
|
||||
if (!strcmp("Temporary", str)) {
|
||||
return CACHE_TEMPORARY;
|
||||
} else if (!strcmp("Persistent", str)) {
|
||||
return CACHE_PERSISTENT;
|
||||
} else if (!strcmp("Either", str)) {
|
||||
return CACHE_EITHER;
|
||||
} else if (!strcmp("Permanent", str)) {
|
||||
return CACHE_PERMANENT;
|
||||
} else {
|
||||
char buff[2048];
|
||||
snprintf(buff, 2048,
|
||||
"Bad cache policy value in %s. Got %s, expected Temporary, Persistent, Either, or Permanent.", file,
|
||||
str);
|
||||
throw std::runtime_error(buff);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
|
||||
element = element->FirstChildElement();
|
||||
// No drums
|
||||
if (element == nullptr) {
|
||||
soundFont->soundFont.drums = nullptr;
|
||||
soundFont->soundFont.numDrums = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
int patch = element->IntAttribute("Patches", -1);
|
||||
Drum* drum;
|
||||
if (patch != -1) {
|
||||
drum = soundFont->drumAddresses[patch];
|
||||
} else {
|
||||
drum = new Drum;
|
||||
}
|
||||
std::vector<AdsrEnvelope> envelopes;
|
||||
drum->releaseRate = element->IntAttribute("ReleaseRate");
|
||||
drum->pan = element->IntAttribute("Pan");
|
||||
drum->loaded = element->IntAttribute("Loaded");
|
||||
drum->sound.tuning = element->FloatAttribute("Tuning");
|
||||
const char* sampleStr = element->Attribute("SampleRef");
|
||||
|
||||
if (sampleStr != nullptr && sampleStr[0] != 0) {
|
||||
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr);
|
||||
drum->sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
} else {
|
||||
drum->sound.sample = nullptr;
|
||||
}
|
||||
|
||||
element = element->FirstChildElement();
|
||||
if (!strcmp(element->Name(), "Envelopes")) {
|
||||
// element = (tinyxml2::XMLElement*)element->FirstChildElement();
|
||||
unsigned int envCount = 0;
|
||||
envelopes = ParseEnvelopes(soundFont, element, &envCount);
|
||||
element = (tinyxml2::XMLElement*)element->Parent();
|
||||
soundFont->drumEnvelopeArrays.push_back(envelopes);
|
||||
// If we are applying a patch the envelopes are already allocated
|
||||
// TODO revert this if we enable editing envelopes in a patch
|
||||
if (patch == -1) {
|
||||
drum->envelope = new AdsrEnvelope[envelopes.size()];
|
||||
}
|
||||
memcpy(drum->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope));
|
||||
} else {
|
||||
drum->envelope = nullptr;
|
||||
}
|
||||
|
||||
if (drum->sound.sample == nullptr) {
|
||||
soundFont->drumAddresses.push_back(nullptr);
|
||||
} else {
|
||||
soundFont->drumAddresses.push_back(drum);
|
||||
}
|
||||
|
||||
element = element->NextSiblingElement();
|
||||
} while (element != nullptr);
|
||||
|
||||
soundFont->soundFont.numDrums = soundFont->drumAddresses.size();
|
||||
soundFont->soundFont.drums = soundFont->drumAddresses.data();
|
||||
}
|
||||
|
||||
void ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
|
||||
element = element->FirstChildElement();
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
int patch = element->IntAttribute("Patches", -1);
|
||||
Instrument* instrument;
|
||||
// Same as drums, if applying a patch, don't re-allocate and clear.
|
||||
if (patch != -1) {
|
||||
instrument = soundFont->instrumentAddresses[patch];
|
||||
} else {
|
||||
instrument = new Instrument;
|
||||
memset(instrument, 0, sizeof(Instrument));
|
||||
}
|
||||
unsigned int envCount = 0;
|
||||
std::vector<AdsrEnvelope> envelopes;
|
||||
|
||||
int isValid = element->BoolAttribute("IsValid");
|
||||
instrument->loaded = element->IntAttribute("Loaded");
|
||||
instrument->normalRangeLo = element->IntAttribute("NormalRangeLo");
|
||||
instrument->normalRangeHi = element->IntAttribute("NormalRangeHi");
|
||||
instrument->releaseRate = element->IntAttribute("ReleaseRate");
|
||||
tinyxml2::XMLElement* instrumentElement = element->FirstChildElement();
|
||||
tinyxml2::XMLElement* instrumentElementCopy = instrumentElement;
|
||||
|
||||
if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) {
|
||||
envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount);
|
||||
if (patch == -1) {
|
||||
instrument->envelope = new AdsrEnvelope[envelopes.size()];
|
||||
}
|
||||
memcpy(instrument->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope));
|
||||
instrumentElement = instrumentElement->NextSiblingElement();
|
||||
}
|
||||
|
||||
if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) {
|
||||
instrument->lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
|
||||
const char* sampleStr = instrumentElement->Attribute("SampleRef");
|
||||
if (sampleStr != nullptr && sampleStr[0] != 0) {
|
||||
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
|
||||
if (res->tuning != -1.0f) {
|
||||
instrument->lowNotesSound.tuning = res->tuning;
|
||||
}
|
||||
instrument->lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
}
|
||||
instrumentElement = instrumentElement->NextSiblingElement();
|
||||
}
|
||||
|
||||
if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) {
|
||||
instrument->normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
|
||||
const char* sampleStr = instrumentElement->Attribute("SampleRef");
|
||||
if (sampleStr != nullptr && sampleStr[0] != 0) {
|
||||
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
|
||||
if (res->tuning != -1.0f) {
|
||||
instrument->normalNotesSound.tuning = res->tuning;
|
||||
}
|
||||
instrument->normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
}
|
||||
instrumentElement = instrumentElement->NextSiblingElement();
|
||||
}
|
||||
|
||||
if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) {
|
||||
instrument->highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
|
||||
const char* sampleStr = instrumentElement->Attribute("SampleRef");
|
||||
if (sampleStr != nullptr && sampleStr[0] != 0) {
|
||||
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
|
||||
if (res->tuning != -1.0f) {
|
||||
instrument->highNotesSound.tuning = res->tuning;
|
||||
}
|
||||
instrument->highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
}
|
||||
instrumentElement = instrumentElement->NextSiblingElement();
|
||||
}
|
||||
// Don't add it to the list if applying a patch
|
||||
if (patch == -1) {
|
||||
soundFont->instrumentAddresses.push_back(instrument);
|
||||
}
|
||||
element = instrumentElementCopy;
|
||||
element = (tinyxml2::XMLElement*)element->Parent();
|
||||
element = element->NextSiblingElement();
|
||||
} while (element != nullptr);
|
||||
|
||||
soundFont->soundFont.instruments = soundFont->instrumentAddresses.data();
|
||||
soundFont->soundFont.numInstruments = soundFont->instrumentAddresses.size();
|
||||
}
|
||||
|
||||
void ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
|
||||
size_t count = element->IntAttribute("Count");
|
||||
|
||||
element = element->FirstChildElement();
|
||||
|
||||
while (element != nullptr) {
|
||||
int patch = element->IntAttribute("Patches", -1);
|
||||
|
||||
SoundFontSound sound = {};
|
||||
|
||||
const char* sampleStr = element->Attribute("SampleRef");
|
||||
// Insert an empty sound effect. The game assumes the empty slots are
|
||||
// filled so we can't just skip them
|
||||
if (sampleStr == nullptr)
|
||||
goto skip;
|
||||
|
||||
sound.tuning = element->FloatAttribute("Tuning");
|
||||
if (sampleStr[0] != 0) {
|
||||
auto res = static_pointer_cast<SOH::AudioSample>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
|
||||
if (res->tuning != -1.0f) {
|
||||
sound.tuning = res->tuning;
|
||||
}
|
||||
sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
|
||||
}
|
||||
skip:
|
||||
element = element->NextSiblingElement();
|
||||
if (patch != -1) {
|
||||
soundFont->soundEffects[patch] = sound;
|
||||
} else {
|
||||
soundFont->soundEffects.push_back(sound);
|
||||
}
|
||||
}
|
||||
soundFont->soundFont.soundEffects = soundFont->soundEffects.data();
|
||||
soundFont->soundFont.numSfx = soundFont->soundEffects.size();
|
||||
}
|
||||
|
||||
std::vector<AdsrEnvelope> SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont,
|
||||
tinyxml2::XMLElement* element,
|
||||
unsigned int* count) {
|
||||
std::vector<AdsrEnvelope> envelopes;
|
||||
unsigned int total = 0;
|
||||
element = element->FirstChildElement("Envelope");
|
||||
while (element != nullptr) {
|
||||
AdsrEnvelope env = {
|
||||
.delay = (s16)element->IntAttribute("Delay"),
|
||||
.arg = (s16)element->IntAttribute("Arg"),
|
||||
};
|
||||
env.delay = BSWAP16(env.delay);
|
||||
env.arg = BSWAP16(env.arg);
|
||||
envelopes.emplace_back(env);
|
||||
element = element->NextSiblingElement("Envelope");
|
||||
total++;
|
||||
}
|
||||
*count = total;
|
||||
return envelopes;
|
||||
}
|
||||
|
||||
std::shared_ptr<Ship::IResource>
|
||||
ResourceFactoryXMLSoundFontV0::ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) {
|
||||
if (!FileHasValidFormatAndReader(file, initData)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
|
||||
const char* patch = child->Attribute("Patches");
|
||||
std::shared_ptr<Ship::IResource> sf;
|
||||
std::shared_ptr<AudioSoundFont> audioSoundFont;
|
||||
// If we are patching an existing SF, load the original, otherwise create and clear a new one.
|
||||
if (patch != nullptr) {
|
||||
std::string origName = "audio/fonts/";
|
||||
origName += patch;
|
||||
audioSoundFont = dynamic_pointer_cast<AudioSoundFont>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(origName));
|
||||
} else {
|
||||
audioSoundFont = std::make_shared<AudioSoundFont>(initData);
|
||||
memset(&audioSoundFont->soundFont, 0, sizeof(audioSoundFont->soundFont));
|
||||
}
|
||||
// Header data
|
||||
audioSoundFont->soundFont.fntIndex = child->IntAttribute("Num", 0);
|
||||
|
||||
const char* mediumStr = child->Attribute("Medium");
|
||||
audioSoundFont->medium = MediumStrToInt(mediumStr, initData->Path.c_str());
|
||||
|
||||
const char* cachePolicyStr = child->Attribute("CachePolicy");
|
||||
audioSoundFont->cachePolicy = CachePolicyToInt(cachePolicyStr, initData->Path.c_str());
|
||||
|
||||
audioSoundFont->data1 = child->IntAttribute("Data1");
|
||||
audioSoundFont->data2 = child->IntAttribute("Data2");
|
||||
audioSoundFont->data3 = child->IntAttribute("Data3");
|
||||
|
||||
audioSoundFont->soundFont.sampleBankId1 = audioSoundFont->data1 >> 8;
|
||||
audioSoundFont->soundFont.sampleBankId2 = audioSoundFont->data1 & 0xFF;
|
||||
|
||||
child = (tinyxml2::XMLElement*)child->FirstChildElement();
|
||||
|
||||
while (child != nullptr) {
|
||||
const char* name = child->Name();
|
||||
|
||||
if (!strcmp(name, "Drums")) {
|
||||
ParseDrums(audioSoundFont.get(), child);
|
||||
} else if (!strcmp(name, "Instruments")) {
|
||||
ParseInstruments(audioSoundFont.get(), child);
|
||||
} else if (!strcmp(name, "SfxTable")) {
|
||||
ParseSfxTable(audioSoundFont.get(), child);
|
||||
}
|
||||
child = child->NextSiblingElement();
|
||||
}
|
||||
return audioSoundFont;
|
||||
}
|
||||
|
||||
} // namespace SOH
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "Resource.h"
|
||||
#include "ResourceFactoryBinary.h"
|
||||
#include "ResourceFactoryXML.h"
|
||||
#include "soh/resource/type/AudioSoundFont.h"
|
||||
|
||||
namespace SOH {
|
||||
class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactoryBinary {
|
||||
|
@ -9,4 +11,20 @@ class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactory
|
|||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
};
|
||||
|
||||
class ResourceFactoryXMLSoundFontV0 final : public Ship::ResourceFactoryXML {
|
||||
public:
|
||||
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
|
||||
std::shared_ptr<Ship::ResourceInitData> initData) override;
|
||||
static int8_t MediumStrToInt(const char* str, const char* file);
|
||||
static int8_t CachePolicyToInt(const char* str, const char* file);
|
||||
|
||||
private:
|
||||
void ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
|
||||
void ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
|
||||
void ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
|
||||
std::vector<AdsrEnvelope> ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element,
|
||||
unsigned int* count);
|
||||
};
|
||||
|
||||
} // namespace SOH
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
#include "AudioSample.h"
|
||||
|
||||
namespace SOH {
|
||||
AudioSample::~AudioSample() {
|
||||
if (sample.book != nullptr && sample.book->book != nullptr) {
|
||||
delete[] sample.book->book;
|
||||
}
|
||||
if (sample.sampleAddr != nullptr) {
|
||||
delete[] sample.sampleAddr;
|
||||
}
|
||||
}
|
||||
Sample* AudioSample::GetPointer() {
|
||||
return &sample;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "Resource.h"
|
||||
#include <libultraship/libultra/types.h>
|
||||
|
||||
namespace SOH {
|
||||
typedef struct {
|
||||
/* 0x00 */ uintptr_t start;
|
||||
/* 0x04 */ uintptr_t end;
|
||||
/* 0x00 */ u32 start;
|
||||
/* 0x04 */ u32 end;
|
||||
/* 0x08 */ u32 count;
|
||||
/* 0x0C */ char unk_0C[0x4];
|
||||
/* 0x10 */ s16 state[16]; // only exists if count != 0. 8-byte aligned
|
||||
|
@ -20,24 +19,26 @@ typedef struct {
|
|||
/* 0x08 */ s16* book; // size 8 * order * npredictors. 8-byte aligned
|
||||
} AdpcmBook; // s
|
||||
|
||||
typedef struct {
|
||||
typedef struct Sample {
|
||||
union {
|
||||
struct {
|
||||
/* 0x00 */ u32 codec : 4;
|
||||
/* 0x00 */ u32 medium : 2;
|
||||
/* 0x00 */ u32 unk_bit26 : 1;
|
||||
/* 0x00 */ u32 unk_bit25 : 1; // this has been named isRelocated in zret
|
||||
/* 0x01 */ u32 size : 24;
|
||||
///* 0x0 */ u32 unk_0 : 1;
|
||||
/* 0x0 */ u32 codec : 4; // The state of compression or decompression, See `SampleCodec`
|
||||
/* 0x0 */ u32 medium : 2; // Medium where sample is currently stored. See `SampleMedium`
|
||||
/* 0x0 */ u32 unk_bit26 : 1;
|
||||
/* 0x0 */ u32 isRelocated : 1; // Has the sample header been relocated (offsets to pointers)
|
||||
};
|
||||
u32 asU32;
|
||||
};
|
||||
|
||||
/* 0x04 */ u8* sampleAddr;
|
||||
/* 0x08 */ AdpcmLoop* loop;
|
||||
/* 0x0C */ AdpcmBook* book;
|
||||
u32 sampleRateMagicValue; // For wav samples only...
|
||||
s32 sampleRate; // For wav samples only...
|
||||
} Sample; // size = 0x10
|
||||
/* 0x1 */ u32 size; // Size of the sample
|
||||
u32 fileSize;
|
||||
/* 0x4 */ u8* sampleAddr; // Raw sample data. Offset from the start of the sample bank or absolute address to either
|
||||
// rom or ram
|
||||
/* 0x8 */ AdpcmLoop*
|
||||
loop; // Adpcm loop parameters used by the sample. Offset from the start of the sound font / pointer to ram
|
||||
/* 0xC */ AdpcmBook*
|
||||
book; // Adpcm book parameters used by the sample. Offset from the start of the sound font / pointer to ram
|
||||
} Sample; // size = 0x10
|
||||
|
||||
class AudioSample : public Ship::Resource<Sample> {
|
||||
public:
|
||||
|
@ -45,18 +46,15 @@ class AudioSample : public Ship::Resource<Sample> {
|
|||
|
||||
AudioSample() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
|
||||
}
|
||||
~AudioSample();
|
||||
|
||||
Sample* GetPointer();
|
||||
size_t GetPointerSize();
|
||||
|
||||
Sample sample;
|
||||
std::vector<uint8_t> audioSampleData;
|
||||
|
||||
AdpcmLoop loop;
|
||||
uint32_t loopStateCount;
|
||||
|
||||
AdpcmBook book;
|
||||
uint32_t bookDataCount;
|
||||
std::vector<int16_t> bookData;
|
||||
// Only applies to streamed audio
|
||||
float tuning = -1.0f;
|
||||
};
|
||||
}; // namespace SOH
|
||||
}; // namespace SOH
|
|
@ -9,4 +9,10 @@ Sequence* AudioSequence::GetPointer() {
|
|||
size_t AudioSequence::GetPointerSize() {
|
||||
return sizeof(Sequence);
|
||||
}
|
||||
} // namespace SOH
|
||||
|
||||
AudioSequence::~AudioSequence() {
|
||||
delete[] sequence.seqData;
|
||||
sequence.seqData = nullptr;
|
||||
}
|
||||
|
||||
} // namespace SOH
|
|
@ -1,19 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "Resource.h"
|
||||
#include <libultraship/libultra/types.h>
|
||||
|
||||
namespace SOH {
|
||||
|
||||
typedef struct {
|
||||
char* seqData;
|
||||
int32_t seqDataSize;
|
||||
uint32_t seqDataSize;
|
||||
uint16_t seqNumber;
|
||||
uint8_t medium;
|
||||
uint8_t cachePolicy;
|
||||
int32_t numFonts;
|
||||
uint32_t numFonts;
|
||||
uint8_t fonts[16];
|
||||
} Sequence;
|
||||
|
||||
|
@ -23,11 +21,11 @@ class AudioSequence : public Ship::Resource<Sequence> {
|
|||
|
||||
AudioSequence() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
|
||||
}
|
||||
~AudioSequence();
|
||||
|
||||
Sequence* GetPointer();
|
||||
size_t GetPointerSize();
|
||||
|
||||
Sequence sequence;
|
||||
std::vector<char> sequenceData;
|
||||
};
|
||||
}; // namespace SOH
|
||||
}; // namespace SOH
|
|
@ -1,6 +1,23 @@
|
|||
#include "AudioSoundFont.h"
|
||||
|
||||
namespace SOH {
|
||||
|
||||
AudioSoundFont::~AudioSoundFont() {
|
||||
for (auto i : instrumentAddresses) {
|
||||
if (i != nullptr) {
|
||||
delete[] i->envelope;
|
||||
delete i;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto d : drumAddresses) {
|
||||
if (d != nullptr) {
|
||||
delete[] d->envelope;
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoundFont* AudioSoundFont::GetPointer() {
|
||||
return &soundFont;
|
||||
}
|
||||
|
@ -8,4 +25,4 @@ SoundFont* AudioSoundFont::GetPointer() {
|
|||
size_t AudioSoundFont::GetPointerSize() {
|
||||
return sizeof(SoundFont);
|
||||
}
|
||||
} // namespace SOH
|
||||
} // namespace SOH
|
|
@ -58,6 +58,7 @@ class AudioSoundFont : public Ship::Resource<SoundFont> {
|
|||
|
||||
AudioSoundFont() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
|
||||
}
|
||||
~AudioSoundFont();
|
||||
|
||||
SoundFont* GetPointer();
|
||||
size_t GetPointerSize();
|
||||
|
@ -68,15 +69,10 @@ class AudioSoundFont : public Ship::Resource<SoundFont> {
|
|||
uint16_t data2;
|
||||
uint16_t data3;
|
||||
|
||||
std::vector<Drum> drums;
|
||||
std::vector<Drum*> drumAddresses;
|
||||
std::vector<uint32_t> drumEnvelopeCounts;
|
||||
std::vector<std::vector<AdsrEnvelope>> drumEnvelopeArrays;
|
||||
|
||||
std::vector<Instrument> instruments;
|
||||
std::vector<Instrument*> instrumentAddresses;
|
||||
std::vector<uint32_t> instrumentEnvelopeCounts;
|
||||
std::vector<std::vector<AdsrEnvelope>> instrumentEnvelopeArrays;
|
||||
|
||||
std::vector<SoundFontSound> soundEffects;
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ void func_800DDE3C(void) {
|
|||
void AudioHeap_ResetLoadStatus(void) {
|
||||
s32 i;
|
||||
|
||||
for (i = 0; i < 0x30; i++) {
|
||||
for (i = 0; i < fontMapSize; i++) {
|
||||
if (gAudioContext.fontLoadStatus[i] != 5) {
|
||||
gAudioContext.fontLoadStatus[i] = 0;
|
||||
}
|
||||
|
@ -940,7 +940,7 @@ void AudioHeap_Init(void) {
|
|||
reverb->sample.sampleAddr = (u8*)reverb->leftRingBuf;
|
||||
reverb->loop.start = 0;
|
||||
reverb->loop.count = 1;
|
||||
reverb->loop.end = reverb->windowSize;
|
||||
reverb->loop.loopEnd = reverb->windowSize;
|
||||
|
||||
if (reverb->downsampleRate != 1) {
|
||||
reverb->unk_0E = 0x8000 / reverb->downsampleRate;
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
#include "soh/Enhancements/audio/AudioCollection.h"
|
||||
#include "soh/Enhancements/audio/AudioEditor.h"
|
||||
#include "soh/ResourceManagerHelpers.h"
|
||||
#include <stdio.h>
|
||||
#ifdef _MSC_VER
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
|
||||
#define MK_ASYNC_MSG(retData, tableType, id, status) (((retData) << 24) | ((tableType) << 16) | ((id) << 8) | (status))
|
||||
#define ASYNC_TBLTYPE(v) ((u8)(v >> 16))
|
||||
|
@ -82,7 +86,8 @@ char** sequenceMap;
|
|||
size_t sequenceMapSize;
|
||||
// A map of authentic sequence IDs to their cache policies, for use with sequence swapping.
|
||||
u8 seqCachePolicyMap[MAX_AUTHENTIC_SEQID];
|
||||
char* fontMap[256];
|
||||
size_t fontMapSize;
|
||||
char** fontMap;
|
||||
|
||||
uintptr_t fontStart;
|
||||
uint32_t fontOffsets[8192];
|
||||
|
@ -419,7 +424,7 @@ void AudioLoad_SyncLoadSeqParts(s32 seqId, s32 arg1) {
|
|||
s32 AudioLoad_SyncLoadSample(SoundFontSample* sample, s32 fontId) {
|
||||
void* sampleAddr;
|
||||
|
||||
if (sample->unk_bit25 == 1) {
|
||||
if (sample->isRelocated == 1) {
|
||||
if (sample->medium != MEDIUM_RAM) {
|
||||
sampleAddr = AudioHeap_AllocSampleCache(sample->size, fontId, (void*)sample->sampleAddr, sample->medium,
|
||||
CACHE_PERSISTENT);
|
||||
|
@ -701,7 +706,7 @@ SoundFontData* AudioLoad_SyncLoadFont(u32 fontId) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
|
||||
sampleBankId1 = sf->sampleBankId1;
|
||||
sampleBankId2 = sf->sampleBankId2;
|
||||
|
@ -759,7 +764,7 @@ uintptr_t AudioLoad_SyncLoad(u32 tableType, u32 id, s32* didAllocate) {
|
|||
cachePolicy = sData.cachePolicy;
|
||||
romAddr = 0;
|
||||
} else if (tableType == FONT_TABLE) {
|
||||
fnt = ResourceMgr_LoadAudioSoundFont(fontMap[id]);
|
||||
fnt = ResourceMgr_LoadAudioSoundFontByName(fontMap[id]);
|
||||
size = sizeof(SoundFont);
|
||||
medium = 2;
|
||||
cachePolicy = 0;
|
||||
|
@ -887,6 +892,7 @@ AudioTable* AudioLoad_GetLoadTable(s32 tableType) {
|
|||
}
|
||||
|
||||
void AudioLoad_RelocateFont(s32 fontId, SoundFontData* mem, RelocInfo* relocInfo) {
|
||||
return;
|
||||
uintptr_t reloc;
|
||||
uintptr_t reloc2;
|
||||
Instrument* inst;
|
||||
|
@ -898,7 +904,7 @@ void AudioLoad_RelocateFont(s32 fontId, SoundFontData* mem, RelocInfo* relocInfo
|
|||
s32 numInstruments = 0;
|
||||
s32 numSfx = 0;
|
||||
|
||||
sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
numDrums = sf->numDrums;
|
||||
numInstruments = sf->numInstruments;
|
||||
numSfx = sf->numSfx;
|
||||
|
@ -1247,12 +1253,13 @@ int strcmp_sort(const void* str1, const void* str2) {
|
|||
}
|
||||
|
||||
void AudioLoad_Init(void* heap, size_t heapSize) {
|
||||
char pad[0x48];
|
||||
s32 pad1[9];
|
||||
s32 numFonts;
|
||||
void* temp_v0_3;
|
||||
s32 pad2[2];
|
||||
u8* audioCtxPtr;
|
||||
void* addr;
|
||||
s32 i;
|
||||
u64* heapP;
|
||||
s16* u2974p;
|
||||
s32 j;
|
||||
|
||||
D_801755D0 = NULL;
|
||||
gAudioContext.resetTimer = 0;
|
||||
|
@ -1266,10 +1273,12 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
gAudioContext.unk_2960 = 20.03042f;
|
||||
gAudioContext.refreshRate = 50;
|
||||
break;
|
||||
|
||||
case OS_TV_MPAL:
|
||||
gAudioContext.unk_2960 = 16.546f;
|
||||
gAudioContext.refreshRate = 60;
|
||||
break;
|
||||
|
||||
case OS_TV_NTSC:
|
||||
default:
|
||||
gAudioContext.unk_2960 = 16.713f;
|
||||
|
@ -1278,7 +1287,7 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
|
||||
Audio_InitMesgQueues();
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
for (i = 0; i < ARRAY_COUNT(gAudioContext.aiBufLengths); i++) {
|
||||
gAudioContext.aiBufLengths[i] = 0xA0;
|
||||
}
|
||||
|
||||
|
@ -1289,12 +1298,14 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
gAudioContext.currTask = NULL;
|
||||
gAudioContext.rspTask[0].task.t.data_size = 0;
|
||||
gAudioContext.rspTask[1].task.t.data_size = 0;
|
||||
|
||||
osCreateMesgQueue(&gAudioContext.syncDmaQueue, &gAudioContext.syncDmaMesg, 1);
|
||||
osCreateMesgQueue(&gAudioContext.currAudioFrameDmaQueue, gAudioContext.currAudioFrameDmaMesgBuf, 0x40);
|
||||
osCreateMesgQueue(&gAudioContext.currAudioFrameDmaQueue, gAudioContext.currAudioFrameDmaMesgBuf,
|
||||
ARRAY_COUNT(gAudioContext.currAudioFrameDmaMesgBuf));
|
||||
osCreateMesgQueue(&gAudioContext.externalLoadQueue, gAudioContext.externalLoadMesgBuf,
|
||||
ARRAY_COUNT(gAudioContext.externalLoadMesgBuf));
|
||||
osCreateMesgQueue(&gAudioContext.preloadSampleQueue, gAudioContext.preloadSampleMesgBuf,
|
||||
ARRAY_COUNT(gAudioContext.externalLoadMesgBuf));
|
||||
ARRAY_COUNT(gAudioContext.preloadSampleMesgBuf));
|
||||
gAudioContext.curAudioFrameDmaCount = 0;
|
||||
gAudioContext.sampleDmaCount = 0;
|
||||
gAudioContext.cartHandle = osCartRomInit();
|
||||
|
@ -1304,20 +1315,24 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
gAudioContext.audioHeapSize = D_8014A6C4.heapSize;
|
||||
} else {
|
||||
void** hp = &heap;
|
||||
|
||||
gAudioContext.audioHeap = *hp;
|
||||
gAudioContext.audioHeapSize = heapSize;
|
||||
}
|
||||
|
||||
for (i = 0; i < gAudioContext.audioHeapSize / 8; i++) {
|
||||
for (i = 0; i < ((s32)gAudioContext.audioHeapSize / (s32)sizeof(u64)); i++) {
|
||||
((u64*)gAudioContext.audioHeap)[i] = 0;
|
||||
}
|
||||
|
||||
// Main Pool Split (split entirety of audio heap into initPool and sessionPool)
|
||||
AudioHeap_InitMainPools(D_8014A6C4.initPoolSize);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
// Initialize the audio interface buffers
|
||||
for (i = 0; i < ARRAY_COUNT(gAudioContext.aiBuffers); i++) {
|
||||
gAudioContext.aiBuffers[i] = AudioHeap_AllocZeroed(&gAudioContext.audioInitPool, AIBUF_LEN * sizeof(s16));
|
||||
}
|
||||
|
||||
// Connect audio tables to their tables in memory
|
||||
// gAudioContext.sequenceTable = (AudioTable*)gSequenceTable;
|
||||
// gAudioContext.soundFontTable = (AudioTable*)gSoundFontTable;
|
||||
// gAudioContext.sampleBankTable = (AudioTable*)gSampleBankTable;
|
||||
|
@ -1325,31 +1340,60 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
// gAudioContext.numSequences = gAudioContext.sequenceTable->numEntries;
|
||||
|
||||
gAudioContext.audioResetSpecIdToLoad = 0;
|
||||
gAudioContext.resetStatus = 1;
|
||||
|
||||
gAudioContext.resetStatus = 1; // Set reset to immediately initialize the audio heap
|
||||
AudioHeap_ResetStep();
|
||||
|
||||
// Initialize audio tables
|
||||
// AudioLoad_InitTable(gAudioContext.sequenceTable, SEGMENT_ROM_START(Audioseq), 0);
|
||||
// AudioLoad_InitTable(gAudioContext.soundFontTable, SEGMENT_ROM_START(Audiobank), 0);
|
||||
// AudioLoad_InitTable(gAudioContext.sampleBankTable, SEGMENT_ROM_START(Audiotable), 0);
|
||||
|
||||
// #region 2S2H [Port] Audio in the archive and custom sequences
|
||||
// Only load the original sequences right now because custom songs may require data from sound fonts and samples
|
||||
int seqListSize = 0;
|
||||
int customSeqListSize = 0;
|
||||
char** seqList = ResourceMgr_ListFiles("audio/sequences*", &seqListSize);
|
||||
char** customSeqList = ResourceMgr_ListFiles("custom/music/*", &customSeqListSize);
|
||||
sequenceMapSize = (size_t)(AudioCollection_SequenceMapSize() + customSeqListSize);
|
||||
sequenceMap = malloc(sequenceMapSize * sizeof(char*));
|
||||
gAudioContext.seqLoadStatus = malloc(sequenceMapSize * sizeof(char*));
|
||||
sequenceMapSize = (size_t)(seqListSize + customSeqListSize);
|
||||
sequenceMap = malloc((sequenceMapSize + 0xF) * sizeof(char*));
|
||||
|
||||
gAudioContext.seqLoadStatus = malloc(sequenceMapSize);
|
||||
memset(gAudioContext.seqLoadStatus, 5, sequenceMapSize);
|
||||
for (size_t i = 0; i < seqListSize; i++) {
|
||||
SequenceData sDat = ResourceMgr_LoadSeqByName(seqList[i]);
|
||||
|
||||
char* str = malloc(strlen(seqList[i]) + 1);
|
||||
strcpy(str, seqList[i]);
|
||||
|
||||
sequenceMap[sDat.seqNumber] = str;
|
||||
sequenceMap[sDat.seqNumber] = strdup(seqList[i]);
|
||||
seqCachePolicyMap[sDat.seqNumber] = sDat.cachePolicy;
|
||||
}
|
||||
|
||||
free(seqList);
|
||||
|
||||
int startingSeqNum = MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence
|
||||
// 2S2H [Streamed Audio] We need to load the custom songs after the fonts because streamed songs will use a hash to
|
||||
// find its soundfont
|
||||
int fntListSize = 0;
|
||||
int customFntListSize = 0;
|
||||
char** fntList = ResourceMgr_ListFiles("audio/fonts*", &fntListSize);
|
||||
char** customFntList = ResourceMgr_ListFiles("custom/fonts/*", &customFntListSize);
|
||||
|
||||
gAudioContext.fontLoadStatus = malloc(customFntListSize + fntListSize);
|
||||
fontMap = calloc(customFntListSize + fntListSize, sizeof(char*));
|
||||
fontMapSize = customFntListSize + fntListSize;
|
||||
for (int i = 0; i < fntListSize; i++) {
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fntList[i]);
|
||||
fontMap[sf->fntIndex] = strdup(fntList[i]);
|
||||
}
|
||||
|
||||
free(fntList);
|
||||
|
||||
int customFontStart = fntListSize;
|
||||
for (int i = customFontStart; i < customFntListSize + fntListSize; i++) {
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(customFntList[i - customFontStart]);
|
||||
sf->fntIndex = i;
|
||||
fontMap[i] = strdup(customFntList[i - customFontStart]);
|
||||
}
|
||||
free(customFntList);
|
||||
|
||||
// 2S2H Port I think we need to take use seqListSize because entry 0x7A is missing.
|
||||
int startingSeqNum = seqListSize; // MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence
|
||||
qsort(customSeqList, customSeqListSize, sizeof(char*), strcmp_sort);
|
||||
|
||||
// Because AudioCollection's sequenceMap actually has more than sequences (including instruments from 130-135 and
|
||||
|
@ -1361,48 +1405,61 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
|
|||
|
||||
for (size_t i = startingSeqNum; i < startingSeqNum + customSeqListSize; i++) {
|
||||
// ensure that what would be the next sequence number is actually unassigned in AudioCollection
|
||||
int j = i - startingSeqNum;
|
||||
SequenceData* sDat = ResourceMgr_LoadSeqPtrByName(customSeqList[j]);
|
||||
|
||||
if (sDat->numFonts == -1) {
|
||||
uint64_t crc;
|
||||
|
||||
memcpy(&crc, sDat->fonts, sizeof(uint64_t));
|
||||
const char* res = ResourceGetNameByCrc(crc);
|
||||
if (res == NULL) {
|
||||
// Passing a null buffer and length of 0 to snprintf will return the required numbers of characters the
|
||||
// buffer needs to be.
|
||||
int len =
|
||||
snprintf(NULL, 0, "Could not find sound font for sequence %s. It will not be in the audio editor.",
|
||||
customSeqList[j]);
|
||||
char* error = malloc(len + 1);
|
||||
snprintf(error, len, "Could not find sound font for sequence %s. It will not be in the audio editor.",
|
||||
customSeqList[j]);
|
||||
LUSLOG_ERROR("%s", error);
|
||||
Messagebox_ShowErrorBox("Invalid Sequence", error);
|
||||
free(error);
|
||||
continue;
|
||||
}
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(res);
|
||||
memset(&sDat->fonts[0], 0, sizeof(sDat->fonts));
|
||||
sDat->fonts[0] = sf->fntIndex;
|
||||
sDat->numFonts = 1;
|
||||
}
|
||||
|
||||
while (AudioCollection_HasSequenceNum(seqNum)) {
|
||||
seqNum++;
|
||||
}
|
||||
int j = i - startingSeqNum;
|
||||
|
||||
AudioCollection_AddToCollection(customSeqList[j], seqNum);
|
||||
SequenceData sDat = ResourceMgr_LoadSeqByName(customSeqList[j]);
|
||||
sDat.seqNumber = seqNum;
|
||||
|
||||
char* str = malloc(strlen(customSeqList[j]) + 1);
|
||||
strcpy(str, customSeqList[j]);
|
||||
|
||||
sequenceMap[sDat.seqNumber] = str;
|
||||
sDat->seqNumber = seqNum;
|
||||
printf("%d\n", seqNum);
|
||||
sequenceMap[sDat->seqNumber] = strdup(customSeqList[j]);
|
||||
seqNum++;
|
||||
}
|
||||
|
||||
free(customSeqList);
|
||||
|
||||
int fntListSize = 0;
|
||||
char** fntList = ResourceMgr_ListFiles("audio/fonts*", &fntListSize);
|
||||
|
||||
for (int i = 0; i < fntListSize; i++) {
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fntList[i]);
|
||||
|
||||
char* str = malloc(strlen(fntList[i]) + 1);
|
||||
strcpy(str, fntList[i]);
|
||||
|
||||
fontMap[sf->fntIndex] = str;
|
||||
}
|
||||
|
||||
numFonts = fntListSize;
|
||||
|
||||
free(fntList);
|
||||
// #end region
|
||||
gAudioContext.soundFonts = AudioHeap_Alloc(&gAudioContext.audioInitPool, numFonts * sizeof(SoundFont));
|
||||
|
||||
if (temp_v0_3 = AudioHeap_Alloc(&gAudioContext.audioInitPool, D_8014A6C4.permanentPoolSize), temp_v0_3 == NULL) {
|
||||
if (addr = AudioHeap_Alloc(&gAudioContext.audioInitPool, D_8014A6C4.permanentPoolSize), addr == NULL) {
|
||||
// cast away const from D_8014A6C4
|
||||
// *((u32*)&D_8014A6C4.permanentPoolSize) = 0;
|
||||
*((u32*)&D_8014A6C4.permanentPoolSize) = 0;
|
||||
}
|
||||
|
||||
AudioHeap_AllocPoolInit(&gAudioContext.permanentPool, temp_v0_3, D_8014A6C4.permanentPoolSize);
|
||||
AudioHeap_AllocPoolInit(&gAudioContext.permanentPool, addr, D_8014A6C4.permanentPoolSize);
|
||||
gAudioContextInitalized = true;
|
||||
osSendMesg32(gAudioContext.taskStartQueueP, gAudioContext.totalTaskCnt, OS_MESG_NOBLOCK);
|
||||
osSendMesg(gAudioContext.taskStartQueueP, OS_MESG_32(gAudioContext.totalTaskCnt), OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
void AudioLoad_InitSlowLoads(void) {
|
||||
|
@ -2059,7 +2116,7 @@ void AudioLoad_PreloadSamplesForFont(s32 fontId, s32 async, RelocInfo* relocInfo
|
|||
|
||||
gAudioContext.numUsedSamples = 0;
|
||||
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
|
||||
numDrums = sf->numDrums;
|
||||
numInstruments = sf->numInstruments;
|
||||
|
@ -2188,7 +2245,7 @@ void AudioLoad_LoadPermanentSamples(void) {
|
|||
fontId = AudioLoad_GetRealTableIndex(FONT_TABLE, gAudioContext.permanentCache[i].id);
|
||||
// fontId = gAudioContext.permanentCache[i].id;
|
||||
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
relocInfo.sampleBankId1 = sf->sampleBankId1;
|
||||
relocInfo.sampleBankId2 = sf->sampleBankId2;
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ void Audio_NoteInit(Note* note) {
|
|||
note->noteSubEu = gDefaultNoteSub;
|
||||
}
|
||||
|
||||
extern void aOPUSFree(struct OggOpusFile* opusFile);
|
||||
void Audio_NoteDisable(Note* note) {
|
||||
if (note->noteSubEu.bitField0.needsInit == true) {
|
||||
note->noteSubEu.bitField0.needsInit = false;
|
||||
|
@ -159,6 +160,10 @@ void Audio_NoteDisable(Note* note) {
|
|||
note->playbackState.prevParentLayer = NO_LAYER;
|
||||
note->playbackState.adsr.action.s.state = ADSR_STATE_DISABLED;
|
||||
note->playbackState.adsr.current = 0;
|
||||
if (note->synthesisState.opusFile != NULL) {
|
||||
aOPUSFree(note->synthesisState.opusFile);
|
||||
note->synthesisState.opusFile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Audio_ProcessNotes(void) {
|
||||
|
@ -293,13 +298,6 @@ void Audio_ProcessNotes(void) {
|
|||
|
||||
f32 resampRate = gAudioContext.audioBufferParameters.resampleRate;
|
||||
|
||||
// CUSTOM SAMPLE CHECK
|
||||
if (!noteSubEu2->bitField1.isSyntheticWave && noteSubEu2->sound.soundFontSound != NULL &&
|
||||
noteSubEu2->sound.soundFontSound->sample != NULL &&
|
||||
noteSubEu2->sound.soundFontSound->sample->sampleRateMagicValue == 'RIFF') {
|
||||
resampRate = CALC_RESAMPLE_FREQ(noteSubEu2->sound.soundFontSound->sample->sampleRate);
|
||||
}
|
||||
|
||||
subAttrs.frequency *= resampRate;
|
||||
|
||||
subAttrs.velocity *= scale;
|
||||
|
@ -335,7 +333,7 @@ Instrument* Audio_GetInstrumentInner(s32 fontId, s32 instId) {
|
|||
}
|
||||
|
||||
int instCnt = 0;
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
|
||||
if (instId >= sf->numInstruments)
|
||||
return NULL;
|
||||
|
@ -362,7 +360,7 @@ Drum* Audio_GetDrum(s32 fontId, s32 drumId) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
if (drumId < sf->numDrums) {
|
||||
drum = sf->drums[drumId];
|
||||
}
|
||||
|
@ -386,7 +384,7 @@ SoundFontSound* Audio_GetSfx(s32 fontId, s32 sfxId) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
|
||||
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
|
||||
if (sfxId < sf->numSfx) {
|
||||
sfx = &sf->soundEffects[sfxId];
|
||||
}
|
||||
|
|
|
@ -789,7 +789,7 @@ s32 AudioSeq_SeqLayerProcessScriptStep4(SequenceLayer* layer, s32 cmd) {
|
|||
layer->freqScale *= layer->unk_34;
|
||||
if (layer->delay == 0) {
|
||||
if (layer->sound != NULL) {
|
||||
time = (f32)layer->sound->sample->loop->end;
|
||||
time = (f32)layer->sound->sample->loop->loopEnd;
|
||||
} else {
|
||||
time = 0.0f;
|
||||
}
|
||||
|
|
|
@ -761,7 +761,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
|
|||
audioFontSample = noteSubEu->sound.soundFontSound->sample;
|
||||
|
||||
loopInfo = audioFontSample->loop;
|
||||
loopEndPos = loopInfo->end;
|
||||
loopEndPos = loopInfo->loopEnd;
|
||||
sampleAddr = audioFontSample->sampleAddr;
|
||||
resampledTempLen = 0;
|
||||
|
||||
|
@ -853,14 +853,27 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
|
|||
s5 = samplesLenAdjusted;
|
||||
goto skip;
|
||||
case CODEC_S16:
|
||||
AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, (samplesLenAdjusted * 2) + 0x20);
|
||||
AudioSynth_LoadBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, ALIGN16(nSamplesToLoad * 2),
|
||||
audioFontSample->sampleAddr + (synthState->samplePosInt * 2));
|
||||
|
||||
case CODEC_OPUS:
|
||||
AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, (samplesLenAdjusted + 16) * 2);
|
||||
flags = A_CONTINUE;
|
||||
skipBytes = 0;
|
||||
nSamplesProcessed = samplesLenAdjusted;
|
||||
s5 = samplesLenAdjusted;
|
||||
size_t bytesToRead;
|
||||
nSamplesProcessed += samplesLenAdjusted;
|
||||
|
||||
if (((synthState->samplePosInt * 2) + (samplesLenAdjusted)*2) < audioFontSample->size) {
|
||||
bytesToRead = (samplesLenAdjusted)*2;
|
||||
} else {
|
||||
bytesToRead = audioFontSample->size - (synthState->samplePosInt * 2);
|
||||
}
|
||||
// 2S2H [Port] [Custom audio] Handle decoding OPUS data
|
||||
if (audioFontSample->codec == CODEC_OPUS) {
|
||||
aOPUSdecImpl(sampleAddr, DMEM_UNCOMPRESSED_NOTE, bytesToRead, &synthState->opusFile,
|
||||
synthState->samplePosInt, audioFontSample->fileSize);
|
||||
} else {
|
||||
aLoadBuffer(cmd++, sampleAddr + (synthState->samplePosInt * 2), DMEM_UNCOMPRESSED_NOTE,
|
||||
bytesToRead);
|
||||
}
|
||||
|
||||
goto skip;
|
||||
case CODEC_REVERB:
|
||||
break;
|
||||
|
@ -886,6 +899,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
|
|||
sampleDataStartPad = (uintptr_t)sampleData & 0xF;
|
||||
aligned = ALIGN16((nFramesToDecode * frameSize) + 16);
|
||||
addr = DMEM_COMPRESSED_ADPCM_DATA - aligned;
|
||||
|
||||
aLoadBuffer(cmd++, sampleData - sampleDataStartPad, addr, aligned);
|
||||
} else {
|
||||
nSamplesToDecode = 0;
|
||||
|
@ -893,7 +907,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
|
|||
}
|
||||
|
||||
if (synthState->restart) {
|
||||
aSetLoop(cmd++, audioFontSample->loop->state);
|
||||
aSetLoop(cmd++, audioFontSample->loop->predictorState);
|
||||
flags = A_LOOP;
|
||||
synthState->restart = false;
|
||||
}
|
||||
|
|
|
@ -792,7 +792,7 @@ s32 func_800E6590(s32 playerIdx, s32 arg1, s32 arg2) {
|
|||
if (sound == NULL) {
|
||||
return 0;
|
||||
}
|
||||
loopEnd = sound->sample->loop->end;
|
||||
loopEnd = sound->sample->loop->loopEnd;
|
||||
samplePos = note->synthesisState.samplePosInt;
|
||||
return loopEnd - samplePos;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue