Bring over changes from 2ship

This commit is contained in:
louist103 2025-04-26 19:19:42 -04:00
parent 8db97a45bd
commit 931c80f5e1
33 changed files with 2158 additions and 427 deletions

View file

@ -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 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

View file

@ -1 +1 @@
libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal
libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal libogg +universal libopus +universal opusfile +universal libvorbis +universal

61
CMake/FindOgg.cmake Normal file
View file

@ -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)

44
CMake/FindOpus.cmake Normal file
View file

@ -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)

55
CMake/FindOpusFile.cmake Normal file
View file

@ -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()

210
CMake/FindVorbis.cmake Normal file
View file

@ -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)

View file

@ -82,8 +82,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()
@ -153,6 +152,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

View file

@ -278,6 +278,14 @@ endif()
# Find/download Boost
################################################################################
include(FetchContent)
FetchContent_Declare(
dr_libs
GIT_REPOSITORY https://github.com/mackron/dr_libs.git
GIT_TAG da35f9d6c7374a95353fd1df1d394d44ab66cf01
)
FetchContent_MakeAvailable(dr_libs)
FetchContent_Declare(
Boost
URL https://archives.boost.io/release/1.81.0/source/boost_1_81_0.tar.gz
@ -350,6 +358,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE assets
${SDL2-NET-INCLUDE}
${BOOST-INCLUDE}
${CMAKE_CURRENT_SOURCE_DIR}/assets/
${dr_libs_SOURCE_DIR}
.
)
@ -589,6 +598,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
-Wno-missing-braces
-Wno-int-conversion
-Wno-implicit-int
# -fsanitize=address
$<$<COMPILE_LANGUAGE:C>:
-Werror-implicit-function-declaration
-Wno-incompatible-pointer-types
@ -601,6 +611,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_link_options(${PROJECT_NAME} PRIVATE
-pthread
#-fsanitize=address
-Wl,-export-dynamic
)
endif()
@ -636,6 +647,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;"
@ -649,6 +669,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
@ -689,10 +715,21 @@ else()
find_package(SDL2)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(Threads REQUIRED)
find_package(Ogg REQUIRED)
find_package(Vorbis REQUIRED)
find_package(Opus REQUIRED)
find_package(OpusFile REQUIRED)
set(ADDITIONAL_LIBRARY_DEPENDENCIES
"libultraship;"
"ZAPDLib;"
SDL2::SDL2
"Ogg::ogg"
"Vorbis::vorbis"
"Vorbis::vorbisenc"
"Vorbis::vorbisfile"
"Opus::opus"
"Opusfile::Opusfile"
"$<$<BOOL:${BUILD_REMOTE_CONTROL}>:SDL2_net::SDL2_net>"
${CMAKE_DL_LIBS}
Threads::Threads

View file

@ -1,6 +1,10 @@
#ifndef Z64_AUDIO_H
#define Z64_AUDIO_H
#ifdef __cplusplus
extern "C" {
#endif
#include <endianness.h>
#define MK_CMD(b0,b1,b2,b3) ((((b0) & 0xFF) << 0x18) | (((b1) & 0xFF) << 0x10) | (((b2) & 0xFF) << 0x8) | (((b3) & 0xFF) << 0))
@ -24,8 +28,8 @@
//#define MAX_SEQUENCES 0x800
extern size_t sequenceMapSize;
extern char* fontMap[256];
extern size_t fontMapSize;
extern char** fontMap;
#define MAX_AUTHENTIC_SEQID 110
@ -54,7 +58,8 @@ typedef enum {
/* 2 */ CODEC_S16_INMEMORY,
/* 3 */ CODEC_SMALL_ADPCM,
/* 4 */ CODEC_REVERB,
/* 5 */ CODEC_S16
/* 5 */ CODEC_S16,
/* 6 */ CODEC_OPUS,
} SampleCodec;
typedef enum {
@ -117,13 +122,14 @@ typedef struct {
/* 0x2 */ s16 arg;
} AdsrEnvelope; // size = 0x4
typedef struct {
/* 0x00 */ uintptr_t start;
/* 0x04 */ uintptr_t end;
/* 0x08 */ u32 count;
/* 0x0C */ char unk_0C[0x4];
/* 0x10 */ s16 state[16]; // only exists if count != 0. 8-byte aligned
} AdpcmLoop; // size = 0x30 (or 0x10)
typedef struct AdpcmLoop {
/* 0x00 */ u32 start;
/* 0x04 */ u32 loopEnd; // numSamples position into the sample where the loop ends
/* 0x08 */ u32 count; // The number of times the loop is played before the sound completes. Setting count to -1
// indicates that the loop should play indefinitely.
/* 0x0C */ u32 sampleEnd; // total number of s16-samples in the sample audio clip
/* 0x10 */ s16 predictorState[16]; // only exists if count != 0. 8-byte aligned
} AdpcmLoop; // size = 0x30 (or 0x10)
typedef struct {
/* 0x00 */ s32 order;
@ -131,24 +137,23 @@ typedef struct {
/* 0x08 */ s16* book; // size 8 * order * npredictors. 8-byte aligned
} AdpcmBook; // size >= 0x8
typedef struct
{
typedef struct SoundFontSample {
union {
struct {
/* 0x00 */ u32 codec : 4;
/* 0x00 */ u32 medium : 2;
/* 0x00 */ u32 unk_bit26 : 1;
/* 0x00 */ u32 unk_bit25 : 1; // this has been named isRelocated in zret
/* 0x01 */ u32 size : 24;
///* 0x0 */ u32 unk_0 : 1;
/* 0x0 */ u32 codec : 4; // The state of compression or decompression, See `SampleCodec`
/* 0x0 */ u32 medium : 2; // Medium where sample is currently stored. See `SampleMedium`
/* 0x0 */ u32 unk_bit26 : 1;
/* 0x0 */ u32 isRelocated : 1; // Has the sample header been relocated (offsets to pointers)
};
u32 asU32;
};
/* 0x04 */ u8* sampleAddr;
/* 0x08 */ AdpcmLoop* loop;
/* 0x0C */ AdpcmBook* book;
u32 sampleRateMagicValue; // For wav samples only...
s32 sampleRate; // For wav samples only...
/* 0x1 */ u32 size; // Size of the sample
u32 fileSize;
/* 0x4 */ u8* sampleAddr; // Raw sample data. Offset from the start of the sample bank or absolute address to either rom or ram
/* 0x8 */ AdpcmLoop* loop; // Adpcm loop parameters used by the sample. Offset from the start of the sound font / pointer to ram
/* 0xC */ AdpcmBook* book; // Adpcm book parameters used by the sample. Offset from the start of the sound font / pointer to ram
} SoundFontSample; // size = 0x10
typedef struct {
@ -465,6 +470,8 @@ typedef struct {
/* 0x00F0 */ s16 dummyResampleState[0x10];
} NoteSynthesisBuffers; // size = 0x110
struct OggOpusFile;
typedef struct {
/* 0x00 */ u8 restart;
/* 0x01 */ u8 sampleDmaIndex;
@ -483,6 +490,7 @@ typedef struct {
/* 0x1A */ u8 unk_1A;
/* 0x1C */ u16 unk_1C;
/* 0x1E */ u16 unk_1E;
struct OggOpusFile* opusFile; // Only for streamed opus audio
} NoteSynthesisState; // size = 0x20
typedef struct {
@ -917,7 +925,7 @@ typedef struct {
/* 0x3420 */ AudioPoolSplit3 persistentCommonPoolSplit;
/* 0x342C */ AudioPoolSplit3 temporaryCommonPoolSplit;
/* 0x3438 */ u8 sampleFontLoadStatus[0x30];
/* 0x3468 */ u8 fontLoadStatus[0x30];
/* 0x3468 */ u8* fontLoadStatus;
/* 0x3498 */ u8* seqLoadStatus;
/* 0x3518 */ volatile u8 resetStatus;
/* 0x3519 */ u8 audioResetSpecIdToLoad;
@ -1119,10 +1127,6 @@ typedef struct {
uint8_t fonts[16];
} SequenceData;
#ifdef __cplusplus
extern "C" {
#endif
void Audio_SetGameVolume(int player_id, f32 volume);
float Audio_GetGameVolume(int player_id);

View file

@ -403,12 +403,19 @@ OTRGlobals::OTRGlobals() {
static_cast<uint32_t>(SOH::ResourceType::SOH_Text), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSampleV2>(), RESOURCE_FORMAT_BINARY,
"AudioSample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSampleV0>(), RESOURCE_FORMAT_XML,
"Sample", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSample), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSoundFontV2>(),
RESOURCE_FORMAT_BINARY, "AudioSoundFont",
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLSoundFontV0>(), RESOURCE_FORMAT_XML,
"SoundFont", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSoundFont), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryAudioSequenceV2>(),
RESOURCE_FORMAT_BINARY, "AudioSequence",
static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 2);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryXMLAudioSequenceV0>(), RESOURCE_FORMAT_XML,
"Sequence", static_cast<uint32_t>(SOH::ResourceType::SOH_AudioSequence), 0);
loader->RegisterResourceFactory(std::make_shared<SOH::ResourceFactoryBinaryBackgroundV0>(), RESOURCE_FORMAT_BINARY,
"Background", static_cast<uint32_t>(SOH::ResourceType::SOH_Background), 0);
@ -601,6 +608,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
{
@ -611,6 +624,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() {
@ -1070,6 +1096,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] != '.';

View file

@ -166,6 +166,7 @@ void CheckTracker_OnMessageClose();
GetItemID RetrieveGetItemIDFromItemID(ItemID itemID);
RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID);
void Messagebox_ShowErrorBox(char* title, char* body);
#endif
#ifdef __cplusplus

View file

@ -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);
}

View file

@ -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();

View file

@ -1,9 +1,11 @@
//! This file is always optimized by a rule in the CMakeList. This is done because the SIMD functions are very large
//! when unoptimized and clang does not allow optimizing a single function.
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "mixer.h"
#ifndef __clang__
#pragma GCC optimize("unroll-loops")
#endif
@ -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 <opus/opus.h>
#include <opusfile.h>
void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OggOpusFile** decState, int32_t pos,
uint32_t size) {
int readSamples = 0;
if (*decState == NULL) {
*decState = op_open_memory(source_addr, size, NULL);
}
op_pcm_seek(*decState, pos);
int ret = op_read(*decState, BUF_S16(dest_addr), nbytes / 2, NULL);
if (ret < 0) {
return;
}
readSamples += ret;
while (readSamples < nbytes / 2) {
ret = op_read(*decState, BUF_S16(dest_addr + readSamples * 2), (nbytes - readSamples * 2) / 2, NULL);
if (ret == 0)
break;
readSamples += ret;
}
}
void aOPUSFree(struct OggOpusFile* opusFile) {
op_free(opusFile);
}
void aSaveBufferImpl(uint16_t source_addr, int16_t* dest_addr, uint16_t nbytes) {
memcpy(dest_addr, BUF_S16(source_addr), ROUND_DOWN_16(nbytes));
}
@ -296,7 +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 <immintrin.h>
static const ALIGN_ASSET(16) int16_t x7fff[8] = {
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
};
static const ALIGN_ASSET(16) int32_t x4000[4] = {
0x4000,
0x4000,
0x4000,
0x4000,
};
static void aMixImplSSE2(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
int16_t* in = BUF_S16(in_addr);
int16_t* out = BUF_S16(out_addr);
int i;
int32_t sample;
if (gain == -0x8000) {
while (nbytes > 0) {
for (unsigned int i = 0; i < 2; i++) {
__m128i outVec = _mm_loadu_si128((__m128i*)out);
__m128i inVec = _mm_loadu_si128((__m128i*)in);
__m128i subsVec = _mm_subs_epi16(outVec, inVec);
_mm_storeu_si128(out, subsVec);
nbytes -= 8 * sizeof(int16_t);
in += 8;
out += 8;
}
}
}
// Load constants into vectors from aligned memory.
__m128i x7fffVec = _mm_load_si128((__m128i*)x7fff);
__m128i x4000Vec = _mm_load_si128((__m128i*)x4000);
__m128i gainVec = _mm_set1_epi16(gain);
while (nbytes > 0) {
for (unsigned int i = 0; i < 2; i++) {
// Load input and output data into vectors
__m128i outVec = _mm_loadu_si128((__m128i*)out);
__m128i inVec = _mm_loadu_si128((__m128i*)in);
// Multiply `out` by `0x7FFF` producing 32 bit results, and store the upper and lower bits in each vector.
// Equivalent to `out[0..8] * 0x7FFF`
__m128i outx7fffLoVec = _mm_mullo_epi16(outVec, x7fffVec);
__m128i outx7fffHiVec = _mm_mulhi_epi16(outVec, x7fffVec);
// Same as above but for in and gain. Equivalent to `in[0..8] * gain`
__m128i inxGainLoVec = _mm_mullo_epi16(inVec, gainVec);
__m128i inxGainHiVec = _mm_mulhi_epi16(inVec, gainVec);
// Interleave the lo and hi bits into one 32 bit value for each vector element.
// So now we have 4 full elements in each vector instead of 8 half elements.
outx7fffLoVec = _mm_unpacklo_epi16(outx7fffLoVec, outx7fffHiVec);
outx7fffHiVec = _mm_unpackhi_epi16(outx7fffLoVec, outx7fffHiVec);
inxGainLoVec = _mm_unpacklo_epi16(inxGainLoVec, inxGainHiVec);
inxGainHiVec = _mm_unpackhi_epi16(inxGainLoVec, inxGainHiVec);
// Now we have 4 32 bit elements. Continue the calculaton per the reference implementation.
// We already did out + 0x7fff and in * gain.
// *out * 0x7fff + *in++ * gain is the final result of these two calculations.
__m128i addLoVec = _mm_add_epi32(outx7fffLoVec, inxGainLoVec);
__m128i addHiVec = _mm_add_epi32(outx7fffHiVec, inxGainHiVec);
// Add 0x4000 to each element
addLoVec = _mm_add_epi32(addLoVec, x4000Vec);
addHiVec = _mm_add_epi32(addHiVec, x4000Vec);
// Shift each element over by 15
__m128i shiftedLoVec = _mm_srai_epi32(addLoVec, 15);
__m128i shiftedHiVec = _mm_srai_epi32(addHiVec, 15);
// Convert each 32 bit element to 16 bit with saturation (clamp) and store in `outVec`
outVec = _mm_packs_epi32(shiftedLoVec, shiftedHiVec);
// Write the final vector back to memory
// The final calculation is ((out[0..8] * 0x7fff + in[0..8] * gain) + 0x4000) >> 15;
_mm_storeu_si128((__m128i*)out, outVec);
in += 8;
out += 8;
nbytes -= 8 * sizeof(int16_t);
}
}
}
#endif
#if defined(__ARM_NEON)
#include <arm_neon.h>
static const int32_t x4000Arr[4] = { 0x4000, 0x4000, 0x4000, 0x4000 };
void aMixImplNEON(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
int16_t* in = BUF_S16(in_addr);
int16_t* out = BUF_S16(out_addr);
int i;
int32_t sample;
if (gain == -0x8000) {
while (nbytes > 0) {
for (unsigned int i = 0; i < 2; i++) {
int16x8_t outVec = vld1q_s16(out);
int16x8_t inVec = vld1q_s16(in);
int16x8_t subVec = vqsubq_s16(outVec, inVec);
vst1q_s16(out, subVec);
nbytes -= 8 * sizeof(int16_t);
out += 8;
in += 8;
}
}
}
int16x8_t gainVec = vdupq_n_s16(gain);
int32x4_t x4000Vec = vld1q_s32(x4000Arr);
while (nbytes > 0) {
for (unsigned int i = 0; i < 2; i++) {
// for (i = 0; i < 16; i++) {
int16x8_t outVec = vld1q_s16(out);
int16x8_t inVec = vld1q_s16(in);
int16x4_t outLoVec = vget_low_s16(outVec);
int16x8_t outLoVec2 = vcombine_s16(outLoVec, outLoVec);
int16x4_t inLoVec = vget_low_s16(inVec);
int16x8_t inLoVec2 = vcombine_s16(inLoVec, inLoVec);
int32x4_t outX7fffHiVec = vmull_high_n_s16(outVec, 0x7FFF);
int32x4_t outX7fffLoVec = vmull_high_n_s16(outLoVec2, 0x7FFF);
int32x4_t inGainLoVec = vmull_high_s16(inLoVec2, gainVec);
int32x4_t inGainHiVec = vmull_high_s16(inVec, gainVec);
int32x4_t addVecLo = vaddq_s32(outX7fffLoVec, inGainLoVec);
int32x4_t addVecHi = vaddq_s32(outX7fffHiVec, inGainHiVec);
addVecHi = vaddq_s32(addVecHi, x4000Vec);
addVecLo = vaddq_s32(addVecLo, x4000Vec);
int32x4_t shiftVecHi = vshrq_n_s32(addVecHi, 15);
int32x4_t shiftVecLo = vshrq_n_s32(addVecLo, 15);
int16x4_t shiftedNarrowHiVec = vqmovn_s32(shiftVecHi);
int16x4_t shiftedNarrowLoVec = vqmovn_s32(shiftVecLo);
vst1_s16(out, shiftedNarrowLoVec);
out += 4;
vst1_s16(out, shiftedNarrowHiVec);
// int16x8_t finalVec = vcombine_s16(shiftedNarrowLoVec, shiftedNarrowHiVec);
// vst1q_s16(out, finalVec);
out += 4;
in += 8;
nbytes -= 8 * sizeof(int16_t);
}
}
}
#endif
#if 0
static const ALIGN_ASSET(32) int16_t x7fff[16] = { 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,};
static const ALIGN_ASSET(32) int32_t x4000[8] = { 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000};
#pragma GCC target("avx2")
// AVX2 version of the SSE2 implementation above. AVX2 wasn't released until 2014 and I don't have a good way of checking for it at compile time.
void aMixImpl256(uint16_t count, int16_t gain, uint16_t in_addr, uint16_t out_addr) {
int nbytes = ROUND_UP_32(ROUND_DOWN_16(count << 4));
int16_t* in = BUF_S16(in_addr);
int16_t* out = BUF_S16(out_addr);
int i;
int32_t sample;
if (gain == -0x8000) {
while (nbytes > 0) {
__m256i outVec =_mm256_loadu_si256((__m256*)out);
__m256i inVec =_mm256_loadu_si256((__m256i*)in);
__m256i subsVec =_mm256_subs_epi16(outVec, inVec);
_mm256_storeu_si256(out, subsVec);
in += 16;
out += 16;
nbytes -= 16 * sizeof(int16_t);
}
}
// Load constants into vectors from aligned memory.
__m256i x7fffVec = _mm256_load_si256((__m256i*)x7fff);
__m256i x4000Vec = _mm256_load_si256((__m256i*)x4000);
__m256i gainVec = _mm256_set1_epi16(gain);
while (nbytes > 0) {
// Load input and output data into vectors
__m256i outVec = _mm256_loadu_si256((__m256i*)out);
__m256i inVec = _mm256_loadu_si256((__m256i*)in);
// Multiply `out` by `0x7FFF` producing 32 bit results, and store the upper and lower bits in each vector.
// Equivalent to `out[0..16] * 0x7FFF`
__m256i outx7fffLoVec = _mm256_mullo_epi16(outVec, x7fffVec);
__m256i outx7fffHiVec = _mm256_mulhi_epi16(outVec, x7fffVec);
// Same as above but for in and gain. Equivalent to `in[0..16] * gain`
__m256i inxGainLoVec = _mm256_mullo_epi16(inVec, gainVec);
__m256i inxGainHiVec = _mm256_mulhi_epi16(inVec, gainVec);
// Interleave the lo and hi bits into one 32 bit value for each vector element.
// So now we have 8 full elements in each vector instead of 16 half elements.
outx7fffLoVec = _mm256_unpacklo_epi16(outx7fffLoVec, outx7fffHiVec);
outx7fffHiVec = _mm256_unpackhi_epi16(outx7fffLoVec, outx7fffHiVec);
inxGainLoVec = _mm256_unpacklo_epi16(inxGainLoVec, inxGainHiVec);
inxGainHiVec = _mm256_unpackhi_epi16(inxGainLoVec, inxGainHiVec);
// Now we have 8 32 bit elements. Continue the calculaton per the reference implementation.
// We already did out + 0x7fff and in * gain.
// *out * 0x7fff + *in++ * gain is the final result of these two calculations.
__m256i addLoVec = _mm256_add_epi32(outx7fffLoVec, inxGainLoVec);
__m256i addHiVec = _mm256_add_epi32(outx7fffHiVec, inxGainHiVec);
// Add 0x4000 to each element
addLoVec = _mm256_add_epi32(addLoVec, x4000Vec);
addHiVec = _mm256_add_epi32(addHiVec, x4000Vec);
// Shift each element over by 15
__m256i shiftedLoVec = _mm256_srai_epi32(addLoVec, 15);
__m256i shiftedHiVec = _mm256_srai_epi32(addHiVec, 15);
// Convert each 32 bit element to 16 bit with saturation (clamp) and store in `outVec`
outVec = _mm256_packs_epi32(shiftedLoVec, shiftedHiVec);
// Write the final vector back to memory
// The final calculation is ((out[0..16] * 0x7fff + in[0..16] * gain) + 0x4000) >> 15;
_mm256_storeu_si256((__m256i*)out, outVec);
in += 16;
out += 16;
nbytes -= 16 * sizeof(int16_t);
}
}
#endif

View file

@ -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)

View file

@ -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 <dr_wav.h>
#define DR_MP3_IMPLEMENTATION
#include <dr_mp3.h>
#define DR_FLAC_IMPLEMENTATION
#include <dr_flac.h>
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include "vorbis/vorbisfile.h"
struct OggFileData {
void* data;
size_t pos;
size_t size;
};
typedef enum class OggType {
None = -1,
Vorbis,
Opus,
} OggType;
static size_t VorbisReadCallback(void* out, size_t size, size_t elems, void* src) {
OggFileData* data = static_cast<OggFileData*>(src);
size_t toRead = size * elems;
if (toRead > data->size - data->pos) {
toRead = data->size - data->pos;
}
memcpy(out, static_cast<uint8_t*>(data->data) + data->pos, toRead);
data->pos += toRead;
return toRead / size;
}
static int VorbisSeekCallback(void* src, ogg_int64_t pos, int whence) {
OggFileData* data = static_cast<OggFileData*>(src);
size_t newPos;
switch (whence) {
case SEEK_SET:
newPos = pos;
break;
case SEEK_CUR:
newPos = data->pos + pos;
break;
case SEEK_END:
newPos = data->size + pos;
break;
default:
return -1;
}
if (newPos > data->size) {
return -1;
}
data->pos = newPos;
return 0;
}
static int VorbisCloseCallback([[maybe_unused]] void* src) {
return 0;
}
static long VorbisTellCallback(void* src) {
OggFileData* data = static_cast<OggFileData*>(src);
return data->pos;
}
static const ov_callbacks vorbisCallbacks = {
VorbisReadCallback,
VorbisSeekCallback,
VorbisCloseCallback,
VorbisTellCallback,
};
static OggType GetOggType(OggFileData* data) {
ogg_sync_state oy;
ogg_stream_state os;
ogg_page og;
ogg_packet op;
OggType type;
// The first page as the header information, containing, among other things, what kind of data this ogg holds.
ogg_sync_init(&oy);
char* buffer = ogg_sync_buffer(&oy, 4096);
VorbisReadCallback(buffer, 4096, 1, data);
ogg_sync_wrote(&oy, 4096);
ogg_sync_pageout(&oy, &og);
ogg_stream_init(&os, ogg_page_serialno(&og));
ogg_stream_pagein(&os, &og);
ogg_stream_packetout(&os, &op);
// Can't use strmp because op.packet isn't a null terminated string.
if (memcmp((char*)op.packet, "\x01vorbis", 7) == 0) {
type = OggType::Vorbis;
} else if (memcmp((char*)op.packet, "OpusHead", 8) == 0) {
type = OggType::Opus;
} else {
type = OggType::None;
}
ogg_stream_clear(&os);
ogg_sync_clear(&oy);
return type;
}
static void Mp3DecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile) {
drmp3 mp3;
drwav_uint64 numFrames;
drmp3_bool32 ret =
drmp3_init_memory(&mp3, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
numFrames = drmp3_get_pcm_frame_count(&mp3);
drwav_uint64 channels = mp3.channels;
drwav_uint64 sampleRate = mp3.sampleRate;
audioSample->sample.sampleAddr = new uint8_t[numFrames * channels * 2];
drmp3_read_pcm_frames_s16(&mp3, numFrames, (int16_t*)audioSample->sample.sampleAddr);
}
static void FlacDecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile) {
drflac* flac = drflac_open_memory(sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
drflac_uint64 numFrames = flac->totalPCMFrameCount;
audioSample->sample.sampleAddr = new uint8_t[numFrames * flac->channels * 2];
drflac_read_pcm_frames_s16(flac, numFrames, (int16_t*)audioSample->sample.sampleAddr);
drflac_close(flac);
}
static void OggDecoderWorker(std::shared_ptr<SOH::AudioSample> audioSample, std::shared_ptr<Ship::File> sampleFile,
std::shared_ptr<Ship::ResourceInitData> initData) {
OggVorbis_File vf;
char dataBuff[4096];
long read = 0;
size_t pos = 0;
OggFileData fileData = {
.data = sampleFile->Buffer.get()->data(),
.pos = 0,
.size = sampleFile->Buffer.get()->size(),
};
switch (GetOggType(&fileData)) {
case OggType::Vorbis: {
// Getting the type advanced the position. We are going to use a different library to decode the file which
// assumes the file starts at 0
fileData.pos = 0;
int ret = ov_open_callbacks(&fileData, &vf, nullptr, 0, vorbisCallbacks);
vorbis_info* vi = ov_info(&vf, -1);
uint64_t numFrames = ov_pcm_total(&vf, -1);
uint64_t sampleRate = vi->rate;
uint64_t numChannels = vi->channels;
int bitStream = 0;
size_t toRead = numFrames * numChannels * 2;
audioSample->sample.sampleAddr = new uint8_t[toRead];
do {
read = ov_read(&vf, dataBuff, 4096, 0, 2, 1, &bitStream);
memcpy(audioSample->sample.sampleAddr + pos, dataBuff, read);
pos += read;
} while (read != 0);
ov_clear(&vf);
break;
}
case OggType::Opus: {
// OPUS encoded data is decoded by the audio driver.
audioSample->sample.codec = CODEC_OPUS;
audioSample->sample.sampleAddr = new uint8_t[sampleFile->Buffer.get()->size()];
memcpy(audioSample->sample.sampleAddr, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size());
break;
}
case OggType::None: {
char buff[2048];
snprintf(buff, 2048, "Ogg file %s is not Vorbis or OPUS", initData->Path.c_str());
throw std::runtime_error(buff);
break;
}
}
}
namespace SOH {
std::shared_ptr<Ship::IResource>
@ -16,108 +201,155 @@ ResourceFactoryBinaryAudioSampleV2::ReadResource(std::shared_ptr<Ship::File> fil
audioSample->sample.codec = reader->ReadUByte();
audioSample->sample.medium = reader->ReadUByte();
audioSample->sample.unk_bit26 = reader->ReadUByte();
audioSample->sample.unk_bit25 = reader->ReadUByte();
audioSample->sample.isRelocated = reader->ReadUByte();
audioSample->sample.size = reader->ReadUInt32();
audioSample->audioSampleData.reserve(audioSample->sample.size);
audioSample->sample.sampleAddr = new uint8_t[audioSample->sample.size];
for (uint32_t i = 0; i < audioSample->sample.size; i++) {
audioSample->audioSampleData.push_back(reader->ReadUByte());
audioSample->sample.sampleAddr[i] = reader->ReadUByte();
}
audioSample->sample.sampleAddr = audioSample->audioSampleData.data();
audioSample->loop.start = reader->ReadUInt32();
audioSample->loop.end = reader->ReadUInt32();
audioSample->loop.count = reader->ReadUInt32();
audioSample->loopStateCount = reader->ReadUInt32();
// This always seems to be 16. Can it be removed in V3?
uint32_t loopStateCount = reader->ReadUInt32();
for (int i = 0; i < 16; i++) {
audioSample->loop.state[i] = 0;
}
for (uint32_t i = 0; i < audioSample->loopStateCount; i++) {
for (uint32_t i = 0; i < loopStateCount; i++) {
audioSample->loop.state[i] = reader->ReadInt16();
}
audioSample->sample.loop = &audioSample->loop;
audioSample->book.order = reader->ReadInt32();
audioSample->book.npredictors = reader->ReadInt32();
audioSample->bookDataCount = reader->ReadUInt32();
uint32_t bookDataCount = reader->ReadUInt32();
audioSample->bookData.reserve(audioSample->bookDataCount);
for (uint32_t i = 0; i < audioSample->bookDataCount; i++) {
audioSample->bookData.push_back(reader->ReadInt16());
audioSample->book.book = new int16_t[bookDataCount];
for (uint32_t i = 0; i < bookDataCount; i++) {
audioSample->book.book[i] = reader->ReadInt16();
}
audioSample->book.book = audioSample->bookData.data();
audioSample->sample.book = &audioSample->book;
return audioSample;
}
} // namespace SOH
/*
in ResourceMgr_LoadAudioSample we used to have
--------------
if (cachedCustomSFs.find(path) != cachedCustomSFs.end())
return cachedCustomSFs[path];
SoundFontSample* cSample = ReadCustomSample(path);
if (cSample != nullptr)
return cSample;
--------------
before the rest of the standard sample reading, this is the ReadCustomSample code we used to have
extern "C" SoundFontSample* ReadCustomSample(const char* path) {
if (!ExtensionCache.contains(path))
std::shared_ptr<Ship::IResource>
ResourceFactoryXMLAudioSampleV0::ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) {
if (!FileHasValidFormatAndReader(file, initData)) {
return nullptr;
ExtensionEntry entry = ExtensionCache[path];
auto sampleRaw = Ship::Context::GetInstance()->GetResourceManager()->LoadFile(entry.path);
uint32_t* strem = (uint32_t*)sampleRaw->Buffer.get();
uint8_t* strem2 = (uint8_t*)strem;
SoundFontSample* sampleC = new SoundFontSample;
if (entry.ext == "wav") {
drwav_uint32 channels;
drwav_uint32 sampleRate;
drwav_uint64 totalPcm;
drmp3_int16* pcmData =
drwav_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &channels, &sampleRate, &totalPcm,
NULL); sampleC->size = totalPcm; sampleC->sampleAddr = (uint8_t*)pcmData; sampleC->codec = CODEC_S16;
sampleC->loop = new AdpcmLoop;
sampleC->loop->start = 0;
sampleC->loop->end = sampleC->size - 1;
sampleC->loop->count = 0;
sampleC->sampleRateMagicValue = 'RIFF';
sampleC->sampleRate = sampleRate;
cachedCustomSFs[path] = sampleC;
return sampleC;
} else if (entry.ext == "mp3") {
drmp3_config mp3Info;
drmp3_uint64 totalPcm;
drmp3_int16* pcmData =
drmp3_open_memory_and_read_pcm_frames_s16(strem2, sampleRaw->BufferSize, &mp3Info, &totalPcm, NULL);
sampleC->size = totalPcm * mp3Info.channels * sizeof(short);
sampleC->sampleAddr = (uint8_t*)pcmData;
sampleC->codec = CODEC_S16;
sampleC->loop = new AdpcmLoop;
sampleC->loop->start = 0;
sampleC->loop->end = sampleC->size;
sampleC->loop->count = 0;
sampleC->sampleRateMagicValue = 'RIFF';
sampleC->sampleRate = mp3Info.sampleRate;
cachedCustomSFs[path] = sampleC;
return sampleC;
}
return nullptr;
auto audioSample = std::make_shared<AudioSample>(initData);
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
const char* customFormatStr = child->Attribute("CustomFormat");
memset(&audioSample->sample, 0, sizeof(audioSample->sample));
audioSample->sample.isRelocated = 0;
audioSample->sample.codec = CodecStrToInt(child->Attribute("Codec"), initData->Path.c_str());
audioSample->sample.medium =
ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium"), initData->Path.c_str());
audioSample->sample.unk_bit26 = child->IntAttribute("bit26");
tinyxml2::XMLElement* loopRoot = child->FirstChildElement("ADPCMLoop");
if (loopRoot != nullptr) {
size_t i = 0;
audioSample->loop.start = loopRoot->UnsignedAttribute("Start");
audioSample->loop.end = loopRoot->UnsignedAttribute("End");
audioSample->loop.count = loopRoot->UnsignedAttribute("Count");
tinyxml2::XMLElement* predictor = loopRoot->FirstChildElement("Predictor");
while (predictor != nullptr) {
audioSample->loop.state[i++] = predictor->IntAttribute("State");
predictor = predictor->NextSiblingElement();
}
}
tinyxml2::XMLElement* bookRoot = child->FirstChildElement("ADPCMBook");
if (bookRoot != nullptr) {
size_t i = 0;
audioSample->book.npredictors = bookRoot->IntAttribute("Npredictors");
audioSample->book.order = bookRoot->IntAttribute("Order");
tinyxml2::XMLElement* book = bookRoot->FirstChildElement("Book");
size_t numBooks = audioSample->book.npredictors * audioSample->book.order * 8;
audioSample->book.book = new int16_t[numBooks];
while (book != nullptr) {
audioSample->book.book[i++] = book->IntAttribute("Page");
book = book->NextSiblingElement();
}
audioSample->sample.book = &audioSample->book;
}
audioSample->sample.loop = &audioSample->loop;
size_t size = child->Int64Attribute("Size");
audioSample->sample.size = size;
const char* path = child->Attribute("Path");
auto sampleFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path);
audioSample->sample.fileSize = sampleFile->Buffer.get()->size();
if (customFormatStr != nullptr) {
// Compressed files can take a really long time to decode (~250ms per).
// This worked when we tested it (09/04/2024) (Works on my machine)
if (strcmp(customFormatStr, "wav") == 0) {
drwav wav;
drwav_uint64 numFrames;
drwav_bool32 ret =
drwav_init_memory(&wav, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr);
drwav_get_length_in_pcm_frames(&wav, &numFrames);
audioSample->tuning = (wav.sampleRate * wav.channels) / 32000.0f;
audioSample->sample.sampleAddr = new uint8_t[numFrames * wav.channels * 2];
drwav_read_pcm_frames_s16(&wav, numFrames, (int16_t*)audioSample->sample.sampleAddr);
return audioSample;
} else if (strcmp(customFormatStr, "mp3") == 0) {
std::thread fileDecoderThread = std::thread(Mp3DecoderWorker, audioSample, sampleFile);
fileDecoderThread.detach();
return audioSample;
} else if (strcmp(customFormatStr, "ogg") == 0) {
std::thread fileDecoderThread = std::thread(OggDecoderWorker, audioSample, sampleFile, initData);
fileDecoderThread.detach();
return audioSample;
} else if (strcmp(customFormatStr, "flac") == 0) {
std::thread fileDecoderThread = std::thread(FlacDecoderWorker, audioSample, sampleFile);
fileDecoderThread.detach();
return audioSample;
}
}
// Not a normal streamed sample. Fallback to the original ADPCM sample to be decoded by the audio engine.
audioSample->sample.sampleAddr = new uint8_t[size];
// Can't use memcpy due to endianness issues.
for (uint32_t i = 0; i < size; i++) {
audioSample->sample.sampleAddr[i] = sampleFile->Buffer.get()->data()[i];
}
return audioSample;
}
*/
uint8_t ResourceFactoryXMLAudioSampleV0::CodecStrToInt(const char* str, const char* file) {
if (strcmp("ADPCM", str) == 0) {
return CODEC_ADPCM;
} else if (strcmp("S8", str) == 0) {
return CODEC_S8;
} else if (strcmp("S16MEM", str) == 0) {
return CODEC_S16_INMEMORY;
} else if (strcmp("ADPCMSMALL", str) == 0) {
return CODEC_SMALL_ADPCM;
} else if (strcmp("REVERB", str) == 0) {
return CODEC_REVERB;
} else if (strcmp("S16", str) == 0) {
return CODEC_S16;
} else {
char buff[2048];
snprintf(buff, 2048,
"Invalid codec in %s. Got %s, expected ADPCM, S8, S16MEM, ADPCMSMALL, REVERB, S16.", file,
str);
throw std::runtime_error(buff);
}
}
} // namespace SOH

View file

@ -2,11 +2,22 @@
#include "Resource.h"
#include "ResourceFactoryBinary.h"
#include "ResourceFactoryXML.h"
namespace SOH {
class ResourceFactoryBinaryAudioSampleV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryBinaryAudioSampleV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryXMLAudioSampleV0 : public Ship::ResourceFactoryXML {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
private:
static uint8_t CodecStrToInt(const char* str, const char* file);
};
} // namespace SOH

View file

@ -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 <type_traits>
namespace SOH {
std::shared_ptr<Ship::IResource>
ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr<Ship::File> file,
@ -13,12 +19,11 @@ ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr<Ship::File> f
auto audioSequence = std::make_shared<AudioSequence>(initData);
auto reader = std::get<std::shared_ptr<Ship::BinaryReader>>(file->Reader);
audioSequence->sequence.seqDataSize = reader->ReadInt32();
audioSequence->sequenceData.reserve(audioSequence->sequence.seqDataSize);
audioSequence->sequence.seqDataSize = reader->ReadUInt32();
audioSequence->sequence.seqData = new char[audioSequence->sequence.seqDataSize];
for (uint32_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<Ship::File> f
return audioSequence;
}
} // namespace SOH
template <typename T> static void WriteInsnOneArg(Ship::BinaryWriter* writer, uint8_t opcode, T arg) {
static_assert(std::is_fundamental<T>::value);
writer->Write(opcode);
writer->Write(arg);
}
template <typename T1, typename T2>
static void WriteInsnTwoArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2) {
static_assert(std::is_fundamental<T1>::value && std::is_fundamental<T2>::value);
writer->Write(opcode);
writer->Write(arg1);
writer->Write(arg2);
}
template <typename T1, typename T2, typename T3>
static void WriteInsnThreeArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2, T3 arg3) {
static_assert(std::is_fundamental<T1>::value && std::is_fundamental<T2>::value);
writer->Write(opcode);
writer->Write(arg1);
writer->Write(arg2);
writer->Write(arg3);
}
static void WriteInsnNoArg(Ship::BinaryWriter* writer, uint8_t opcode) {
writer->Write(opcode);
}
static void WriteLegato(Ship::BinaryWriter* writer) {
WriteInsnNoArg(writer, 0xC4);
}
static void WriteNoLegato(Ship::BinaryWriter* writer) {
WriteInsnNoArg(writer, 0xC5);
}
static void WriteMuteBhv(Ship::BinaryWriter* writer, uint8_t arg) {
WriteInsnOneArg(writer, 0xD3, arg);
}
static void WriteMuteScale(Ship::BinaryWriter* writer, uint8_t arg) {
WriteInsnOneArg(writer, 0xD5, arg);
}
static void WriteInitchan(Ship::BinaryWriter* writer, uint16_t channels) {
WriteInsnOneArg(writer, 0xD7, channels);
}
static void WriteLdchan(Ship::BinaryWriter* writer, uint8_t channel, uint16_t offset) {
WriteInsnOneArg(writer, 0x90 | channel, offset);
}
static void WriteVolSHeader(Ship::BinaryWriter* writer, uint8_t vol) {
WriteInsnOneArg(writer, 0xDB, vol);
}
static void WriteVolCHeader(Ship::BinaryWriter* writer, uint8_t vol) {
WriteInsnOneArg(writer, 0xDF, vol);
}
static void WriteTempo(Ship::BinaryWriter* writer, uint8_t tempo) {
WriteInsnOneArg(writer, 0xDD, tempo);
}
static void WriteJump(Ship::BinaryWriter* writer, uint16_t offset) {
WriteInsnOneArg(writer, 0xFB, offset);
}
static void WriteDisablecan(Ship::BinaryWriter* writer, uint16_t channels) {
WriteInsnOneArg(writer, 0xD6, channels);
}
static void WriteNoshort(Ship::BinaryWriter* writer) {
WriteInsnNoArg(writer, 0xC4);
}
static void WriteLdlayer(Ship::BinaryWriter* writer, uint8_t layer, uint16_t offset) {
WriteInsnOneArg(writer, 0x88 | layer, offset);
}
static void WritePan(Ship::BinaryWriter* writer, uint8_t pan) {
WriteInsnOneArg(writer, 0xDD, pan);
}
static void WriteBend(Ship::BinaryWriter* writer, uint8_t bend) {
WriteInsnOneArg(writer, 0xD3, bend);
}
static void WriteInstrument(Ship::BinaryWriter* writer, uint8_t instrument) {
WriteInsnOneArg(writer, 0xC1, instrument);
}
static void WriteTranspose(Ship::BinaryWriter* writer, int8_t transpose) {
WriteInsnOneArg(writer, 0xC2, transpose);
}
static void WriteDelay(Ship::BinaryWriter* writer, uint16_t delay) {
if (delay > 0x7F) {
WriteInsnOneArg(writer, 0xFD, static_cast<uint16_t>(delay | 0x8000));
} else {
WriteInsnOneArg(writer, 0xFD, static_cast<uint8_t>(delay));
}
}
template <typename T> static void WriteLDelay(Ship::BinaryWriter* writer, T delay) {
WriteInsnOneArg(writer, 0xC0, delay);
}
template <typename T> static void WriteNotedv(Ship::BinaryWriter* writer, uint8_t note, T delay, uint8_t velocity) {
WriteInsnTwoArg(writer, note, delay, velocity);
}
static void WriteNotedvg(Ship::BinaryWriter* writer, uint8_t note, uint16_t delay, uint8_t velocity, uint8_t gateTime) {
if (delay > 0x7F) {
WriteInsnThreeArg(writer, note, static_cast<uint16_t>(delay | 0x8000), velocity, gateTime);
} else {
WriteInsnThreeArg(writer, note, static_cast<uint8_t>(delay), velocity, gateTime);
}
}
static void WriteMonoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) {
uint16_t channelStart;
uint16_t channelPlaceholderOff;
uint16_t loopPoint;
uint16_t layerPlaceholderOff;
uint16_t layerStart;
if (looped) {
delay = 0x7FFF;
}
// Write seq header
// These two values are always the same in OOT and MM
WriteMuteBhv(writer, 0x20);
WriteMuteScale(writer, 0x32);
// We only have one channel
WriteInitchan(writer, 0b11);
// Store the current position so we can write the address of the channel when we are ready.
channelPlaceholderOff = writer->GetBaseAddress();
// Store the current position so we can loop here after the song ends.
loopPoint = writer->GetBaseAddress();
WriteLdchan(writer, 0, 0); // Fill in the actual address later
WriteVolSHeader(writer, 127); // Max volume
WriteTempo(writer, tempo);
WriteDelay(writer, delay);
if (looped) {
WriteJump(writer, loopPoint);
}
WriteDisablecan(writer, 0b11);
writer->Write(static_cast<uint8_t>(0xFF));
// Fill in the ldchan from before
channelStart = writer->GetBaseAddress();
writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start);
WriteLdchan(writer, 0, channelStart);
writer->Seek(channelStart, Ship::SeekOffsetType::Start);
// Channel header
layerPlaceholderOff = writer->GetBaseAddress();
WriteNoshort(writer);
WriteLdlayer(writer, 0, 0);
WritePan(writer, 64);
WriteVolCHeader(writer, 127); // Max volume
WriteBend(writer, 0);
WriteInstrument(writer, 0);
WriteDelay(writer, delay);
writer->Write(static_cast<uint8_t>(0xFF));
layerStart = writer->GetBaseAddress();
writer->Seek(layerPlaceholderOff, Ship::SeekOffsetType::Start);
WriteLdlayer(writer, 0, layerStart);
writer->Seek(layerStart, Ship::SeekOffsetType::Start);
// Note layer
WriteLegato(writer);
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
writer->Write(static_cast<uint8_t>(0xFF));
}
static void WriteStereoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) {
uint16_t lChannelStart;
uint16_t rChannelStart;
uint16_t channelPlaceholderOff;
uint16_t loopPoint;
uint16_t lLayerPlaceholderOff;
uint16_t rLayerPlaceholderOff;
uint16_t lLayerOffset;
uint16_t rLayerOffset;
uint16_t layerStart;
// Write seq header
if (looped) {
delay = 0x7FFF;
}
// These two values are always the same in OOT and MM
WriteMuteBhv(writer, 0x20);
WriteMuteScale(writer, 0x32);
// We only have one channel
WriteInitchan(writer, 0b11);
// Store the current position so we can write the address of the channel when we are ready.
channelPlaceholderOff = writer->GetBaseAddress();
// Store the current position so we can loop here after the song ends.
loopPoint = writer->GetBaseAddress();
// Left note channel
WriteLdchan(writer, 0, 0); // Fill in the actual address later
// Right note channel
WriteLdchan(writer, 1, 0); // Fill in the actual address later
WriteVolSHeader(writer, 127); // Max volume
WriteTempo(writer, tempo);
WriteDelay(writer, delay);
if (looped) {
WriteJump(writer, loopPoint);
}
WriteDisablecan(writer, 0b11);
writer->Write(static_cast<uint8_t>(0xFF));
lChannelStart = writer->GetBaseAddress();
// Left Channel header
WriteNoshort(writer);
lLayerPlaceholderOff = writer->GetBaseAddress();
WriteLdlayer(writer, 0, 0);
WritePan(writer, 0);
WriteVolCHeader(writer, 127); // Max volume
WriteBend(writer, 0);
WriteInstrument(writer, 0);
WriteDelay(writer, delay);
writer->Write(static_cast<uint8_t>(0xFF));
rChannelStart = writer->GetBaseAddress();
// Right Channel header
WriteNoshort(writer);
rLayerPlaceholderOff = writer->GetBaseAddress();
WriteLdlayer(writer, 1, 0);
WritePan(writer, 127);
WriteVolCHeader(writer, 127); // Max volume
WriteBend(writer, 0);
WriteInstrument(writer, 1);
WriteDelay(writer, delay);
writer->Write(static_cast<uint8_t>(0xFF));
uint16_t placeHolder = writer->GetBaseAddress();
writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start);
WriteLdchan(writer, 0, lChannelStart);
WriteLdchan(writer, 1, rChannelStart);
writer->Seek(placeHolder, Ship::SeekOffsetType::Start);
// Left Note layer
lLayerOffset = writer->GetBaseAddress();
WriteLegato(writer);
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
writer->Write(static_cast<uint8_t>(0xFF));
// Right Note layer
rLayerOffset = writer->GetBaseAddress();
WriteLegato(writer);
WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast<uint8_t>(0x7F), static_cast<uint8_t>(1));
writer->Write(static_cast<uint8_t>(0xFF));
writer->Seek(lLayerPlaceholderOff, Ship::SeekOffsetType::Start);
WriteLdlayer(writer, 0, lLayerOffset);
writer->Seek(rLayerPlaceholderOff, Ship::SeekOffsetType::Start);
WriteLdlayer(writer, 1, rLayerOffset);
}
std::shared_ptr<Ship::IResource>
ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) {
if (!FileHasValidFormatAndReader(file, initData)) {
return nullptr;
}
auto sequence = std::make_shared<AudioSequence>(initData);
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
unsigned int i = 0;
sequence->sequence.medium =
ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium"), initData->Path.c_str());
sequence->sequence.cachePolicy =
ResourceFactoryXMLSoundFontV0::CachePolicyToInt(child->Attribute("CachePolicy"), initData->Path.c_str());
sequence->sequence.seqDataSize = child->IntAttribute("Size");
sequence->sequence.seqNumber = child->IntAttribute("Index");
bool streamed = child->BoolAttribute("Streamed");
memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts));
tinyxml2::XMLElement* fontsElement = child->FirstChildElement();
tinyxml2::XMLElement* fontElement = fontsElement->FirstChildElement();
while (fontElement != nullptr) {
sequence->sequence.fonts[i] = fontElement->IntAttribute("FontIdx");
fontElement = fontElement->NextSiblingElement();
i++;
}
sequence->sequence.numFonts = i;
const char* path = child->Attribute("Path");
std::shared_ptr<Ship::File> seqFile;
if (path != nullptr) {
seqFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path);
}
if (!streamed) {
sequence->sequence.seqDataSize = seqFile->Buffer.get()->size();
sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()];
memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size());
} else {
// setting numFonts to -1 tells the game's audio engine the sound font to used is CRC64 encoded in the font
// indicies.
sequence->sequence.numFonts = -1;
if (path != nullptr) {
sequence->sequence.seqDataSize = seqFile->Buffer.get()->size();
sequence->sequence.seqData = new char[seqFile->Buffer.get()->size()];
memcpy(sequence->sequence.seqData, seqFile->Buffer.get()->data(), seqFile->Buffer.get()->size());
} else {
unsigned int length = child->UnsignedAttribute("Length");
bool looped = child->BoolAttribute("Looped", true);
bool stereo = child->BoolAttribute("Stereo", false);
Ship::BinaryWriter writer = Ship::BinaryWriter();
writer.SetEndianness(Ship::Endianness::Big);
// 1 second worth of ticks can be found by using `ticks = 60 / (bpm * 48)`
// Get the number of ticks per second and then divide the length by this number to get the number of ticks
// for the song.
constexpr uint8_t TEMPO = 1;
constexpr float TEMPO_F = TEMPO;
// Use floats for this first calculation so we can round up
float delayF = length / (60.0f / (TEMPO_F * 48.0f));
// Convert to u16. This way this value is encoded changes depending on the value.
// It can be at most 0xFFFF so store it in a u16 for now.
uint16_t delay;
if (delayF >= 65535.0f) {
delay = 0x7FFF;
} else {
delay = delayF;
}
if (stereo) {
WriteStereoSingleSeq(&writer, delay, TEMPO, looped);
} else {
WriteMonoSingleSeq(&writer, delay, TEMPO, looped);
}
sequence->sequence.seqDataSize = writer.ToVector().size();
sequence->sequence.seqData = new char[sequence->sequence.seqDataSize];
memcpy(sequence->sequence.seqData, writer.ToVector().data(), sequence->sequence.seqDataSize);
}
}
return sequence;
}
} // namespace SOH

