diff --git a/.github/workflows/apt-deps.txt b/.github/workflows/apt-deps.txt index 3461cdbac..dba3511bf 100644 --- a/.github/workflows/apt-deps.txt +++ b/.github/workflows/apt-deps.txt @@ -1 +1 @@ -libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev libespeak-ng-dev ninja-build +libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev ninja-build libogg-dev libopus-dev opus-tools libopusfile-dev libvorbis-dev libespeak-ng-dev \ No newline at end of file diff --git a/.github/workflows/macports-deps.txt b/.github/workflows/macports-deps.txt index d15039078..fd97d0c30 100644 --- a/.github/workflows/macports-deps.txt +++ b/.github/workflows/macports-deps.txt @@ -1 +1 @@ -libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal \ No newline at end of file +libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal libogg +universal libopus +universal opusfile +universal libvorbis +universal \ No newline at end of file diff --git a/CMake/FindOgg.cmake b/CMake/FindOgg.cmake new file mode 100644 index 000000000..9cf5ce430 --- /dev/null +++ b/CMake/FindOgg.cmake @@ -0,0 +1,61 @@ +# - Find ogg +# Find the native ogg includes and libraries +# +# OGG_INCLUDE_DIRS - where to find ogg.h, etc. +# OGG_LIBRARIES - List of libraries when using ogg. +# OGG_FOUND - True if ogg found. + +if (OGG_INCLUDE_DIR) + # Already in cache, be silent + set(OGG_FIND_QUIETLY TRUE) +endif () + +find_package (PkgConfig QUIET) +pkg_check_modules (PC_OGG QUIET ogg>=1.3.0) + +set (OGG_VERSION ${PC_OGG_VERSION}) + +find_path (OGG_INCLUDE_DIR ogg/ogg.h + HINTS + ${PC_OGG_INCLUDEDIR} + ${PC_OGG_INCLUDE_DIRS} + ${OGG_ROOT} + ) +# MSVC built ogg may be named ogg_static. +# The provided project files name the library with the lib prefix. +find_library (OGG_LIBRARY + NAMES + ogg + ogg_static + libogg + libogg_static + HINTS + ${PC_OGG_LIBDIR} + ${PC_OGG_LIBRARY_DIRS} + ${OGG_ROOT} + ) +# Handle the QUIETLY and REQUIRED arguments and set OGG_FOUND +# to TRUE if all listed variables are TRUE. +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (Ogg + REQUIRED_VARS + OGG_LIBRARY + OGG_INCLUDE_DIR + VERSION_VAR + OGG_VERSION + ) + +if (OGG_FOUND) + set (OGG_LIBRARIES ${OGG_LIBRARY}) + set (OGG_INCLUDE_DIRS ${OGG_INCLUDE_DIR}) + + if(NOT TARGET Ogg::ogg) + add_library(Ogg::ogg UNKNOWN IMPORTED) + set_target_properties(Ogg::ogg PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OGG_INCLUDE_DIRS}" + IMPORTED_LOCATION "${OGG_LIBRARIES}" + ) + endif () +endif () + +mark_as_advanced (OGG_INCLUDE_DIR OGG_LIBRARY) diff --git a/CMake/FindOpus.cmake b/CMake/FindOpus.cmake new file mode 100644 index 000000000..1e6e30d68 --- /dev/null +++ b/CMake/FindOpus.cmake @@ -0,0 +1,44 @@ +# - FindOpus.cmake +# Find the native opus includes and libraries +# +# OPUS_INCLUDE_DIRS - where to find opus/opus.h, etc. +# OPUS_LIBRARIES - List of libraries when using libopus(file). +# OPUS_FOUND - True if libopus found. + +if(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY) + # Already in cache, be silent + set(OPUS_FIND_QUIETLY TRUE) +endif(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY) + +find_path(OPUS_INCLUDE_DIR + NAMES opusfile.h + PATH_SUFFIXES opus +) + +# MSVC built opus may be named opus_static +# The provided project files name the library with the lib prefix. +find_library(OPUS_LIBRARY + NAMES opus opus_static libopus libopus_static +) +#find_library(OPUSFILE_LIBRARY +# NAMES opusfile opusfile_static libopusfile libopusfile_static +#) + +# Handle the QUIETLY and REQUIRED arguments and set OPUS_FOUND +# to TRUE if all listed variables are TRUE. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Opus DEFAULT_MSG + OPUS_LIBRARY OPUS_INCLUDE_DIR +) + +if(OPUS_FOUND) + set(OPUS_LIBRARIES ${OPUS_LIBRARY}) + set(OPUS_INCLUDE_DIRS ${OPUS_INCLUDE_DIR}) + if(NOT TARGET Opus::opus) + add_library(Opus::opus UNKNOWN IMPORTED) + set_target_properties(Opus::opus PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPUS_INCLUDE_DIRS}" + IMPORTED_LOCATION "${OPUS_LIBRARIES}" + ) + endif() +endif(OPUS_FOUND) diff --git a/CMake/FindOpusFile.cmake b/CMake/FindOpusFile.cmake new file mode 100644 index 000000000..ed3b4b7ba --- /dev/null +++ b/CMake/FindOpusFile.cmake @@ -0,0 +1,55 @@ +# FindOpusFile.cmake +# Locate the libopusfile library and its dependencies (libopus and libogg). +# Defines the following variables on success: +# OPUSFILE_FOUND - Indicates if opusfile was found +# OPUSFILE_INCLUDE_DIR - Directory containing opusfile.h +# OPUSFILE_LIBRARY - Path to the opusfile library +# OPUSFILE_LIBRARIES - Full list of libraries to link (opusfile, opus, ogg) + +# Search for the OpusFile header +find_path(OPUSFILE_INCLUDE_DIR + NAMES opusfile.h + PATHS /usr/include/opus /usr/local/include/opus /opt/local/include/opus /opt/homebrew/include/opus + DOC "Directory where opusfile.h is located" +) + +# Search for the OpusFile library +find_library(OPUSFILE_LIBRARY + NAMES opusfile + DOC "Path to the libopusfile library" +) + +# Search for the Opus library (dependency of OpusFile) +find_library(OPUS_LIBRARY + NAMES opus + DOC "Path to the libopus library (dependency of libopusfile)" +) + +# Search for the Ogg library (dependency of OpusFile) +find_library(OGG_LIBRARY + NAMES ogg + DOC "Path to the libogg library (dependency of libopusfile)" +) + +# Check if all required components are found +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpusFile + REQUIRED_VARS OPUSFILE_LIBRARY OPUSFILE_INCLUDE_DIR OPUS_LIBRARY OGG_LIBRARY + VERSION_VAR OPUSFILE_VERSION +) + +# Define an imported target if everything is found +if (OPUSFILE_FOUND) + add_library(Opusfile::Opusfile INTERFACE IMPORTED) + + set_target_properties(Opusfile::Opusfile PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPUSFILE_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${OPUSFILE_LIBRARY};${OPUS_LIBRARY};${OGG_LIBRARY}" + ) + + # Optionally expose the include and libraries separately + set(OPUSFILE_LIBRARIES ${OPUSFILE_LIBRARY} ${OPUS_LIBRARY} ${OGG_LIBRARY}) + set(OPUSFILE_INCLUDE_DIRS ${OPUSFILE_INCLUDE_DIR}) +else() + set(OPUSFILE_FOUND FALSE) +endif() diff --git a/CMake/FindVorbis.cmake b/CMake/FindVorbis.cmake new file mode 100644 index 000000000..57e60557f --- /dev/null +++ b/CMake/FindVorbis.cmake @@ -0,0 +1,210 @@ +#[=======================================================================[.rst: +FindVorbis +---------- + +Finds the native vorbis, vorbisenc amd vorbisfile includes and libraries. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``Vorbis::vorbis`` + The Vorbis library +``Vorbis::vorbisenc`` + The VorbisEnc library +``Vorbis::vorbisfile`` + The VorbisFile library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``Vorbis_Vorbis_INCLUDE_DIRS`` + List of include directories when using vorbis. +``Vorbis_Enc_INCLUDE_DIRS`` + List of include directories when using vorbisenc. +``Vorbis_File_INCLUDE_DIRS`` + List of include directories when using vorbisfile. +``Vorbis_Vorbis_LIBRARIES`` + List of libraries when using vorbis. +``Vorbis_Enc_LIBRARIES`` + List of libraries when using vorbisenc. +``Vorbis_File_LIBRARIES`` + List of libraries when using vorbisfile. +``Vorbis_FOUND`` + True if vorbis and requested components found. +``Vorbis_Vorbis_FOUND`` + True if vorbis found. +``Vorbis_Enc_FOUND`` + True if vorbisenc found. +``Vorbis_Enc_FOUND`` + True if vorbisfile found. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``Vorbis_Vorbis_INCLUDE_DIR`` + The directory containing ``vorbis/vorbis.h``. +``Vorbis_Enc_INCLUDE_DIR`` + The directory containing ``vorbis/vorbisenc.h``. +``Vorbis_File_INCLUDE_DIR`` + The directory containing ``vorbis/vorbisenc.h``. +``Vorbis_Vorbis_LIBRARY`` + The path to the vorbis library. +``Vorbis_Enc_LIBRARY`` + The path to the vorbisenc library. +``Vorbis_File_LIBRARY`` + The path to the vorbisfile library. + +Hints +^^^^^ + +A user may set ``Vorbis_ROOT`` to a vorbis installation root to tell this module where to look. + +#]=======================================================================] + +if (Vorbis_Vorbis_INCLUDE_DIR) + # Already in cache, be silent + set (Vorbis_FIND_QUIETLY TRUE) +endif () + +set (Vorbis_Vorbis_FIND_QUIETLY TRUE) +set (Vorbis_Enc_FIND_QUIETLY TRUE) +set (Vorbis_File_FIND_QUIETLY TRUE) + +find_package (Ogg QUIET) + +find_package (PkgConfig QUIET) +pkg_check_modules (PC_Vorbis_Vorbis QUIET vorbis) +pkg_check_modules (PC_Vorbis_Enc QUIET vorbisenc) +pkg_check_modules (PC_Vorbis_File QUIET vorbisfile) + +set (Vorbis_VERSION ${PC_Vorbis_Vorbis_VERSION}) + +find_path (Vorbis_Vorbis_INCLUDE_DIR vorbis/codec.h + HINTS + ${PC_Vorbis_Vorbis_INCLUDEDIR} + ${PC_Vorbis_Vorbis_INCLUDE_DIRS} + ${Vorbis_ROOT} + ) + +find_path (Vorbis_Enc_INCLUDE_DIR vorbis/vorbisenc.h + HINTS + ${PC_Vorbis_Enc_INCLUDEDIR} + ${PC_Vorbis_Enc_INCLUDE_DIRS} + ${Vorbis_ROOT} + ) + +find_path (Vorbis_File_INCLUDE_DIR vorbis/vorbisfile.h + HINTS + ${PC_Vorbis_File_INCLUDEDIR} + ${PC_Vorbis_File_INCLUDE_DIRS} + ${Vorbis_ROOT} + ) + +find_library (Vorbis_Vorbis_LIBRARY + NAMES + vorbis + vorbis_static + libvorbis + libvorbis_static + HINTS + ${PC_Vorbis_Vorbis_LIBDIR} + ${PC_Vorbis_Vorbis_LIBRARY_DIRS} + ${Vorbis_ROOT} + ) + +find_library (Vorbis_Enc_LIBRARY + NAMES + vorbisenc + vorbisenc_static + libvorbisenc + libvorbisenc_static + HINTS + ${PC_Vorbis_Enc_LIBDIR} + ${PC_Vorbis_Enc_LIBRARY_DIRS} + ${Vorbis_ROOT} + ) + +find_library (Vorbis_File_LIBRARY + NAMES + vorbisfile + vorbisfile_static + libvorbisfile + libvorbisfile_static + HINTS + ${PC_Vorbis_File_LIBDIR} + ${PC_Vorbis_File_LIBRARY_DIRS} + ${Vorbis_ROOT} + ) + +include (FindPackageHandleStandardArgs) + +if (Vorbis_Vorbis_LIBRARY AND Vorbis_Vorbis_INCLUDE_DIR AND Ogg_FOUND) + set (Vorbis_Vorbis_FOUND TRUE) +endif () + +if (Vorbis_Enc_LIBRARY AND Vorbis_Enc_INCLUDE_DIR AND Vorbis_Vorbis_FOUND) + set (Vorbis_Enc_FOUND TRUE) +endif () + +if (Vorbis_Vorbis_FOUND AND Vorbis_File_LIBRARY AND Vorbis_File_INCLUDE_DIR) + set (Vorbis_File_FOUND TRUE) +endif () + +find_package_handle_standard_args (Vorbis + REQUIRED_VARS + Vorbis_Vorbis_LIBRARY + Vorbis_Vorbis_INCLUDE_DIR + Ogg_FOUND + HANDLE_COMPONENTS + VERSION_VAR Vorbis_VERSION) + + +if (Vorbis_Vorbis_FOUND) + set (Vorbis_Vorbis_INCLUDE_DIRS ${VORBIS_INCLUDE_DIR}) + set (Vorbis_Vorbis_LIBRARIES ${VORBIS_LIBRARY} ${OGG_LIBRARIES}) + if (NOT TARGET Vorbis::vorbis) + add_library (Vorbis::vorbis UNKNOWN IMPORTED) + set_target_properties (Vorbis::vorbis PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vorbis_Vorbis_INCLUDE_DIR}" + IMPORTED_LOCATION "${Vorbis_Vorbis_LIBRARY}" + INTERFACE_LINK_LIBRARIES Ogg::ogg + ) + endif () + + if (Vorbis_Enc_FOUND) + set (Vorbis_Enc_INCLUDE_DIRS ${Vorbis_Enc_INCLUDE_DIR}) + set (Vorbis_Enc_LIBRARIES ${Vorbis_Enc_LIBRARY} ${Vorbis_Enc_LIBRARIES}) + if (NOT TARGET Vorbis::vorbisenc) + add_library (Vorbis::vorbisenc UNKNOWN IMPORTED) + set_target_properties (Vorbis::vorbisenc PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vorbis_Enc_INCLUDE_DIR}" + IMPORTED_LOCATION "${Vorbis_Enc_LIBRARY}" + INTERFACE_LINK_LIBRARIES Vorbis::vorbis + ) + endif () + endif () + + if (Vorbis_File_FOUND) + set (Vorbis_File_INCLUDE_DIRS ${Vorbis_File_INCLUDE_DIR}) + set (Vorbis_File_LIBRARIES ${Vorbis_File_LIBRARY} ${Vorbis_File_LIBRARIES}) + if (NOT TARGET Vorbis::vorbisfile) + add_library (Vorbis::vorbisfile UNKNOWN IMPORTED) + set_target_properties (Vorbis::vorbisfile PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vorbis_File_INCLUDE_DIR}" + IMPORTED_LOCATION "${Vorbis_File_LIBRARY}" + INTERFACE_LINK_LIBRARIES Vorbis::vorbis + ) + endif () + endif () + +endif () + +mark_as_advanced (Vorbis_Vorbis_INCLUDE_DIR Vorbis_Vorbis_LIBRARY) +mark_as_advanced (Vorbis_Enc_INCLUDE_DIR Vorbis_Enc_LIBRARY) +mark_as_advanced (Vorbis_File_INCLUDE_DIR Vorbis_File_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab90e00b9..7333da8d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,8 +83,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") set(VCPKG_TARGET_TRIPLET x64-windows-static) vcpkg_bootstrap() - vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3 nlohmann-json tinyxml2 spdlog) - + vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3 nlohmann-json tinyxml2 spdlog libogg libvorbis opus opusfile) if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) endif() @@ -154,6 +153,7 @@ set(GFX_DEBUG_DISASSEMBLER ON) # Tell LUS we're using F3DEX_GBI_2 (in a way that doesn't break libgfxd) set(GBI_UCODE F3DEX_GBI_2) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake") ################################################################################ # Set CONTROLLERBUTTONS_T diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 508836819..e74c14f4c 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -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" "$<$:SDL2_net::SDL2_net>" ${CMAKE_DL_LIBS} Threads::Threads diff --git a/soh/include/z64audio.h b/soh/include/z64audio.h index 81775feba..c562b820f 100644 --- a/soh/include/z64audio.h +++ b/soh/include/z64audio.h @@ -1,6 +1,10 @@ #ifndef Z64_AUDIO_H #define Z64_AUDIO_H +#ifdef __cplusplus +extern "C" { +#endif + #include #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); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index d63422551..0d40d670c 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -411,12 +411,19 @@ void OTRGlobals::Initialize() { static_cast(SOH::ResourceType::SOH_Text), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); + + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "Sample", static_cast(SOH::ResourceType::SOH_AudioSample), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "SoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, + "Sequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Background", static_cast(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] != '.'; diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 9e7815523..cab7575cd 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -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 diff --git a/soh/soh/ResourceManagerHelpers.cpp b/soh/soh/ResourceManagerHelpers.cpp index 57e0ab270..a9ec6fc25 100644 --- a/soh/soh/ResourceManagerHelpers.cpp +++ b/soh/soh/ResourceManagerHelpers.cpp @@ -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); } diff --git a/soh/soh/ResourceManagerHelpers.h b/soh/soh/ResourceManagerHelpers.h index 4b34a8a51..b760feae3 100644 --- a/soh/soh/ResourceManagerHelpers.h +++ b/soh/soh/ResourceManagerHelpers.h @@ -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(); diff --git a/soh/soh/mixer.c b/soh/soh/mixer.c index 98ac7b69e..d01a9bede 100644 --- a/soh/soh/mixer.c +++ b/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 #include #include +#include #include "mixer.h" - #ifndef __clang__ #pragma GCC optimize("unroll-loops") #endif @@ -14,6 +16,7 @@ #define ROUND_UP_8(v) (((v) + 7) & ~7) #define ROUND_DOWN_16(v) ((v) & ~0xf) + #define DMEM_BUF_SIZE (0x1000 - 0x3C0 - 0x40) #define BUF_U8(a) (rspa.buf.as_u8 + ((a)-0x3C0)) #define BUF_S16(a) (rspa.buf.as_s16 + ((a)-0x3C0) / sizeof(int16_t)) @@ -35,7 +38,7 @@ static struct { uint16_t filter_count; int16_t filter[8]; - union { + __attribute__((aligned(16))) union { int16_t as_s16[DMEM_BUF_SIZE / sizeof(int16_t)]; uint8_t as_u8[DMEM_BUF_SIZE]; } buf; @@ -66,6 +69,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 +105,33 @@ void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbyte #endif } +#include +#include + +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 +329,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 +356,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 +598,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 + +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 +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 diff --git a/soh/soh/mixer.h b/soh/soh/mixer.h index 6bde9621e..048135e44 100644 --- a/soh/soh/mixer.h +++ b/soh/soh/mixer.h @@ -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) diff --git a/soh/soh/resource/importer/AudioSampleFactory.cpp b/soh/soh/resource/importer/AudioSampleFactory.cpp index 0ce82de6f..99f2e558e 100644 --- a/soh/soh/resource/importer/AudioSampleFactory.cpp +++ b/soh/soh/resource/importer/AudioSampleFactory.cpp @@ -1,6 +1,191 @@ #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" +#define DR_WAV_IMPLEMENTATION +#include + +#define DR_MP3_IMPLEMENTATION +#include + +#define DR_FLAC_IMPLEMENTATION +#include + +#include +#include +#include "vorbis/vorbisfile.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(src); + size_t toRead = size * elems; + + if (toRead > data->size - data->pos) { + toRead = data->size - data->pos; + } + + memcpy(out, static_cast(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(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(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 audioSample, std::shared_ptr 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 audioSample, std::shared_ptr 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 audioSample, std::shared_ptr sampleFile, + std::shared_ptr 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 @@ -16,108 +201,155 @@ ResourceFactoryBinaryAudioSampleV2::ReadResource(std::shared_ptr 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 +ResourceFactoryXMLAudioSampleV0::ReadResource(std::shared_ptr file, + std::shared_ptr 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(initData); + auto child = std::get>(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 diff --git a/soh/soh/resource/importer/AudioSampleFactory.h b/soh/soh/resource/importer/AudioSampleFactory.h index 43b028fd2..665abff0a 100644 --- a/soh/soh/resource/importer/AudioSampleFactory.h +++ b/soh/soh/resource/importer/AudioSampleFactory.h @@ -2,11 +2,22 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" namespace SOH { class ResourceFactoryBinaryAudioSampleV2 final : public Ship::ResourceFactoryBinary { - public: - std::shared_ptr ReadResource(std::shared_ptr file, - std::shared_ptr initData) override; -}; + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; + }; + + class ResourceFactoryXMLAudioSampleV0 final : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; + + private: + static uint8_t CodecStrToInt(const char* str, const char* file); + }; + } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSequenceFactory.cpp b/soh/soh/resource/importer/AudioSequenceFactory.cpp index 4a4a06265..e2e7162e8 100644 --- a/soh/soh/resource/importer/AudioSequenceFactory.cpp +++ b/soh/soh/resource/importer/AudioSequenceFactory.cpp @@ -1,7 +1,13 @@ #include "soh/resource/importer/AudioSequenceFactory.h" +#include "soh/resource/importer/AudioSoundFontFactory.h" #include "soh/resource/type/AudioSequence.h" #include "spdlog/spdlog.h" +#include "Context.h" +#include "resource/archive/Archive.h" +#include "BinaryWriter.h" +#include + namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr file, @@ -13,12 +19,11 @@ ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr f auto audioSequence = std::make_shared(initData); auto reader = std::get>(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 +39,355 @@ ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr f return audioSequence; } -} // namespace SOH + +template static void WriteInsnOneArg(Ship::BinaryWriter* writer, uint8_t opcode, T arg) { + static_assert(std::is_fundamental::value); + writer->Write(opcode); + writer->Write(arg); +} + +template +static void WriteInsnTwoArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2) { + static_assert(std::is_fundamental::value && std::is_fundamental::value); + writer->Write(opcode); + writer->Write(arg1); + writer->Write(arg2); +} + +template +static void WriteInsnThreeArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2, T3 arg3) { + static_assert(std::is_fundamental::value && std::is_fundamental::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(delay | 0x8000)); + } else { + WriteInsnOneArg(writer, 0xFD, static_cast(delay)); + } +} + +template static void WriteLDelay(Ship::BinaryWriter* writer, T delay) { + WriteInsnOneArg(writer, 0xC0, delay); +} + +template 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(delay | 0x8000), velocity, gateTime); + } else { + WriteInsnThreeArg(writer, note, static_cast(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(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(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(0x7F), static_cast(1)); + writer->Write(static_cast(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(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(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(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(0x7F), static_cast(1)); + writer->Write(static_cast(0xFF)); + + // Right Note layer + rLayerOffset = writer->GetBaseAddress(); + WriteLegato(writer); + WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); + writer->Write(static_cast(0xFF)); + + writer->Seek(lLayerPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdlayer(writer, 0, lLayerOffset); + writer->Seek(rLayerPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdlayer(writer, 1, rLayerOffset); +} + +std::shared_ptr +ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr file, + std::shared_ptr initData) { + if (!FileHasValidFormatAndReader(file, initData)) { + return nullptr; + } + + auto sequence = std::make_shared(initData); + auto child = std::get>(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 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 \ No newline at end of file diff --git a/soh/soh/resource/importer/AudioSequenceFactory.h b/soh/soh/resource/importer/AudioSequenceFactory.h index 5ae4ed946..dc479418e 100644 --- a/soh/soh/resource/importer/AudioSequenceFactory.h +++ b/soh/soh/resource/importer/AudioSequenceFactory.h @@ -2,11 +2,19 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" namespace SOH { class ResourceFactoryBinaryAudioSequenceV2 final : public Ship::ResourceFactoryBinary { - public: - std::shared_ptr ReadResource(std::shared_ptr file, - std::shared_ptr initData) override; -}; + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; + }; + + class ResourceFactoryXMLAudioSequenceV0 final : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; + }; + } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.cpp b/soh/soh/resource/importer/AudioSoundFontFactory.cpp index 99f160860..e4a7aafbc 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.cpp +++ b/soh/soh/resource/importer/AudioSoundFontFactory.cpp @@ -1,7 +1,9 @@ #include "soh/resource/importer/AudioSoundFontFactory.h" #include "soh/resource/type/AudioSoundFont.h" -#include "spdlog/spdlog.h" -#include "libultraship/libultraship.h" +#include "z64.h" +#include "z64audio.h" +#include "Context.h" +#include "resource/archive/Archive.h" namespace SOH { std::shared_ptr @@ -35,117 +37,115 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr 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 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(res ? res->GetRawPointer() : nullptr); + drum->sound.sample = static_cast(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 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(res ? res->GetRawPointer() : nullptr); + instrument->lowNotesSound.sample = static_cast(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(res ? res->GetRawPointer() : nullptr); + instrument->normalNotesSound.sample = static_cast(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(res ? res->GetRawPointer() : nullptr); + instrument->highNotesSound.sample = static_cast(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 +169,302 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr 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 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(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 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 res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->lowNotesSound.tuning = res->tuning; + } + instrument->lowNotesSound.sample = static_cast(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 res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->normalNotesSound.tuning = res->tuning; + } + instrument->normalNotesSound.sample = static_cast(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 res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->highNotesSound.tuning = res->tuning; + } + instrument->highNotesSound.sample = static_cast(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( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + sound.tuning = res->tuning; + } + sound.sample = static_cast(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 SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, + tinyxml2::XMLElement* element, + unsigned int* count) { + std::vector 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 +ResourceFactoryXMLSoundFontV0::ReadResource(std::shared_ptr file, + std::shared_ptr initData) { + if (!FileHasValidFormatAndReader(file, initData)) { + return nullptr; + } + + auto child = std::get>(file->Reader)->FirstChildElement(); + const char* patch = child->Attribute("Patches"); + std::shared_ptr sf; + std::shared_ptr 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( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(origName)); + } else { + audioSoundFont = std::make_shared(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 diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.h b/soh/soh/resource/importer/AudioSoundFontFactory.h index b978a14bf..fa2f7b615 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.h +++ b/soh/soh/resource/importer/AudioSoundFontFactory.h @@ -2,11 +2,29 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" +#include "soh/resource/type/AudioSoundFont.h" namespace SOH { class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactoryBinary { - public: - std::shared_ptr ReadResource(std::shared_ptr file, - std::shared_ptr initData) override; -}; + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; + }; + + class ResourceFactoryXMLSoundFontV0 final : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr 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 ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, + unsigned int* count); + }; + } // namespace SOH diff --git a/soh/soh/resource/type/AudioSample.cpp b/soh/soh/resource/type/AudioSample.cpp index 288765822..17effa130 100644 --- a/soh/soh/resource/type/AudioSample.cpp +++ b/soh/soh/resource/type/AudioSample.cpp @@ -1,11 +1,19 @@ #include "AudioSample.h" namespace SOH { -Sample* AudioSample::GetPointer() { - return &sample; -} + 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; + } -size_t AudioSample::GetPointerSize() { - return sizeof(Sample); -} + size_t AudioSample::GetPointerSize() { + return sizeof(Sample); + } } // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSample.h b/soh/soh/resource/type/AudioSample.h index b64880801..77399b485 100644 --- a/soh/soh/resource/type/AudioSample.h +++ b/soh/soh/resource/type/AudioSample.h @@ -1,62 +1,60 @@ #pragma once #include -#include #include "Resource.h" #include namespace SOH { -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 { + /* 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 + } AdpcmLoop; // size = 0x30 (or 0x10) -typedef struct { - /* 0x00 */ s32 order; - /* 0x04 */ s32 npredictors; - /* 0x08 */ s16* book; // size 8 * order * npredictors. 8-byte aligned -} AdpcmBook; // s + typedef struct { + /* 0x00 */ s32 order; + /* 0x04 */ s32 npredictors; + /* 0x08 */ s16* book; // size 8 * order * npredictors. 8-byte aligned + } AdpcmBook; // s -typedef struct { - 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; + typedef struct Sample { + union { + struct { + ///* 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; }; - u32 asU32; + /* 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 { + public: + using Resource::Resource; + + AudioSample() : Resource(std::shared_ptr()) { + } + ~AudioSample(); + + Sample* GetPointer(); + size_t GetPointerSize(); + + Sample sample; + AdpcmLoop loop; + AdpcmBook book; + // Only applies to streamed audio + float tuning = -1.0f; }; - - /* 0x04 */ u8* sampleAddr; - /* 0x08 */ AdpcmLoop* loop; - /* 0x0C */ AdpcmBook* book; - u32 sampleRateMagicValue; // For wav samples only... - s32 sampleRate; // For wav samples only... -} Sample; // size = 0x10 - -class AudioSample : public Ship::Resource { - public: - using Resource::Resource; - - AudioSample() : Resource(std::shared_ptr()) { - } - - Sample* GetPointer(); - size_t GetPointerSize(); - - Sample sample; - std::vector audioSampleData; - - AdpcmLoop loop; - uint32_t loopStateCount; - - AdpcmBook book; - uint32_t bookDataCount; - std::vector bookData; -}; -}; // namespace SOH +}; // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSequence.cpp b/soh/soh/resource/type/AudioSequence.cpp index 41029d47d..1d84ee738 100644 --- a/soh/soh/resource/type/AudioSequence.cpp +++ b/soh/soh/resource/type/AudioSequence.cpp @@ -2,11 +2,17 @@ namespace SOH { -Sequence* AudioSequence::GetPointer() { - return &sequence; -} + Sequence* AudioSequence::GetPointer() { + return &sequence; + } -size_t AudioSequence::GetPointerSize() { - return sizeof(Sequence); -} -} // namespace SOH + size_t AudioSequence::GetPointerSize() { + return sizeof(Sequence); + } + + AudioSequence::~AudioSequence() { + delete[] sequence.seqData; + sequence.seqData = nullptr; + } + +} // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSequence.h b/soh/soh/resource/type/AudioSequence.h index 5ff4c9a35..9ca59043f 100644 --- a/soh/soh/resource/type/AudioSequence.h +++ b/soh/soh/resource/type/AudioSequence.h @@ -1,33 +1,31 @@ #pragma once #include -#include #include "Resource.h" -#include namespace SOH { -typedef struct { - char* seqData; - int32_t seqDataSize; - uint16_t seqNumber; - uint8_t medium; - uint8_t cachePolicy; - int32_t numFonts; - uint8_t fonts[16]; -} Sequence; + typedef struct { + char* seqData; + uint32_t seqDataSize; + uint16_t seqNumber; + uint8_t medium; + uint8_t cachePolicy; + uint32_t numFonts; + uint8_t fonts[16]; + } Sequence; -class AudioSequence : public Ship::Resource { - public: - using Resource::Resource; + class AudioSequence : public Ship::Resource { + public: + using Resource::Resource; - AudioSequence() : Resource(std::shared_ptr()) { - } + AudioSequence() : Resource(std::shared_ptr()) { + } + ~AudioSequence(); - Sequence* GetPointer(); - size_t GetPointerSize(); + Sequence* GetPointer(); + size_t GetPointerSize(); - Sequence sequence; - std::vector sequenceData; -}; -}; // namespace SOH + Sequence sequence; + }; +}; // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSoundFont.cpp b/soh/soh/resource/type/AudioSoundFont.cpp index 12218cb64..a30df1567 100644 --- a/soh/soh/resource/type/AudioSoundFont.cpp +++ b/soh/soh/resource/type/AudioSoundFont.cpp @@ -1,11 +1,28 @@ #include "AudioSoundFont.h" namespace SOH { -SoundFont* AudioSoundFont::GetPointer() { - return &soundFont; -} -size_t AudioSoundFont::GetPointerSize() { - return sizeof(SoundFont); -} -} // 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; + } + + size_t AudioSoundFont::GetPointerSize() { + return sizeof(SoundFont); + } +} // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSoundFont.h b/soh/soh/resource/type/AudioSoundFont.h index a7e329519..048b1ed29 100644 --- a/soh/soh/resource/type/AudioSoundFont.h +++ b/soh/soh/resource/type/AudioSoundFont.h @@ -8,78 +8,74 @@ namespace SOH { -typedef struct { - /* 0x0 */ s16 delay; - /* 0x2 */ s16 arg; -} AdsrEnvelope; // size = 0x4 + typedef struct { + /* 0x0 */ s16 delay; + /* 0x2 */ s16 arg; + } AdsrEnvelope; // size = 0x4 -typedef struct { - /* 0x00 */ Sample* sample; - /* 0x04 */ union { - u32 tuningAsU32; - f32 tuning; // frequency scale factor + typedef struct { + /* 0x00 */ Sample* sample; + /* 0x04 */ union { + u32 tuningAsU32; + f32 tuning; // frequency scale factor + }; + } SoundFontSound; // size = 0x8 + + typedef struct { + /* 0x00 */ u8 loaded; + /* 0x01 */ u8 normalRangeLo; + /* 0x02 */ u8 normalRangeHi; + /* 0x03 */ u8 releaseRate; + /* 0x04 */ AdsrEnvelope* envelope; + /* 0x08 */ SoundFontSound lowNotesSound; + /* 0x10 */ SoundFontSound normalNotesSound; + /* 0x18 */ SoundFontSound highNotesSound; + } Instrument; // size = 0x20 + + typedef struct { + /* 0x00 */ u8 releaseRate; + /* 0x01 */ u8 pan; + /* 0x02 */ u8 loaded; + /* 0x04 */ SoundFontSound sound; + /* 0x14 */ AdsrEnvelope* envelope; + } Drum; // size = 0x14 + + typedef struct { + /* 0x00 */ u8 numInstruments; + /* 0x01 */ u8 numDrums; + /* 0x02 */ u8 sampleBankId1; + /* 0x03 */ u8 sampleBankId2; + /* 0x04 */ u16 numSfx; + /* 0x08 */ Instrument** instruments; + /* 0x0C */ Drum** drums; + /* 0x10 */ SoundFontSound* soundEffects; + s32 fntIndex; + } SoundFont; // size = 0x14 + + class AudioSoundFont : public Ship::Resource { + public: + using Resource::Resource; + + AudioSoundFont() : Resource(std::shared_ptr()) { + } + ~AudioSoundFont(); + + SoundFont* GetPointer(); + size_t GetPointerSize(); + + int8_t medium; + int8_t cachePolicy; + uint16_t data1; + uint16_t data2; + uint16_t data3; + + std::vector drumAddresses; + std::vector> drumEnvelopeArrays; + + std::vector instrumentAddresses; + + std::vector soundEffects; + + SoundFont soundFont; }; -} SoundFontSound; // size = 0x8 - -typedef struct { - /* 0x00 */ u8 loaded; - /* 0x01 */ u8 normalRangeLo; - /* 0x02 */ u8 normalRangeHi; - /* 0x03 */ u8 releaseRate; - /* 0x04 */ AdsrEnvelope* envelope; - /* 0x08 */ SoundFontSound lowNotesSound; - /* 0x10 */ SoundFontSound normalNotesSound; - /* 0x18 */ SoundFontSound highNotesSound; -} Instrument; // size = 0x20 - -typedef struct { - /* 0x00 */ u8 releaseRate; - /* 0x01 */ u8 pan; - /* 0x02 */ u8 loaded; - /* 0x04 */ SoundFontSound sound; - /* 0x14 */ AdsrEnvelope* envelope; -} Drum; // size = 0x14 - -typedef struct { - /* 0x00 */ u8 numInstruments; - /* 0x01 */ u8 numDrums; - /* 0x02 */ u8 sampleBankId1; - /* 0x03 */ u8 sampleBankId2; - /* 0x04 */ u16 numSfx; - /* 0x08 */ Instrument** instruments; - /* 0x0C */ Drum** drums; - /* 0x10 */ SoundFontSound* soundEffects; - s32 fntIndex; -} SoundFont; // size = 0x14 - -class AudioSoundFont : public Ship::Resource { - public: - using Resource::Resource; - - AudioSoundFont() : Resource(std::shared_ptr()) { - } - - SoundFont* GetPointer(); - size_t GetPointerSize(); - - int8_t medium; - int8_t cachePolicy; - uint16_t data1; - uint16_t data2; - uint16_t data3; - - std::vector drums; - std::vector drumAddresses; - std::vector drumEnvelopeCounts; - std::vector> drumEnvelopeArrays; - - std::vector instruments; - std::vector instrumentAddresses; - std::vector instrumentEnvelopeCounts; - std::vector> instrumentEnvelopeArrays; - - std::vector soundEffects; - - SoundFont soundFont; -}; }; // namespace SOH diff --git a/soh/src/code/audio_heap.c b/soh/src/code/audio_heap.c index e6d622c43..398946fa9 100644 --- a/soh/src/code/audio_heap.c +++ b/soh/src/code/audio_heap.c @@ -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; diff --git a/soh/src/code/audio_load.c b/soh/src/code/audio_load.c index 746a09568..5ceb474fa 100644 --- a/soh/src/code/audio_load.c +++ b/soh/src/code/audio_load.c @@ -8,6 +8,10 @@ #include "soh/Enhancements/audio/AudioCollection.h" #include "soh/Enhancements/audio/AudioEditor.h" #include "soh/ResourceManagerHelpers.h" +#include +#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; diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c index 122823fde..a43a08380 100644 --- a/soh/src/code/audio_playback.c +++ b/soh/src/code/audio_playback.c @@ -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]; } diff --git a/soh/src/code/audio_seqplayer.c b/soh/src/code/audio_seqplayer.c index 871f9a376..b99d29e1e 100644 --- a/soh/src/code/audio_seqplayer.c +++ b/soh/src/code/audio_seqplayer.c @@ -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; } diff --git a/soh/src/code/audio_synthesis.c b/soh/src/code/audio_synthesis.c index cd25386c6..a9f6b2f69 100644 --- a/soh/src/code/audio_synthesis.c +++ b/soh/src/code/audio_synthesis.c @@ -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,28 @@ 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; + 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); + } - flags = A_CONTINUE; - skipBytes = 0; - nSamplesProcessed = samplesLenAdjusted; - s5 = samplesLenAdjusted; goto skip; case CODEC_REVERB: break; @@ -886,6 +900,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 +908,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; } diff --git a/soh/src/code/code_800E4FE0.c b/soh/src/code/code_800E4FE0.c index a608b5234..440fb05a6 100644 --- a/soh/src/code/code_800E4FE0.c +++ b/soh/src/code/code_800E4FE0.c @@ -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; }