View file

@ -2,11 +2,19 @@
#include "Resource.h"
#include "ResourceFactoryBinary.h"
#include "ResourceFactoryXML.h"
namespace SOH {
class ResourceFactoryBinaryAudioSequenceV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryBinaryAudioSequenceV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryXMLAudioSequenceV0 : public Ship::ResourceFactoryXML {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
} // namespace SOH

View file

@ -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<Ship::IResource>
@ -35,117 +37,115 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
audioSoundFont->soundFont.numSfx = soundEffectCount;
// 🥁 DRUMS 🥁
audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums);
// audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums);
audioSoundFont->drumAddresses.reserve(audioSoundFont->soundFont.numDrums);
for (uint32_t i = 0; i < audioSoundFont->soundFont.numDrums; i++) {
Drum drum;
drum.releaseRate = reader->ReadUByte();
drum.pan = reader->ReadUByte();
drum.loaded = reader->ReadUByte();
drum.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont
Drum* drum = new Drum;
drum->releaseRate = reader->ReadUByte();
drum->pan = reader->ReadUByte();
drum->loaded = reader->ReadUByte();
drum->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName
uint32_t envelopeCount = reader->ReadUInt32();
audioSoundFont->drumEnvelopeCounts.push_back(envelopeCount);
std::vector<AdsrEnvelope> drumEnvelopes;
drumEnvelopes.reserve(audioSoundFont->drumEnvelopeCounts[i]);
for (uint32_t j = 0; j < audioSoundFont->drumEnvelopeCounts.back(); j++) {
AdsrEnvelope env;
drum->envelope = new AdsrEnvelope[envelopeCount];
for (uint32_t j = 0; j < envelopeCount; j++) {
int16_t delay = reader->ReadInt16();
int16_t arg = reader->ReadInt16();
env.delay = BE16SWAP(delay);
env.arg = BE16SWAP(arg);
drumEnvelopes.push_back(env);
drum->envelope[j].delay = BE16SWAP(delay);
drum->envelope[j].arg = BE16SWAP(arg);
}
audioSoundFont->drumEnvelopeArrays.push_back(drumEnvelopes);
drum.envelope = audioSoundFont->drumEnvelopeArrays.back().data();
bool hasSample = reader->ReadInt8();
std::string sampleFileName = reader->ReadString();
drum.sound.tuning = reader->ReadFloat();
drum->sound.tuning = reader->ReadFloat();
if (sampleFileName.empty()) {
drum.sound.sample = nullptr;
drum->sound.sample = nullptr;
} else {
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
drum.sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
drum->sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
}
audioSoundFont->drums.push_back(drum);
audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back());
// audioSoundFont->drums.push_back(drum);
// BENTODO clean this up in V3.
if (drum->sound.sample == nullptr) {
delete[] drum->envelope;
delete drum;
audioSoundFont->drumAddresses.push_back(nullptr);
} else {
audioSoundFont->drumAddresses.push_back(drum);
}
}
audioSoundFont->soundFont.drums = audioSoundFont->drumAddresses.data();
// 🎺🎻🎷🎸🎹 INSTRUMENTS 🎹🎸🎷🎻🎺
audioSoundFont->instruments.reserve(audioSoundFont->soundFont.numInstruments);
for (uint32_t i = 0; i < audioSoundFont->soundFont.numInstruments; i++) {
Instrument instrument;
Instrument* instrument = new Instrument;
uint8_t isValidEntry = reader->ReadUByte();
instrument.loaded = reader->ReadUByte();
instrument.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont
instrument->loaded = reader->ReadUByte();
instrument->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName
instrument.normalRangeLo = reader->ReadUByte();
instrument.normalRangeHi = reader->ReadUByte();
instrument.releaseRate = reader->ReadUByte();
instrument->normalRangeLo = reader->ReadUByte();
instrument->normalRangeHi = reader->ReadUByte();
instrument->releaseRate = reader->ReadUByte();
uint32_t envelopeCount = reader->ReadInt32();
audioSoundFont->instrumentEnvelopeCounts.push_back(envelopeCount);
std::vector<AdsrEnvelope> instrumentEnvelopes;
for (uint32_t j = 0; j < audioSoundFont->instrumentEnvelopeCounts.back(); j++) {
AdsrEnvelope env;
instrument->envelope = new AdsrEnvelope[envelopeCount];
for (uint32_t j = 0; j < envelopeCount; j++) {
int16_t delay = reader->ReadInt16();
int16_t arg = reader->ReadInt16();
env.delay = BE16SWAP(delay);
env.arg = BE16SWAP(arg);
instrumentEnvelopes.push_back(env);
instrument->envelope[j].delay = BE16SWAP(delay);
instrument->envelope[j].arg = BE16SWAP(arg);
}
audioSoundFont->instrumentEnvelopeArrays.push_back(instrumentEnvelopes);
instrument.envelope = audioSoundFont->instrumentEnvelopeArrays.back().data();
bool hasLowNoteSoundFontEntry = reader->ReadInt8();
if (hasLowNoteSoundFontEntry) {
bool hasSampleRef = reader->ReadInt8();
std::string sampleFileName = reader->ReadString();
instrument.lowNotesSound.tuning = reader->ReadFloat();
instrument->lowNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
instrument->lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
} else {
instrument.lowNotesSound.sample = nullptr;
instrument.lowNotesSound.tuning = 0;
instrument->lowNotesSound.sample = nullptr;
instrument->lowNotesSound.tuning = 0;
}
bool hasNormalNoteSoundFontEntry = reader->ReadInt8();
if (hasNormalNoteSoundFontEntry) {
// BENTODO remove in V3
bool hasSampleRef = reader->ReadInt8();
std::string sampleFileName = reader->ReadString();
instrument.normalNotesSound.tuning = reader->ReadFloat();
instrument->normalNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
instrument->normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
} else {
instrument.normalNotesSound.sample = nullptr;
instrument.normalNotesSound.tuning = 0;
instrument->normalNotesSound.sample = nullptr;
instrument->normalNotesSound.tuning = 0;
}
bool hasHighNoteSoundFontEntry = reader->ReadInt8();
if (hasHighNoteSoundFontEntry) {
bool hasSampleRef = reader->ReadInt8();
std::string sampleFileName = reader->ReadString();
instrument.highNotesSound.tuning = reader->ReadFloat();
instrument->highNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
instrument->highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
} else {
instrument.highNotesSound.sample = nullptr;
instrument.highNotesSound.tuning = 0;
instrument->highNotesSound.sample = nullptr;
instrument->highNotesSound.tuning = 0;
}
audioSoundFont->instruments.push_back(instrument);
audioSoundFont->instrumentAddresses.push_back(isValidEntry ? &audioSoundFont->instruments.back() : nullptr);
if (isValidEntry) {
audioSoundFont->instrumentAddresses.push_back(instrument);
} else {
delete[] instrument->envelope;
delete instrument;
audioSoundFont->instrumentAddresses.push_back(nullptr);
}
}
audioSoundFont->soundFont.instruments = audioSoundFont->instrumentAddresses.data();
@ -169,4 +169,302 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
return audioSoundFont;
}
int8_t ResourceFactoryXMLSoundFontV0::MediumStrToInt(const char* str, const char* file) {
if (!strcmp("Ram", str)) {
return MEDIUM_RAM;
} else if (!strcmp("Unk", str)) {
return MEDIUM_UNK;
} else if (!strcmp("Cart", str)) {
return MEDIUM_CART;
} else if (!strcmp("Disk", str)) {
return MEDIUM_DISK_DRIVE;
// 4 is skipped
} else {
char buff[2048];
snprintf(buff, 2048, "Bad medium value in %s. Got %s, expected Ram, Unk, Cart, or Disk.", file, str);
throw std::runtime_error(buff);
}
}
int8_t ResourceFactoryXMLSoundFontV0::CachePolicyToInt(const char* str, const char* file) {
if (!strcmp("Temporary", str)) {
return CACHE_TEMPORARY;
} else if (!strcmp("Persistent", str)) {
return CACHE_PERSISTENT;
} else if (!strcmp("Either", str)) {
return CACHE_EITHER;
} else if (!strcmp("Permanent", str)) {
return CACHE_PERMANENT;
} else {
char buff[2048];
snprintf(buff, 2048,
"Bad cache policy value in %s. Got %s, expected Temporary, Persistent, Either, or Permanent.", file,
str);
throw std::runtime_error(buff);
}
}
void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
element = element->FirstChildElement();
// No drums
if (element == nullptr) {
soundFont->soundFont.drums = nullptr;
soundFont->soundFont.numDrums = 0;
return;
}
do {
int patch = element->IntAttribute("Patches", -1);
Drum* drum;
if (patch != -1) {
drum = soundFont->drumAddresses[patch];
} else {
drum = new Drum;
}
std::vector<AdsrEnvelope> envelopes;
drum->releaseRate = element->IntAttribute("ReleaseRate");
drum->pan = element->IntAttribute("Pan");
drum->loaded = element->IntAttribute("Loaded");
drum->sound.tuning = element->FloatAttribute("Tuning");
const char* sampleStr = element->Attribute("SampleRef");
if (sampleStr != nullptr && sampleStr[0] != 0) {
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr);
drum->sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
} else {
drum->sound.sample = nullptr;
}
element = element->FirstChildElement();
if (!strcmp(element->Name(), "Envelopes")) {
// element = (tinyxml2::XMLElement*)element->FirstChildElement();
unsigned int envCount = 0;
envelopes = ParseEnvelopes(soundFont, element, &envCount);
element = (tinyxml2::XMLElement*)element->Parent();
soundFont->drumEnvelopeArrays.push_back(envelopes);
// If we are applying a patch the envelopes are already allocated
// TODO revert this if we enable editing envelopes in a patch
if (patch == -1) {
drum->envelope = new AdsrEnvelope[envelopes.size()];
}
memcpy(drum->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope));
} else {
drum->envelope = nullptr;
}
if (drum->sound.sample == nullptr) {
soundFont->drumAddresses.push_back(nullptr);
} else {
soundFont->drumAddresses.push_back(drum);
}
element = element->NextSiblingElement();
} while (element != nullptr);
soundFont->soundFont.numDrums = soundFont->drumAddresses.size();
soundFont->soundFont.drums = soundFont->drumAddresses.data();
}
void ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
element = element->FirstChildElement();
if (element == nullptr) {
return;
}
do {
int patch = element->IntAttribute("Patches", -1);
Instrument* instrument;
// Same as drums, if applying a patch, don't re-allocate and clear.
if (patch != -1) {
instrument = soundFont->instrumentAddresses[patch];
} else {
instrument = new Instrument;
memset(instrument, 0, sizeof(Instrument));
}
unsigned int envCount = 0;
std::vector<AdsrEnvelope> envelopes;
int isValid = element->BoolAttribute("IsValid");
instrument->loaded = element->IntAttribute("Loaded");
instrument->normalRangeLo = element->IntAttribute("NormalRangeLo");
instrument->normalRangeHi = element->IntAttribute("NormalRangeHi");
instrument->releaseRate = element->IntAttribute("ReleaseRate");
tinyxml2::XMLElement* instrumentElement = element->FirstChildElement();
tinyxml2::XMLElement* instrumentElementCopy = instrumentElement;
if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) {
envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount);
if (patch == -1) {
instrument->envelope = new AdsrEnvelope[envelopes.size()];
}
memcpy(instrument->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope));
instrumentElement = instrumentElement->NextSiblingElement();
}
if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) {
instrument->lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
const char* sampleStr = instrumentElement->Attribute("SampleRef");
if (sampleStr != nullptr && sampleStr[0] != 0) {
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
if (res->tuning != -1.0f) {
instrument->lowNotesSound.tuning = res->tuning;
}
instrument->lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
}
instrumentElement = instrumentElement->NextSiblingElement();
}
if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) {
instrument->normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
const char* sampleStr = instrumentElement->Attribute("SampleRef");
if (sampleStr != nullptr && sampleStr[0] != 0) {
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
if (res->tuning != -1.0f) {
instrument->normalNotesSound.tuning = res->tuning;
}
instrument->normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
}
instrumentElement = instrumentElement->NextSiblingElement();
}
if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) {
instrument->highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning");
const char* sampleStr = instrumentElement->Attribute("SampleRef");
if (sampleStr != nullptr && sampleStr[0] != 0) {
std::shared_ptr<SOH::AudioSample> res = static_pointer_cast<SOH::AudioSample>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
if (res->tuning != -1.0f) {
instrument->highNotesSound.tuning = res->tuning;
}
instrument->highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
}
instrumentElement = instrumentElement->NextSiblingElement();
}
// Don't add it to the list if applying a patch
if (patch == -1) {
soundFont->instrumentAddresses.push_back(instrument);
}
element = instrumentElementCopy;
element = (tinyxml2::XMLElement*)element->Parent();
element = element->NextSiblingElement();
} while (element != nullptr);
soundFont->soundFont.instruments = soundFont->instrumentAddresses.data();
soundFont->soundFont.numInstruments = soundFont->instrumentAddresses.size();
}
void ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) {
size_t count = element->IntAttribute("Count");
element = element->FirstChildElement();
while (element != nullptr) {
int patch = element->IntAttribute("Patches", -1);
SoundFontSound sound = {};
const char* sampleStr = element->Attribute("SampleRef");
// Insert an empty sound effect. The game assumes the empty slots are
// filled so we can't just skip them
if (sampleStr == nullptr)
goto skip;
sound.tuning = element->FloatAttribute("Tuning");
if (sampleStr[0] != 0) {
auto res = static_pointer_cast<SOH::AudioSample>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr));
if (res->tuning != -1.0f) {
sound.tuning = res->tuning;
}
sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
}
skip:
element = element->NextSiblingElement();
if (patch != -1) {
soundFont->soundEffects[patch] = sound;
} else {
soundFont->soundEffects.push_back(sound);
}
}
soundFont->soundFont.soundEffects = soundFont->soundEffects.data();
soundFont->soundFont.numSfx = soundFont->soundEffects.size();
}
std::vector<AdsrEnvelope> SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont,
tinyxml2::XMLElement* element,
unsigned int* count) {
std::vector<AdsrEnvelope> envelopes;
unsigned int total = 0;
element = element->FirstChildElement("Envelope");
while (element != nullptr) {
AdsrEnvelope env = {
.delay = (s16)element->IntAttribute("Delay"),
.arg = (s16)element->IntAttribute("Arg"),
};
env.delay = BSWAP16(env.delay);
env.arg = BSWAP16(env.arg);
envelopes.emplace_back(env);
element = element->NextSiblingElement("Envelope");
total++;
}
*count = total;
return envelopes;
}
std::shared_ptr<Ship::IResource>
ResourceFactoryXMLSoundFontV0::ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) {
if (!FileHasValidFormatAndReader(file, initData)) {
return nullptr;
}
auto child = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(file->Reader)->FirstChildElement();
const char* patch = child->Attribute("Patches");
std::shared_ptr<Ship::IResource> sf;
std::shared_ptr<AudioSoundFont> audioSoundFont;
// If we are patching an existing SF, load the original, otherwise create and clear a new one.
if (patch != nullptr) {
std::string origName = "audio/fonts/";
origName += patch;
audioSoundFont = dynamic_pointer_cast<AudioSoundFont>(
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(origName));
} else {
audioSoundFont = std::make_shared<AudioSoundFont>(initData);
memset(&audioSoundFont->soundFont, 0, sizeof(audioSoundFont->soundFont));
}
// Header data
audioSoundFont->soundFont.fntIndex = child->IntAttribute("Num", 0);
const char* mediumStr = child->Attribute("Medium");
audioSoundFont->medium = MediumStrToInt(mediumStr, initData->Path.c_str());
const char* cachePolicyStr = child->Attribute("CachePolicy");
audioSoundFont->cachePolicy = CachePolicyToInt(cachePolicyStr, initData->Path.c_str());
audioSoundFont->data1 = child->IntAttribute("Data1");
audioSoundFont->data2 = child->IntAttribute("Data2");
audioSoundFont->data3 = child->IntAttribute("Data3");
audioSoundFont->soundFont.sampleBankId1 = audioSoundFont->data1 >> 8;
audioSoundFont->soundFont.sampleBankId2 = audioSoundFont->data1 & 0xFF;
child = (tinyxml2::XMLElement*)child->FirstChildElement();
while (child != nullptr) {
const char* name = child->Name();
if (!strcmp(name, "Drums")) {
ParseDrums(audioSoundFont.get(), child);
} else if (!strcmp(name, "Instruments")) {
ParseInstruments(audioSoundFont.get(), child);
} else if (!strcmp(name, "SfxTable")) {
ParseSfxTable(audioSoundFont.get(), child);
}
child = child->NextSiblingElement();
}
return audioSoundFont;
}
} // namespace SOH

View file

@ -2,11 +2,29 @@
#include "Resource.h"
#include "ResourceFactoryBinary.h"
#include "ResourceFactoryXML.h"
#include "soh/resource/type/AudioSoundFont.h"
namespace SOH {
class ResourceFactoryBinaryAudioSoundFontV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryBinaryAudioSoundFontV2 : public Ship::ResourceFactoryBinary {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
class ResourceFactoryXMLSoundFontV0 : public Ship::ResourceFactoryXML {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
static int8_t MediumStrToInt(const char* str, const char* file);
static int8_t CachePolicyToInt(const char* str, const char* file);
private:
void ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
void ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
void ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element);
std::vector<AdsrEnvelope> ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element,
unsigned int* count);
};
} // namespace SOH

View file

@ -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

View file

@ -1,62 +1,60 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Resource.h"
#include <libultraship/libultra/types.h>
namespace SOH {
typedef struct {
/* 0x00 */ uintptr_t start;
/* 0x04 */ uintptr_t end;
/* 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<Sample> {
public:
using Resource::Resource;
AudioSample() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
~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<Sample> {
public:
using Resource::Resource;
AudioSample() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
Sample* GetPointer();
size_t GetPointerSize();
Sample sample;
std::vector<uint8_t> audioSampleData;
AdpcmLoop loop;
uint32_t loopStateCount;
AdpcmBook book;
uint32_t bookDataCount;
std::vector<int16_t> bookData;
};
}; // namespace SOH
}; // namespace SOH

View file

@ -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

View file

@ -1,33 +1,31 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Resource.h"
#include <libultraship/libultra/types.h>
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<Sequence> {
public:
using Resource::Resource;
class AudioSequence : public Ship::Resource<Sequence> {
public:
using Resource::Resource;
AudioSequence() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
AudioSequence() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
~AudioSequence();
Sequence* GetPointer();
size_t GetPointerSize();
Sequence* GetPointer();
size_t GetPointerSize();
Sequence sequence;
std::vector<char> sequenceData;
};
}; // namespace SOH
Sequence sequence;
};
}; // namespace SOH

View file

@ -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

View file

@ -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<SoundFont> {
public:
using Resource::Resource;
AudioSoundFont() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
~AudioSoundFont();
SoundFont* GetPointer();
size_t GetPointerSize();
int8_t medium;
int8_t cachePolicy;
uint16_t data1;
uint16_t data2;
uint16_t data3;
std::vector<Drum*> drumAddresses;
std::vector<std::vector<AdsrEnvelope>> drumEnvelopeArrays;
std::vector<Instrument*> instrumentAddresses;
std::vector<SoundFontSound> 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<SoundFont> {
public:
using Resource::Resource;
AudioSoundFont() : Resource(std::shared_ptr<Ship::ResourceInitData>()) {
}
SoundFont* GetPointer();
size_t GetPointerSize();
int8_t medium;
int8_t cachePolicy;
uint16_t data1;
uint16_t data2;
uint16_t data3;
std::vector<Drum> drums;
std::vector<Drum*> drumAddresses;
std::vector<uint32_t> drumEnvelopeCounts;
std::vector<std::vector<AdsrEnvelope>> drumEnvelopeArrays;
std::vector<Instrument> instruments;
std::vector<Instrument*> instrumentAddresses;
std::vector<uint32_t> instrumentEnvelopeCounts;
std::vector<std::vector<AdsrEnvelope>> instrumentEnvelopeArrays;
std::vector<SoundFontSound> soundEffects;
SoundFont soundFont;
};
}; // namespace SOH

View file

@ -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;

View file

@ -8,6 +8,10 @@
#include "soh/Enhancements/audio/AudioCollection.h"
#include "soh/Enhancements/audio/AudioEditor.h"
#include "soh/ResourceManagerHelpers.h"
#include <stdio.h>
#ifdef _MSC_VER
#define strdup _strdup
#endif
#define MK_ASYNC_MSG(retData, tableType, id, status) (((retData) << 24) | ((tableType) << 16) | ((id) << 8) | (status))
#define ASYNC_TBLTYPE(v) ((u8)(v >> 16))
@ -82,7 +86,8 @@ char** sequenceMap;
size_t sequenceMapSize;
// A map of authentic sequence IDs to their cache policies, for use with sequence swapping.
u8 seqCachePolicyMap[MAX_AUTHENTIC_SEQID];
char* fontMap[256];
size_t fontMapSize;
char** fontMap;
uintptr_t fontStart;
uint32_t fontOffsets[8192];
@ -419,7 +424,7 @@ void AudioLoad_SyncLoadSeqParts(s32 seqId, s32 arg1) {
s32 AudioLoad_SyncLoadSample(SoundFontSample* sample, s32 fontId) {
void* sampleAddr;
if (sample->unk_bit25 == 1) {
if (sample->isRelocated == 1) {
if (sample->medium != MEDIUM_RAM) {
sampleAddr = AudioHeap_AllocSampleCache(sample->size, fontId, (void*)sample->sampleAddr, sample->medium,
CACHE_PERSISTENT);
@ -701,7 +706,7 @@ SoundFontData* AudioLoad_SyncLoadFont(u32 fontId) {
return NULL;
}
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
sampleBankId1 = sf->sampleBankId1;
sampleBankId2 = sf->sampleBankId2;
@ -759,7 +764,7 @@ uintptr_t AudioLoad_SyncLoad(u32 tableType, u32 id, s32* didAllocate) {
cachePolicy = sData.cachePolicy;
romAddr = 0;
} else if (tableType == FONT_TABLE) {
fnt = ResourceMgr_LoadAudioSoundFont(fontMap[id]);
fnt = ResourceMgr_LoadAudioSoundFontByName(fontMap[id]);
size = sizeof(SoundFont);
medium = 2;
cachePolicy = 0;
@ -887,6 +892,7 @@ AudioTable* AudioLoad_GetLoadTable(s32 tableType) {
}
void AudioLoad_RelocateFont(s32 fontId, SoundFontData* mem, RelocInfo* relocInfo) {
return;
uintptr_t reloc;
uintptr_t reloc2;
Instrument* inst;
@ -898,7 +904,7 @@ void AudioLoad_RelocateFont(s32 fontId, SoundFontData* mem, RelocInfo* relocInfo
s32 numInstruments = 0;
s32 numSfx = 0;
sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
numDrums = sf->numDrums;
numInstruments = sf->numInstruments;
numSfx = sf->numSfx;
@ -1247,12 +1253,13 @@ int strcmp_sort(const void* str1, const void* str2) {
}
void AudioLoad_Init(void* heap, size_t heapSize) {
char pad[0x48];
s32 pad1[9];
s32 numFonts;
void* temp_v0_3;
s32 pad2[2];
u8* audioCtxPtr;
void* addr;
s32 i;
u64* heapP;
s16* u2974p;
s32 j;
D_801755D0 = NULL;
gAudioContext.resetTimer = 0;
@ -1266,10 +1273,12 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
gAudioContext.unk_2960 = 20.03042f;
gAudioContext.refreshRate = 50;
break;
case OS_TV_MPAL:
gAudioContext.unk_2960 = 16.546f;
gAudioContext.refreshRate = 60;
break;
case OS_TV_NTSC:
default:
gAudioContext.unk_2960 = 16.713f;
@ -1278,7 +1287,7 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
Audio_InitMesgQueues();
for (i = 0; i < 3; i++) {
for (i = 0; i < ARRAY_COUNT(gAudioContext.aiBufLengths); i++) {
gAudioContext.aiBufLengths[i] = 0xA0;
}
@ -1289,12 +1298,14 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
gAudioContext.currTask = NULL;
gAudioContext.rspTask[0].task.t.data_size = 0;
gAudioContext.rspTask[1].task.t.data_size = 0;
osCreateMesgQueue(&gAudioContext.syncDmaQueue, &gAudioContext.syncDmaMesg, 1);
osCreateMesgQueue(&gAudioContext.currAudioFrameDmaQueue, gAudioContext.currAudioFrameDmaMesgBuf, 0x40);
osCreateMesgQueue(&gAudioContext.currAudioFrameDmaQueue, gAudioContext.currAudioFrameDmaMesgBuf,
ARRAY_COUNT(gAudioContext.currAudioFrameDmaMesgBuf));
osCreateMesgQueue(&gAudioContext.externalLoadQueue, gAudioContext.externalLoadMesgBuf,
ARRAY_COUNT(gAudioContext.externalLoadMesgBuf));
osCreateMesgQueue(&gAudioContext.preloadSampleQueue, gAudioContext.preloadSampleMesgBuf,
ARRAY_COUNT(gAudioContext.externalLoadMesgBuf));
ARRAY_COUNT(gAudioContext.preloadSampleMesgBuf));
gAudioContext.curAudioFrameDmaCount = 0;
gAudioContext.sampleDmaCount = 0;
gAudioContext.cartHandle = osCartRomInit();
@ -1304,20 +1315,24 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
gAudioContext.audioHeapSize = D_8014A6C4.heapSize;
} else {
void** hp = &heap;
gAudioContext.audioHeap = *hp;
gAudioContext.audioHeapSize = heapSize;
}
for (i = 0; i < gAudioContext.audioHeapSize / 8; i++) {
for (i = 0; i < ((s32)gAudioContext.audioHeapSize / (s32)sizeof(u64)); i++) {
((u64*)gAudioContext.audioHeap)[i] = 0;
}
// Main Pool Split (split entirety of audio heap into initPool and sessionPool)
AudioHeap_InitMainPools(D_8014A6C4.initPoolSize);
for (i = 0; i < 3; i++) {
// Initialize the audio interface buffers
for (i = 0; i < ARRAY_COUNT(gAudioContext.aiBuffers); i++) {
gAudioContext.aiBuffers[i] = AudioHeap_AllocZeroed(&gAudioContext.audioInitPool, AIBUF_LEN * sizeof(s16));
}
// Connect audio tables to their tables in memory
// gAudioContext.sequenceTable = (AudioTable*)gSequenceTable;
// gAudioContext.soundFontTable = (AudioTable*)gSoundFontTable;
// gAudioContext.sampleBankTable = (AudioTable*)gSampleBankTable;
@ -1325,31 +1340,60 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
// gAudioContext.numSequences = gAudioContext.sequenceTable->numEntries;
gAudioContext.audioResetSpecIdToLoad = 0;
gAudioContext.resetStatus = 1;
gAudioContext.resetStatus = 1; // Set reset to immediately initialize the audio heap
AudioHeap_ResetStep();
// Initialize audio tables
// AudioLoad_InitTable(gAudioContext.sequenceTable, SEGMENT_ROM_START(Audioseq), 0);
// AudioLoad_InitTable(gAudioContext.soundFontTable, SEGMENT_ROM_START(Audiobank), 0);
// AudioLoad_InitTable(gAudioContext.sampleBankTable, SEGMENT_ROM_START(Audiotable), 0);
// #region 2S2H [Port] Audio in the archive and custom sequences
// Only load the original sequences right now because custom songs may require data from sound fonts and samples
int seqListSize = 0;
int customSeqListSize = 0;
char** seqList = ResourceMgr_ListFiles("audio/sequences*", &seqListSize);
char** customSeqList = ResourceMgr_ListFiles("custom/music/*", &customSeqListSize);
sequenceMapSize = (size_t)(AudioCollection_SequenceMapSize() + customSeqListSize);
sequenceMap = malloc(sequenceMapSize * sizeof(char*));
gAudioContext.seqLoadStatus = malloc(sequenceMapSize * sizeof(char*));
sequenceMapSize = (size_t)(seqListSize + customSeqListSize );
sequenceMap = malloc((sequenceMapSize + 0xF) * sizeof(char*));
gAudioContext.seqLoadStatus = malloc(sequenceMapSize);
memset(gAudioContext.seqLoadStatus, 5, sequenceMapSize);
for (size_t i = 0; i < seqListSize; i++) {
SequenceData sDat = ResourceMgr_LoadSeqByName(seqList[i]);
char* str = malloc(strlen(seqList[i]) + 1);
strcpy(str, seqList[i]);
sequenceMap[sDat.seqNumber] = str;
sequenceMap[sDat.seqNumber] = strdup(seqList[i]);
seqCachePolicyMap[sDat.seqNumber] = sDat.cachePolicy;
}
free(seqList);
int startingSeqNum = MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence
// 2S2H [Streamed Audio] We need to load the custom songs after the fonts because streamed songs will use a hash to
// find its soundfont
int fntListSize = 0;
int customFntListSize = 0;
char** fntList = ResourceMgr_ListFiles("audio/fonts*", &fntListSize);
char** customFntList = ResourceMgr_ListFiles("custom/fonts/*", &customFntListSize);
gAudioContext.fontLoadStatus = malloc(customFntListSize + fntListSize);
fontMap = calloc(customFntListSize + fntListSize, sizeof(char*));
fontMapSize = customFntListSize + fntListSize;
for (int i = 0; i < fntListSize; i++) {
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fntList[i]);
fontMap[sf->fntIndex] = strdup(fntList[i]);
}
free(fntList);
int customFontStart = fntListSize;
for (int i = customFontStart; i < customFntListSize + fntListSize; i++) {
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(customFntList[i - customFontStart]);
sf->fntIndex = i;
fontMap[i] = strdup(customFntList[i - customFontStart]);
}
free(customFntList);
// 2S2H Port I think we need to take use seqListSize because entry 0x7A is missing.
int startingSeqNum = seqListSize; // MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence
qsort(customSeqList, customSeqListSize, sizeof(char*), strcmp_sort);
// Because AudioCollection's sequenceMap actually has more than sequences (including instruments from 130-135 and
@ -1361,48 +1405,61 @@ void AudioLoad_Init(void* heap, size_t heapSize) {
for (size_t i = startingSeqNum; i < startingSeqNum + customSeqListSize; i++) {
// ensure that what would be the next sequence number is actually unassigned in AudioCollection
int j = i - startingSeqNum;
SequenceData* sDat = ResourceMgr_LoadSeqPtrByName(customSeqList[j]);
if (sDat->numFonts == -1) {
uint64_t crc;
memcpy(&crc, sDat->fonts, sizeof(uint64_t));
const char* res = ResourceGetNameByCrc(crc);
if (res == NULL) {
// Passing a null buffer and length of 0 to snprintf will return the required numbers of characters the
// buffer needs to be.
int len =
snprintf(NULL, 0, "Could not find sound font for sequence %s. It will not be in the audio editor.",
customSeqList[j]);
char* error = malloc(len + 1);
snprintf(error, len, "Could not find sound font for sequence %s. It will not be in the audio editor.",
customSeqList[j]);
LUSLOG_ERROR("%s", error);
Messagebox_ShowErrorBox("Invalid Sequence", error);
free(error);
continue;
}
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(res);
memset(&sDat->fonts[0], 0, sizeof(sDat->fonts));
sDat->fonts[0] = sf->fntIndex;
sDat->numFonts = 1;
}
while (AudioCollection_HasSequenceNum(seqNum)) {
seqNum++;
}
int j = i - startingSeqNum;
AudioCollection_AddToCollection(customSeqList[j], seqNum);
SequenceData sDat = ResourceMgr_LoadSeqByName(customSeqList[j]);
sDat.seqNumber = seqNum;
char* str = malloc(strlen(customSeqList[j]) + 1);
strcpy(str, customSeqList[j]);
sequenceMap[sDat.seqNumber] = str;
sDat->seqNumber = seqNum;
printf("%d\n", seqNum);
sequenceMap[sDat->seqNumber] = strdup(customSeqList[j]);
seqNum++;
}
free(customSeqList);
int fntListSize = 0;
char** fntList = ResourceMgr_ListFiles("audio/fonts*", &fntListSize);
for (int i = 0; i < fntListSize; i++) {
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fntList[i]);
char* str = malloc(strlen(fntList[i]) + 1);
strcpy(str, fntList[i]);
fontMap[sf->fntIndex] = str;
}
numFonts = fntListSize;
free(fntList);
// #end region
gAudioContext.soundFonts = AudioHeap_Alloc(&gAudioContext.audioInitPool, numFonts * sizeof(SoundFont));
if (temp_v0_3 = AudioHeap_Alloc(&gAudioContext.audioInitPool, D_8014A6C4.permanentPoolSize), temp_v0_3 == NULL) {
if (addr = AudioHeap_Alloc(&gAudioContext.audioInitPool, D_8014A6C4.permanentPoolSize), addr == NULL) {
// cast away const from D_8014A6C4
// *((u32*)&D_8014A6C4.permanentPoolSize) = 0;
*((u32*)&D_8014A6C4.permanentPoolSize) = 0;
}
AudioHeap_AllocPoolInit(&gAudioContext.permanentPool, temp_v0_3, D_8014A6C4.permanentPoolSize);
AudioHeap_AllocPoolInit(&gAudioContext.permanentPool, addr, D_8014A6C4.permanentPoolSize);
gAudioContextInitalized = true;
osSendMesg32(gAudioContext.taskStartQueueP, gAudioContext.totalTaskCnt, OS_MESG_NOBLOCK);
osSendMesg(gAudioContext.taskStartQueueP, OS_MESG_32(gAudioContext.totalTaskCnt), OS_MESG_NOBLOCK);
}
void AudioLoad_InitSlowLoads(void) {
@ -2059,7 +2116,7 @@ void AudioLoad_PreloadSamplesForFont(s32 fontId, s32 async, RelocInfo* relocInfo
gAudioContext.numUsedSamples = 0;
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
numDrums = sf->numDrums;
numInstruments = sf->numInstruments;
@ -2188,7 +2245,7 @@ void AudioLoad_LoadPermanentSamples(void) {
fontId = AudioLoad_GetRealTableIndex(FONT_TABLE, gAudioContext.permanentCache[i].id);
// fontId = gAudioContext.permanentCache[i].id;
SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]);
SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]);
relocInfo.sampleBankId1 = sf->sampleBankId1;
relocInfo.sampleBankId2 = sf->sampleBankId2;

View file

@ -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];
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}