From 5b1a6102d10536171bda2fb689638af5b65abee6 Mon Sep 17 00:00:00 2001 From: Louis <35883445+louist103@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:25:08 -0500 Subject: [PATCH] Backport 2ship streamed audio --- soh/soh/Enhancements/savestates.cpp | 8 +- soh/soh/OTRGlobals.cpp | 70 +-- soh/soh/OTRGlobals.h | 6 +- .../resource/importer/AudioSampleFactory.cpp | 296 ++++++++++--- .../resource/importer/AudioSampleFactory.h | 10 + .../importer/AudioSequenceFactory.cpp | 399 +++++++++++++++++- .../resource/importer/AudioSequenceFactory.h | 11 + .../importer/AudioSoundFontFactory.cpp | 363 +++++++++++++--- .../resource/importer/AudioSoundFontFactory.h | 16 + soh/soh/resource/type/AudioSample.h | 102 ++--- soh/soh/resource/type/AudioSequence.h | 4 +- soh/soh/resource/type/AudioSoundFont.cpp | 17 + soh/soh/resource/type/AudioSoundFont.h | 13 +- soh/src/code/audio_heap.c | 4 +- soh/src/code/audio_load.c | 109 +++-- soh/src/code/audio_playback.c | 13 +- soh/src/code/audio_seqplayer.c | 2 +- soh/src/code/audio_synthesis.c | 30 +- soh/src/code/code_800E4FE0.c | 2 +- 19 files changed, 1164 insertions(+), 311 deletions(-) diff --git a/soh/soh/Enhancements/savestates.cpp b/soh/soh/Enhancements/savestates.cpp index 4d051911e2a..222b0d9d062 100644 --- a/soh/soh/Enhancements/savestates.cpp +++ b/soh/soh/Enhancements/savestates.cpp @@ -862,11 +862,11 @@ void SaveStateMgr::ProcessSaveStateRequests(void) { this->states[request.slot]->Load(); Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->TextDrawNotification(1.0f, true, "loaded state %u", request.slot); } else { - SPDLOG_ERROR("Invalid SaveState slot: {}", request.type); + SPDLOG_ERROR("Invalid SaveState slot: {}", (uint32_t)request.type); } break; [[unlikely]] default: - SPDLOG_ERROR("Invalid SaveState request type: {}", request.type); + SPDLOG_ERROR("Invalid SaveState request type: {}", (uint32_t)request.type); break; } this->requests.pop(); @@ -889,12 +889,12 @@ SaveStateReturn SaveStateMgr::AddRequest(const SaveStateRequest request) { requests.push(request); return SaveStateReturn::SUCCESS; } else { - SPDLOG_ERROR("Invalid SaveState slot: {}", request.type); + SPDLOG_ERROR("Invalid SaveState slot: {}", (uint32_t)request.type); Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->TextDrawNotification(1.0f, true, "state slot %u empty", request.slot); return SaveStateReturn::FAIL_INVALID_SLOT; } [[unlikely]] default: - SPDLOG_ERROR("Invalid SaveState request type: {}", request.type); + SPDLOG_ERROR("Invalid SaveState request type: {}", (uint32_t)request.type); return SaveStateReturn::FAIL_BAD_REQUEST; } } diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 12f94e5fd31..f9fadcd0ca4 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -364,8 +364,11 @@ OTRGlobals::OTRGlobals() { loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Text", static_cast(SOH::ResourceType::SOH_Text), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Text", static_cast(SOH::ResourceType::SOH_Text), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSample", static_cast(SOH::ResourceType::SOH_AudioSample), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Sample", static_cast(SOH::ResourceType::SOH_AudioSample), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "SoundFont", static_cast(SOH::ResourceType::SOH_AudioSoundFont), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioSequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 2); + loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Sequence", static_cast(SOH::ResourceType::SOH_AudioSequence), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Background", static_cast(SOH::ResourceType::SOH_Background), 0); gSaveStateMgr = std::make_shared(); @@ -1866,71 +1869,16 @@ extern "C" SequenceData ResourceMgr_LoadSeqByName(const char* path) { return *sequence; } -std::map cachedCustomSFs; - -extern "C" SoundFontSample* ReadCustomSample(const char* path) { - return nullptr; -/* - if (!ExtensionCache.contains(path)) - 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; -*/ +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); } @@ -2287,6 +2235,10 @@ extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len) { AudioPlayerPlayFrame(buf, len); } +extern "C" void Messagebox_ShowErrorBox(char* title, char* body) { + Extractor::ShowErrorBox(title, body); +} + extern "C" int Controller_ShouldRumble(size_t slot) { for (auto [id, mapping] : Ship::Context::GetInstance() ->GetControlDeck() diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index e0b5c7a3816..808c0fb122e 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -129,9 +129,11 @@ 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); +SoundFont* ResourceMgr_LoadAudioSoundFontByName(const char* path); CollisionHeader* ResourceMgr_LoadColByName(const char* path); void Ctx_ReadSaveFile(uintptr_t addr, void* dramAddr, size_t size); void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size); @@ -197,6 +199,8 @@ void Gfx_TextureCacheDelete(const uint8_t* addr); void SaveManager_ThreadPoolWait(); void CheckTracker_OnMessageClose(); +void Messagebox_ShowErrorBox(char* title, char* body); + GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID); #endif diff --git a/soh/soh/resource/importer/AudioSampleFactory.cpp b/soh/soh/resource/importer/AudioSampleFactory.cpp index 0ec3f526018..a66e9fc998e 100644 --- a/soh/soh/resource/importer/AudioSampleFactory.cpp +++ b/soh/soh/resource/importer/AudioSampleFactory.cpp @@ -1,6 +1,132 @@ #include "soh/resource/importer/AudioSampleFactory.h" #include "soh/resource/type/AudioSample.h" -#include "spdlog/spdlog.h" +#include "soh/resource/importer/AudioSoundFontFactory.h" +#include "StringHelper.h" +#include "libultraship/libultraship.h" +#include "z64audio.h" +#define DR_WAV_IMPLEMENTATION +#include + +#define DR_MP3_IMPLEMENTATION +#include + +#define DR_FLAC_IMPLEMENTATION +#include + +#include "vorbis/vorbisfile.h" + +struct OggFileData { + void* data; + size_t pos; + size_t size; +}; + +static size_t VorbisReadCallback(void* out, size_t size, size_t elems, void* src) { + OggFileData* data = static_cast(src); + size_t toRead = size * elems; + + if (toRead > data->size - data->pos) { + toRead = data->size - data->pos; + } + + memcpy(out, static_cast(data->data) + data->pos, toRead); + data->pos += toRead; + + return toRead / size; +} + +static int VorbisSeekCallback(void* src, ogg_int64_t pos, int whence) { + OggFileData* data = static_cast(src); + size_t newPos; + + switch (whence) { + case SEEK_SET: + newPos = pos; + break; + case SEEK_CUR: + newPos = data->pos + pos; + break; + case SEEK_END: + newPos = data->size + pos; + break; + default: + return -1; + } + if (newPos > data->size) { + return -1; + } + data->pos = newPos; + return 0; +} + +static int VorbisCloseCallback([[maybe_unused]] void* src) { + return 0; +} + +static long VorbisTellCallback(void* src) { + OggFileData* data = static_cast(src); + return data->pos; +} + +static const ov_callbacks vorbisCallbacks = { + VorbisReadCallback, + VorbisSeekCallback, + VorbisCloseCallback, + VorbisTellCallback, +}; + +static void Mp3DecoderWorker(std::shared_ptr audioSample, std::shared_ptr sampleFile) { + drmp3 mp3; + drwav_uint64 numFrames; + drmp3_bool32 ret = + drmp3_init_memory(&mp3, sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr); + numFrames = drmp3_get_pcm_frame_count(&mp3); + drwav_uint64 channels = mp3.channels; + drwav_uint64 sampleRate = mp3.sampleRate; + + audioSample->audioSampleData.reserve(numFrames * channels * 2); // 2 for sizeof(s16) + drmp3_read_pcm_frames_s16(&mp3, numFrames, (int16_t*)audioSample->audioSampleData.data()); + audioSample->sample.sampleAddr = audioSample->audioSampleData.data(); +} + +static void FlacDecoderWorker(std::shared_ptr audioSample, std::shared_ptr sampleFile) { + drflac* flac = drflac_open_memory(sampleFile->Buffer.get()->data(), sampleFile->Buffer.get()->size(), nullptr); + drflac_uint64 numFrames = flac->totalPCMFrameCount; + audioSample->audioSampleData.reserve(numFrames * flac->channels * 2); + drflac_read_pcm_frames_s16(flac, numFrames, (int16_t*)audioSample->audioSampleData.data()); + audioSample->sample.sampleAddr = audioSample->audioSampleData.data(); + drflac_close(flac); +} + +static void OggDecoderWorker(std::shared_ptr audioSample, std::shared_ptr sampleFile) { + 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(), + }; + 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->audioSampleData.reserve(toRead); + do { + read = ov_read(&vf, dataBuff, 4096, 0, 2, 1, &bitStream); + memcpy(audioSample->audioSampleData.data() + pos, dataBuff, read); + pos += read; + } while (read != 0); + audioSample->sample.sampleAddr = audioSample->audioSampleData.data(); + ov_clear(&vf); +} namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSampleV2::ReadResource(std::shared_ptr file) { @@ -49,75 +175,117 @@ std::shared_ptr ResourceFactoryBinaryAudioSampleV2::ReadResourc return audioSample; } -} // namespace SOH -/* -in ResourceMgr_LoadAudioSample we used to have --------------- - if (cachedCustomSFs.find(path) != cachedCustomSFs.end()) - return cachedCustomSFs[path]; +std::shared_ptr ResourceFactoryXMLAudioSampleV0::ReadResource(std::shared_ptr file) { + if (!FileHasValidFormatAndReader(file)) { + return nullptr; + } - SoundFontSample* cSample = ReadCustomSample(path); + auto audioSample = std::make_shared(file->InitData); + auto child = std::get>(file->Reader)->FirstChildElement(); + std::shared_ptr initData = std::make_shared(); + const char* customFormatStr = child->Attribute("CustomFormat"); + memset(&audioSample->sample, 0, sizeof(audioSample->sample)); + audioSample->sample.codec = CodecStrToInt(child->Attribute("Codec")); + audioSample->sample.medium = ResourceFactoryXMLSoundFontV0::MediumStrToInt(child->Attribute("Medium")); + audioSample->sample.unk_bit25 = child->IntAttribute("Relocated"); + audioSample->sample.unk_bit26 = child->IntAttribute("bit26"); + audioSample->loop.start = child->IntAttribute("LoopStart"); + audioSample->loop.end = child->IntAttribute("LoopEnd"); + audioSample->loop.count = child->IntAttribute("LoopCount"); - if (cSample != nullptr) - return cSample; --------------- -before the rest of the standard sample reading, this is the ReadCustomSample code we used to have + // CODEC_ADPCM || CODEC_SMALL_ADPCM + memset(audioSample->loop.state, 0, sizeof(audioSample->loop.state)); + if (audioSample->sample.codec == 0 || audioSample->sample.codec == 3) { + tinyxml2::XMLElement* loopElement = child->FirstChildElement(); + size_t i = 0; + if (loopElement != nullptr) { + while (strcmp(loopElement->Name(), "LoopState") == 0) { + audioSample->loop.state[i] = loopElement->IntAttribute("Loop"); + loopElement = loopElement->NextSiblingElement(); + i++; + } + audioSample->loop.count = i; + i = 0; + while (loopElement != nullptr && strcmp(loopElement->Name(), "Books") == 0) { + audioSample->bookData.push_back(loopElement->IntAttribute("Book")); + loopElement = loopElement->NextSiblingElement(); + i++; + } + } + audioSample->book.npredictors = child->IntAttribute("Npredictors"); + audioSample->book.order = child->IntAttribute("Order"); -extern "C" SoundFontSample* ReadCustomSample(const char* path) { + audioSample->book.book = audioSample->bookData.data(); + audioSample->sample.book = &audioSample->book; + } + audioSample->sample.loop = &audioSample->loop; + size_t size = child->Int64Attribute("SampleSize"); + audioSample->sample.size = size; - if (!ExtensionCache.contains(path)) - return nullptr; + const char* path = child->Attribute("SamplePath"); + initData->Path = path; + initData->IsCustom = false; + initData->ByteOrder = Ship::Endianness::Native; + auto sampleFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path, initData); + 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); - 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; + drwav_get_length_in_pcm_frames(&wav, &numFrames); + + audioSample->tuning = (wav.sampleRate * wav.channels) / 32000.0f; + audioSample->audioSampleData.reserve(numFrames * wav.channels * 2); + + drwav_read_pcm_frames_s16(&wav, numFrames, (int16_t*)audioSample->audioSampleData.data()); + audioSample->sample.sampleAddr = audioSample->audioSampleData.data(); + 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); + 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->audioSampleData.resize(size); + // Can't use memcpy due to endianness issues. + for (uint32_t i = 0; i < size; i++) { + audioSample->audioSampleData[i] = sampleFile->Buffer.get()->data()[i]; } + audioSample->sample.sampleAddr = audioSample->audioSampleData.data(); - return nullptr; + return audioSample; } - -*/ +#include +uint8_t ResourceFactoryXMLAudioSampleV0::CodecStrToInt(const char* str) { + 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 { + assert(0); + } +} +} // namespace SOH diff --git a/soh/soh/resource/importer/AudioSampleFactory.h b/soh/soh/resource/importer/AudioSampleFactory.h index 372e8a31041..1d241e8478a 100644 --- a/soh/soh/resource/importer/AudioSampleFactory.h +++ b/soh/soh/resource/importer/AudioSampleFactory.h @@ -2,10 +2,20 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" namespace SOH { class ResourceFactoryBinaryAudioSampleV2 : public Ship::ResourceFactoryBinary { public: std::shared_ptr ReadResource(std::shared_ptr file) override; }; + +class ResourceFactoryXMLAudioSampleV0 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file) override; + + private: + uint8_t CodecStrToInt(const char* str); +}; + } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSequenceFactory.cpp b/soh/soh/resource/importer/AudioSequenceFactory.cpp index 35da7f798db..b190a6af74c 100644 --- a/soh/soh/resource/importer/AudioSequenceFactory.cpp +++ b/soh/soh/resource/importer/AudioSequenceFactory.cpp @@ -1,6 +1,10 @@ #include "soh/resource/importer/AudioSequenceFactory.h" #include "soh/resource/type/AudioSequence.h" -#include "spdlog/spdlog.h" +#include "BinaryWriter.h" +#include "StringHelper.h" +#include "libultraship/libultraship.h" +#include "BinaryWriter.h" +#include namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResource(std::shared_ptr file) { @@ -11,13 +15,13 @@ std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResou auto audioSequence = std::make_shared(file->InitData); auto reader = std::get>(file->Reader); - audioSequence->sequence.seqDataSize = reader->ReadInt32(); + audioSequence->sequence.seqDataSize = reader->ReadUInt32(); audioSequence->sequenceData.reserve(audioSequence->sequence.seqDataSize); for (uint32_t i = 0; i < audioSequence->sequence.seqDataSize; i++) { audioSequence->sequenceData.push_back(reader->ReadChar()); } audioSequence->sequence.seqData = audioSequence->sequenceData.data(); - + audioSequence->sequence.seqNumber = reader->ReadUByte(); audioSequence->sequence.medium = reader->ReadUByte(); audioSequence->sequence.cachePolicy = reader->ReadUByte(); @@ -32,4 +36,393 @@ std::shared_ptr ResourceFactoryBinaryAudioSequenceV2::ReadResou return audioSequence; } + +int8_t SOH::ResourceFactoryXMLAudioSequenceV0::MediumStrToInt(const char* str) { + if (!strcmp("Ram", str)) { + return 0; + } else if (!strcmp("Unk", str)) { + return 1; + } else if (!strcmp("Cart", str)) { + return 2; + } else if (!strcmp("Disk", str)) { + return 3; + // 4 is skipped + } else if (!strcmp("RamUnloaded", str)) { + return 5; + } else { + throw std::runtime_error( + StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); + } +} + +int8_t ResourceFactoryXMLAudioSequenceV0::CachePolicyToInt(const char* str) { + if (!strcmp("Temporary", str)) { + return 0; + } else if (!strcmp("Persistent", str)) { + return 1; + } else if (!strcmp("Either", str)) { + return 2; + } else if (!strcmp("Permanent", str)) { + return 3; + } else { + throw std::runtime_error(StringHelper::Sprintf( + "Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); + } +} + +template static void WriteInsnOneArg(Ship::BinaryWriter* writer, uint8_t opcode, T arg) { + static_assert(std::is_fundamental::value); + writer->Write(opcode); + writer->Write(arg); +} + +template +static void WriteInsnTwoArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2) { + static_assert(std::is_fundamental::value && std::is_fundamental::value); + writer->Write(opcode); + writer->Write(arg1); + writer->Write(arg2); +} + +template +static void WriteInsnThreeArg(Ship::BinaryWriter* writer, uint8_t opcode, T1 arg1, T2 arg2, T3 arg3) { + static_assert(std::is_fundamental::value && std::is_fundamental::value); + writer->Write(opcode); + writer->Write(arg1); + writer->Write(arg2); + writer->Write(arg3); +} + +static void WriteInsnNoArg(Ship::BinaryWriter* writer, uint8_t opcode) { + writer->Write(opcode); +} + +static void WriteLegato(Ship::BinaryWriter* writer) { + WriteInsnNoArg(writer, 0xC4); +} + +static void WriteNoLegato(Ship::BinaryWriter* writer) { + WriteInsnNoArg(writer, 0xC5); +} + +static void WriteMuteBhv(Ship::BinaryWriter* writer, uint8_t arg) { + WriteInsnOneArg(writer, 0xD3, arg); +} + +static void WriteMuteScale(Ship::BinaryWriter* writer, uint8_t arg) { + WriteInsnOneArg(writer, 0xD5, arg); +} + +static void WriteInitchan(Ship::BinaryWriter* writer, uint16_t channels) { + WriteInsnOneArg(writer, 0xD7, channels); +} + +static void WriteLdchan(Ship::BinaryWriter* writer, uint8_t channel, uint16_t offset) { + WriteInsnOneArg(writer, 0x90 | channel, offset); +} + +static void WriteVolSHeader(Ship::BinaryWriter* writer, uint8_t vol) { + WriteInsnOneArg(writer, 0xDB, vol); +} + +static void WriteVolCHeader(Ship::BinaryWriter* writer, uint8_t vol) { + WriteInsnOneArg(writer, 0xDF, vol); +} + +static void WriteTempo(Ship::BinaryWriter* writer, uint8_t tempo) { + WriteInsnOneArg(writer, 0xDD, tempo); +} + +static void WriteJump(Ship::BinaryWriter* writer, uint16_t offset) { + WriteInsnOneArg(writer, 0xFB, offset); +} + +static void WriteDisablecan(Ship::BinaryWriter* writer, uint16_t channels) { + WriteInsnOneArg(writer, 0xD6, channels); +} + +static void WriteNoshort(Ship::BinaryWriter* writer) { + WriteInsnNoArg(writer, 0xC4); +} + +static void WriteLdlayer(Ship::BinaryWriter* writer, uint8_t layer, uint16_t offset) { + WriteInsnOneArg(writer, 0x88 | layer, offset); +} + +static void WritePan(Ship::BinaryWriter* writer, uint8_t pan) { + WriteInsnOneArg(writer, 0xDD, pan); +} + +static void WriteBend(Ship::BinaryWriter* writer, uint8_t bend) { + WriteInsnOneArg(writer, 0xD3, bend); +} + +static void WriteInstrument(Ship::BinaryWriter* writer, uint8_t instrument) { + WriteInsnOneArg(writer, 0xC1, instrument); +} + +static void WriteTranspose(Ship::BinaryWriter* writer, int8_t transpose) { + WriteInsnOneArg(writer, 0xC2, transpose); +} + +static void WriteDelay(Ship::BinaryWriter* writer, uint16_t delay) { + if (delay > 0x7F) { + WriteInsnOneArg(writer, 0xFD, static_cast(delay | 0x8000)); + } else { + WriteInsnOneArg(writer, 0xFD, static_cast(delay)); + } +} + +template static void WriteLDelay(Ship::BinaryWriter* writer, T delay) { + WriteInsnOneArg(writer, 0xC0, delay); +} + +template static void WriteNotedv(Ship::BinaryWriter* writer, uint8_t note, T delay, uint8_t velocity) { + WriteInsnTwoArg(writer, note, delay, velocity); +} + +static void WriteNotedvg(Ship::BinaryWriter* writer, uint8_t note, uint16_t delay, uint8_t velocity, uint8_t gateTime) { + if (delay > 0x7F) { + WriteInsnThreeArg(writer, note, static_cast(delay | 0x8000), velocity, gateTime); + } else { + WriteInsnThreeArg(writer, note, static_cast(delay), velocity, gateTime); + } +} + +static void WriteMonoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) { + uint16_t channelStart; + uint16_t channelPlaceholderOff; + uint16_t loopPoint; + uint16_t layerPlaceholderOff; + uint16_t layerStart; + if (looped) { + delay = 0x7FFF; + } + // Write seq header + + // These two values are always the same in OOT and MM + WriteMuteBhv(writer, 0x20); + WriteMuteScale(writer, 0x32); + + // We only have one channel + WriteInitchan(writer, 0b11); + // Store the current position so we can write the address of the channel when we are ready. + channelPlaceholderOff = writer->GetBaseAddress(); + // Store the current position so we can loop here after the song ends. + loopPoint = writer->GetBaseAddress(); + WriteLdchan(writer, 0, 0); // Fill in the actual address later + + WriteVolSHeader(writer, 127); // Max volume + WriteTempo(writer, tempo); + + WriteDelay(writer, delay); + if (looped) { + WriteJump(writer, loopPoint); + } + WriteDisablecan(writer, 0b11); + writer->Write(static_cast(0xFF)); + + // Fill in the ldchan from before + channelStart = writer->GetBaseAddress(); + writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdchan(writer, 0, channelStart); + writer->Seek(channelStart, Ship::SeekOffsetType::Start); + + // Channel header + layerPlaceholderOff = writer->GetBaseAddress(); + WriteNoshort(writer); + WriteLdlayer(writer, 0, 0); + WritePan(writer, 64); + WriteVolCHeader(writer, 127); // Max volume + WriteBend(writer, 0); + WriteInstrument(writer, 0); + WriteDelay(writer, delay); + writer->Write(static_cast(0xFF)); + + layerStart = writer->GetBaseAddress(); + writer->Seek(layerPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdlayer(writer, 0, layerStart); + writer->Seek(layerStart, Ship::SeekOffsetType::Start); + + // Note layer + WriteLegato(writer); + WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); + writer->Write(static_cast(0xFF)); +} + +static void WriteStereoSingleSeq(Ship::BinaryWriter* writer, uint16_t delay, uint8_t tempo, bool looped) { + uint16_t lChannelStart; + uint16_t rChannelStart; + uint16_t channelPlaceholderOff; + uint16_t loopPoint; + uint16_t lLayerPlaceholderOff; + uint16_t rLayerPlaceholderOff; + uint16_t lLayerOffset; + uint16_t rLayerOffset; + + uint16_t layerStart; + // Write seq header + if (looped) { + delay = 0x7FFF; + } + // These two values are always the same in OOT and MM + WriteMuteBhv(writer, 0x20); + WriteMuteScale(writer, 0x32); + + // We only have one channel + WriteInitchan(writer, 0b11); + // Store the current position so we can write the address of the channel when we are ready. + channelPlaceholderOff = writer->GetBaseAddress(); + // Store the current position so we can loop here after the song ends. + loopPoint = writer->GetBaseAddress(); + // Left note channel + WriteLdchan(writer, 0, 0); // Fill in the actual address later + // Right note channel + WriteLdchan(writer, 1, 0); // Fill in the actual address later + + WriteVolSHeader(writer, 127); // Max volume + WriteTempo(writer, tempo); + + WriteDelay(writer, delay); + if (looped) { + WriteJump(writer, loopPoint); + } + WriteDisablecan(writer, 0b11); + writer->Write(static_cast(0xFF)); + + lChannelStart = writer->GetBaseAddress(); + // Left Channel header + WriteNoshort(writer); + lLayerPlaceholderOff = writer->GetBaseAddress(); + WriteLdlayer(writer, 0, 0); + WritePan(writer, 0); + WriteVolCHeader(writer, 127); // Max volume + WriteBend(writer, 0); + WriteInstrument(writer, 0); + WriteDelay(writer, delay); + writer->Write(static_cast(0xFF)); + + rChannelStart = writer->GetBaseAddress(); + // Right Channel header + WriteNoshort(writer); + rLayerPlaceholderOff = writer->GetBaseAddress(); + WriteLdlayer(writer, 1, 0); + WritePan(writer, 127); + WriteVolCHeader(writer, 127); // Max volume + WriteBend(writer, 0); + WriteInstrument(writer, 1); + WriteDelay(writer, delay); + writer->Write(static_cast(0xFF)); + uint16_t placeHolder = writer->GetBaseAddress(); + writer->Seek(channelPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdchan(writer, 0, lChannelStart); + WriteLdchan(writer, 1, rChannelStart); + writer->Seek(placeHolder, Ship::SeekOffsetType::Start); + + // Left Note layer + lLayerOffset = writer->GetBaseAddress(); + WriteLegato(writer); + WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); + writer->Write(static_cast(0xFF)); + + // Right Note layer + rLayerOffset = writer->GetBaseAddress(); + WriteLegato(writer); + WriteNotedvg(writer, 39, 0x7FFF - 1, static_cast(0x7F), static_cast(1)); + writer->Write(static_cast(0xFF)); + + writer->Seek(lLayerPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdlayer(writer, 0, lLayerOffset); + writer->Seek(rLayerPlaceholderOff, Ship::SeekOffsetType::Start); + WriteLdlayer(writer, 1, rLayerOffset); +} + +std::shared_ptr ResourceFactoryXMLAudioSequenceV0::ReadResource(std::shared_ptr file) { + if (!FileHasValidFormatAndReader(file)) { + return nullptr; + } + + auto sequence = std::make_shared(file->InitData); + auto child = std::get>(file->Reader)->FirstChildElement(); + unsigned int i = 0; + std::shared_ptr initData = std::make_shared(); + + sequence->sequence.medium = MediumStrToInt(child->Attribute("Medium")); + sequence->sequence.cachePolicy = CachePolicyToInt(child->Attribute("CachePolicy")); + sequence->sequence.seqDataSize = child->IntAttribute("Size"); + sequence->sequence.seqNumber = child->IntAttribute("Index"); + bool streamed = child->BoolAttribute("Streamed"); + + memset(sequence->sequence.fonts, 0, sizeof(sequence->sequence.fonts)); + + tinyxml2::XMLElement* fontsElement = child->FirstChildElement(); + tinyxml2::XMLElement* fontElement = fontsElement->FirstChildElement(); + while (fontElement != nullptr) { + sequence->sequence.fonts[i] = fontElement->IntAttribute("FontIdx"); + fontElement = fontElement->NextSiblingElement(); + i++; + } + sequence->sequence.numFonts = i; + + const char* path = child->Attribute("Path"); + std::shared_ptr seqFile; + if (path != nullptr) { + initData->Path = path; + initData->IsCustom = false; + initData->ByteOrder = Ship::Endianness::Native; + seqFile = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile(path, initData); + } + + if (!streamed) { + sequence->sequenceData = *seqFile->Buffer.get(); + sequence->sequence.seqData = sequence->sequenceData.data(); + } 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->sequenceData = *seqFile->Buffer.get(); + sequence->sequence.seqData = sequence->sequenceData.data(); + sequence->sequence.seqDataSize = 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); + // Placeholder off is the offset of the instruction to be replaced. The second variable is the target adress + // of what we need to load of jump to + uint16_t loopPoint; + uint16_t channelPlaceholderOff, channelStart; + uint16_t layerPlaceholderOff, layerStart; + + // 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->sequenceData = writer.ToVector(); + sequence->sequence.seqData = sequence->sequenceData.data(); + sequence->sequence.seqDataSize = writer.ToVector().size(); + } + } + + return sequence; +} } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSequenceFactory.h b/soh/soh/resource/importer/AudioSequenceFactory.h index 35fd1ebef54..58d5de975eb 100644 --- a/soh/soh/resource/importer/AudioSequenceFactory.h +++ b/soh/soh/resource/importer/AudioSequenceFactory.h @@ -2,10 +2,21 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" +#include "BinaryWriter.h" namespace SOH { class ResourceFactoryBinaryAudioSequenceV2 : public Ship::ResourceFactoryBinary { public: std::shared_ptr ReadResource(std::shared_ptr file) override; }; + +class ResourceFactoryXMLAudioSequenceV0 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file) override; + + private: + int8_t MediumStrToInt(const char* str); + int8_t CachePolicyToInt(const char* str); +}; } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.cpp b/soh/soh/resource/importer/AudioSoundFontFactory.cpp index 534a7914c85..63715bb65b9 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.cpp +++ b/soh/soh/resource/importer/AudioSoundFontFactory.cpp @@ -2,6 +2,8 @@ #include "soh/resource/type/AudioSoundFont.h" #include "spdlog/spdlog.h" #include "libultraship/libultraship.h" +#include "StringHelper.h" +#include "z64audio.h" namespace SOH { std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr file) { @@ -15,7 +17,7 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso audioSoundFont->soundFont.fntIndex = reader->ReadInt32(); audioSoundFont->medium = reader->ReadInt8(); audioSoundFont->cachePolicy = reader->ReadInt8(); - + audioSoundFont->data1 = reader->ReadUInt16(); audioSoundFont->soundFont.sampleBankId1 = audioSoundFont->data1 >> 8; audioSoundFont->soundFont.sampleBankId2 = audioSoundFont->data1 & 0xFF; @@ -25,128 +27,114 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso uint32_t drumCount = reader->ReadUInt32(); audioSoundFont->soundFont.numDrums = drumCount; - + uint32_t instrumentCount = reader->ReadUInt32(); audioSoundFont->soundFont.numInstruments = instrumentCount; - + uint32_t soundEffectCount = reader->ReadUInt32(); audioSoundFont->soundFont.numSfx = soundEffectCount; // πŸ₯ DRUMS πŸ₯ - audioSoundFont->drums.reserve(audioSoundFont->soundFont.numDrums); audioSoundFont->drumAddresses.reserve(audioSoundFont->soundFont.numDrums); for (uint32_t i = 0; i < audioSoundFont->soundFont.numDrums; i++) { - Drum drum; - drum.releaseRate = reader->ReadUByte(); - drum.pan = reader->ReadUByte(); - drum.loaded = reader->ReadUByte(); - drum.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + Drum* drum = new Drum; + drum->releaseRate = reader->ReadUByte(); + drum->pan = reader->ReadUByte(); + drum->loaded = reader->ReadUByte(); + drum->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName uint32_t envelopeCount = reader->ReadUInt32(); - audioSoundFont->drumEnvelopeCounts.push_back(envelopeCount); - std::vector drumEnvelopes; - drumEnvelopes.reserve(audioSoundFont->drumEnvelopeCounts[i]); - for (uint32_t j = 0; j < audioSoundFont->drumEnvelopeCounts.back(); j++) { - AdsrEnvelope env; - + drum->envelope = new AdsrEnvelope[envelopeCount]; + for (uint32_t j = 0; j < envelopeCount; j++) { int16_t delay = reader->ReadInt16(); int16_t arg = reader->ReadInt16(); - env.delay = BE16SWAP(delay); - env.arg = BE16SWAP(arg); - - drumEnvelopes.push_back(env); + drum->envelope[j].delay = BE16SWAP(delay); + drum->envelope[j].arg = BE16SWAP(arg); } - audioSoundFont->drumEnvelopeArrays.push_back(drumEnvelopes); - drum.envelope = audioSoundFont->drumEnvelopeArrays.back().data(); bool hasSample = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - drum.sound.tuning = reader->ReadFloat(); + drum->sound.tuning = reader->ReadFloat(); if (sampleFileName.empty()) { - drum.sound.sample = nullptr; + drum->sound.sample = nullptr; } else { auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + drum->sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } - audioSoundFont->drums.push_back(drum); - audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back()); + audioSoundFont->drumAddresses.push_back(drum); } audioSoundFont->soundFont.drums = audioSoundFont->drumAddresses.data(); // 🎺🎻🎷🎸🎹 INSTRUMENTS 🎹🎸🎷🎻🎺 - audioSoundFont->instruments.reserve(audioSoundFont->soundFont.numInstruments); for (uint32_t i = 0; i < audioSoundFont->soundFont.numInstruments; i++) { - Instrument instrument; + Instrument* instrument = new Instrument; uint8_t isValidEntry = reader->ReadUByte(); - instrument.loaded = reader->ReadUByte(); - instrument.loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + instrument->loaded = reader->ReadUByte(); + instrument->loaded = 0; // this was always getting set to zero in ResourceMgr_LoadAudioSoundFontByName - instrument.normalRangeLo = reader->ReadUByte(); - instrument.normalRangeHi = reader->ReadUByte(); - instrument.releaseRate = reader->ReadUByte(); + instrument->normalRangeLo = reader->ReadUByte(); + instrument->normalRangeHi = reader->ReadUByte(); + instrument->releaseRate = reader->ReadUByte(); uint32_t envelopeCount = reader->ReadInt32(); - audioSoundFont->instrumentEnvelopeCounts.push_back(envelopeCount); - std::vector instrumentEnvelopes; - for (uint32_t j = 0; j < audioSoundFont->instrumentEnvelopeCounts.back(); j++) { - AdsrEnvelope env; + instrument->envelope = new AdsrEnvelope[envelopeCount]; + for (uint32_t j = 0; j < envelopeCount; j++) { int16_t delay = reader->ReadInt16(); int16_t arg = reader->ReadInt16(); - env.delay = BE16SWAP(delay); - env.arg = BE16SWAP(arg); - - instrumentEnvelopes.push_back(env); + instrument->envelope[j].delay = BE16SWAP(delay); + instrument->envelope[j].arg = BE16SWAP(arg); } - audioSoundFont->instrumentEnvelopeArrays.push_back(instrumentEnvelopes); - instrument.envelope = audioSoundFont->instrumentEnvelopeArrays.back().data(); bool hasLowNoteSoundFontEntry = reader->ReadInt8(); if (hasLowNoteSoundFontEntry) { bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.lowNotesSound.tuning = reader->ReadFloat(); + instrument->lowNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.lowNotesSound.sample = nullptr; - instrument.lowNotesSound.tuning = 0; + instrument->lowNotesSound.sample = nullptr; + instrument->lowNotesSound.tuning = 0; } bool hasNormalNoteSoundFontEntry = reader->ReadInt8(); if (hasNormalNoteSoundFontEntry) { + // BENTODO remove in V3 bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.normalNotesSound.tuning = reader->ReadFloat(); + instrument->normalNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.normalNotesSound.sample = nullptr; - instrument.normalNotesSound.tuning = 0; + instrument->normalNotesSound.sample = nullptr; + instrument->normalNotesSound.tuning = 0; } bool hasHighNoteSoundFontEntry = reader->ReadInt8(); if (hasHighNoteSoundFontEntry) { bool hasSampleRef = reader->ReadInt8(); std::string sampleFileName = reader->ReadString(); - instrument.highNotesSound.tuning = reader->ReadFloat(); + instrument->highNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); - instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + instrument->highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } else { - instrument.highNotesSound.sample = nullptr; - instrument.highNotesSound.tuning = 0; + instrument->highNotesSound.sample = nullptr; + instrument->highNotesSound.tuning = 0; } - - audioSoundFont->instruments.push_back(instrument); - audioSoundFont->instrumentAddresses.push_back(isValidEntry ? - &audioSoundFont->instruments.back() : - nullptr); + if (isValidEntry) { + audioSoundFont->instrumentAddresses.push_back(instrument); + } else { + delete[] instrument->envelope; + delete instrument; + audioSoundFont->instrumentAddresses.push_back(nullptr); + } } audioSoundFont->soundFont.instruments = audioSoundFont->instrumentAddresses.data(); @@ -163,11 +151,264 @@ std::shared_ptr ResourceFactoryBinaryAudioSoundFontV2::ReadReso auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); soundEffect.sample = static_cast(res ? res->GetRawPointer() : nullptr); } - + audioSoundFont->soundEffects.push_back(soundEffect); } audioSoundFont->soundFont.soundEffects = audioSoundFont->soundEffects.data(); return audioSoundFont; } + +int8_t ResourceFactoryXMLSoundFontV0::MediumStrToInt(const char* str) { + 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 { + throw std::runtime_error( + StringHelper::Sprintf("Bad medium value. Got %s, expected Ram, Unk, Cart, or Disk.", str)); + } +} + +int8_t ResourceFactoryXMLSoundFontV0::CachePolicyToInt(const char* str) { + 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 { + throw std::runtime_error(StringHelper::Sprintf( + "Bad cache policy value. Got %s, expected Temporary, Persistent, Either, or Permanent.", str)); + } +} + +void ResourceFactoryXMLSoundFontV0::ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = (tinyxml2::XMLElement*)element->FirstChildElement(); + // No drums + if (element == nullptr) { + soundFont->soundFont.drums = nullptr; + soundFont->soundFont.numDrums = 0; + return; + } + + do { + Drum* drum = new Drum; + std::vector envelopes; + drum->releaseRate = element->IntAttribute("ReleaseRate"); + drum->pan = element->IntAttribute("Pan"); + drum->loaded = element->IntAttribute("Loaded"); + drum->sound.tuning = element->FloatAttribute("Tuning"); + const char* sampleStr = element->Attribute("SampleRef"); + + if (sampleStr != nullptr && sampleStr[0] != 0) { + auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr); + drum->sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } else { + drum->sound.sample = nullptr; + } + + element = (tinyxml2::XMLElement*)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); + 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 SOH::ResourceFactoryXMLSoundFontV0::ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + element = element->FirstChildElement(); + do { + Instrument* instrument = new Instrument; + memset(instrument, 0, sizeof(Instrument)); + unsigned int envCount = 0; + std::vector envelopes; + + int isValid = element->BoolAttribute("IsValid"); + instrument->loaded = element->IntAttribute("Loaded"); + instrument->normalRangeLo = element->IntAttribute("NormalRangeLo"); + instrument->normalRangeHi = element->IntAttribute("NormalRangeHi"); + instrument->releaseRate = element->IntAttribute("ReleaseRate"); + tinyxml2::XMLElement* instrumentElement = element->FirstChildElement(); + tinyxml2::XMLElement* instrumentElementCopy = instrumentElement; + + if (instrumentElement != nullptr && !strcmp(instrumentElement->Name(), "Envelopes")) { + envelopes = ParseEnvelopes(soundFont, instrumentElement, &envCount); + instrument->envelope = new AdsrEnvelope[envelopes.size()]; + memcpy(instrument->envelope, envelopes.data(), envelopes.size() * sizeof(AdsrEnvelope)); + instrumentElement = instrumentElement->NextSiblingElement(); + } + + if (instrumentElement != nullptr && !strcmp("LowNotesSound", instrumentElement->Name())) { + instrument->lowNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + std::shared_ptr res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->lowNotesSound.tuning = res->tuning; + } + instrument->lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + + if (instrumentElement != nullptr && !strcmp("NormalNotesSound", instrumentElement->Name())) { + instrument->normalNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + std::shared_ptr res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->normalNotesSound.tuning = res->tuning; + } + instrument->normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + + if (instrumentElement != nullptr && !strcmp("HighNotesSound", instrumentElement->Name())) { + instrument->highNotesSound.tuning = instrumentElement->FloatAttribute("Tuning"); + const char* sampleStr = instrumentElement->Attribute("SampleRef"); + if (sampleStr != nullptr && sampleStr[0] != 0) { + std::shared_ptr res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + instrument->highNotesSound.tuning = res->tuning; + } + instrument->highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + instrumentElement = instrumentElement->NextSiblingElement(); + } + + 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 SOH::ResourceFactoryXMLSoundFontV0::ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element) { + size_t count = element->IntAttribute("Count"); + + element = (tinyxml2::XMLElement*)element->FirstChildElement(); + + while (element != nullptr) { + SoundFontSound sound = { 0 }; + 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 != nullptr && sampleStr[0] != 0) { + auto res = static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleStr)); + if (res->tuning != -1.0f) { + sound.tuning = res->tuning; + } + sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + skip: + element = element->NextSiblingElement(); + soundFont->soundEffects.push_back(sound); + } + soundFont->soundFont.soundEffects = soundFont->soundEffects.data(); + soundFont->soundFont.numSfx = soundFont->soundEffects.size(); +} + +std::vector SOH::ResourceFactoryXMLSoundFontV0::ParseEnvelopes(AudioSoundFont* soundFont, + tinyxml2::XMLElement* element, + unsigned int* count) { + std::vector envelopes; + unsigned int total = 0; + element = element->FirstChildElement("Envelope"); + while (element != nullptr) { + AdsrEnvelope env = { + .delay = (s16)element->IntAttribute("Delay"), + .arg = (s16)element->IntAttribute("Arg"), + }; + env.delay = BSWAP16(env.delay); + env.arg = BSWAP16(env.arg); + envelopes.emplace_back(env); + element = element->NextSiblingElement("Envelope"); + total++; + } + *count = total; + return envelopes; +} + +std::shared_ptr ResourceFactoryXMLSoundFontV0::ReadResource(std::shared_ptr file) { + if (!FileHasValidFormatAndReader(file)) { + return nullptr; + } + auto audioSoundFont = std::make_shared(file->InitData); + auto child = std::get>(file->Reader)->FirstChildElement(); + // Header data + memset(&audioSoundFont->soundFont, 0, sizeof(audioSoundFont->soundFont)); + audioSoundFont->soundFont.fntIndex = child->IntAttribute("Num", 0); + + const char* mediumStr = child->Attribute("Medium"); + audioSoundFont->medium = MediumStrToInt(mediumStr); + + const char* cachePolicyStr = child->Attribute("CachePolicy"); + audioSoundFont->cachePolicy = CachePolicyToInt(cachePolicyStr); + if (audioSoundFont->soundFont.fntIndex == 1) { + int bp = 1; + } + + audioSoundFont->data1 = child->IntAttribute("Data1"); + audioSoundFont->data2 = child->IntAttribute("Data2"); + audioSoundFont->data3 = child->IntAttribute("Data3"); + + audioSoundFont->soundFont.sampleBankId1 = audioSoundFont->data1 >> 8; + audioSoundFont->soundFont.sampleBankId2 = audioSoundFont->data1 & 0xFF; + + child = (tinyxml2::XMLElement*)child->FirstChildElement(); + + while (child != nullptr) { + const char* name = child->Name(); + + if (!strcmp(name, "Drums")) { + ParseDrums(audioSoundFont.get(), child); + } else if (!strcmp(name, "Instruments")) { + ParseInstruments(audioSoundFont.get(), child); + } else if (!strcmp(name, "SfxTable")) { + ParseSfxTable(audioSoundFont.get(), child); + } + child = child->NextSiblingElement(); + } + return audioSoundFont; +} + } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.h b/soh/soh/resource/importer/AudioSoundFontFactory.h index a80b8fe9726..c5f2a93bf43 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.h +++ b/soh/soh/resource/importer/AudioSoundFontFactory.h @@ -2,10 +2,26 @@ #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 ReadResource(std::shared_ptr file) override; }; + +class ResourceFactoryXMLSoundFontV0 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file) override; + static int8_t MediumStrToInt(const char* str); + static int8_t CachePolicyToInt(const char* str); + + private: + void ParseDrums(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + void ParseInstruments(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + void ParseSfxTable(AudioSoundFont* soundFont, tinyxml2::XMLElement* element); + std::vector ParseEnvelopes(AudioSoundFont* soundFont, tinyxml2::XMLElement* element, + unsigned int* count); +}; } // namespace SOH diff --git a/soh/soh/resource/type/AudioSample.h b/soh/soh/resource/type/AudioSample.h index f3765909128..e47ea58325e 100644 --- a/soh/soh/resource/type/AudioSample.h +++ b/soh/soh/resource/type/AudioSample.h @@ -6,56 +6,56 @@ #include namespace SOH { - typedef struct { - /* 0x00 */ uintptr_t start; - /* 0x04 */ uintptr_t end; - /* 0x08 */ u32 count; - /* 0x0C */ char unk_0C[0x4]; - /* 0x10 */ s16 state[16]; // only exists if count != 0. 8-byte aligned - } AdpcmLoop; // size = 0x30 (or 0x10) - - typedef struct { - /* 0x00 */ 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; - }; - u32 asU32; +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 */ 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 }; - - /* 0x04 */ u8* sampleAddr; - /* 0x08 */ AdpcmLoop* loop; - /* 0x0C */ AdpcmBook* book; - u32 sampleRateMagicValue; // For wav samples only... - s32 sampleRate; // For wav samples only... - } Sample; // size = 0x10 - - class AudioSample : public Ship::Resource { - public: - using Resource::Resource; - - AudioSample() : Resource(std::shared_ptr()) {} - - Sample* GetPointer(); - size_t GetPointerSize(); - - Sample sample; - std::vector audioSampleData; - - AdpcmLoop loop; - uint32_t loopStateCount; - - AdpcmBook book; - uint32_t bookDataCount; - std::vector bookData; + u32 asU32; }; -}; // namespace LUS + /* 0x01 */ u32 size; + /* 0x04 */ u8* sampleAddr; + /* 0x08 */ AdpcmLoop* loop; + /* 0x0C */ AdpcmBook* book; +} Sample; // size = 0x10 + +class AudioSample : public Ship::Resource { + public: + using Resource::Resource; + + AudioSample() : Resource(std::shared_ptr()) { + } + + Sample* GetPointer(); + size_t GetPointerSize(); + + Sample sample; + std::vector audioSampleData; + + AdpcmLoop loop; + uint32_t loopStateCount; + + AdpcmBook book; + uint32_t bookDataCount; + std::vector bookData; + // Only applies to streamed audio + float tuning = -1.0f; +}; +}; // namespace SOH diff --git a/soh/soh/resource/type/AudioSequence.h b/soh/soh/resource/type/AudioSequence.h index d7a8820a398..883dfd79cbd 100644 --- a/soh/soh/resource/type/AudioSequence.h +++ b/soh/soh/resource/type/AudioSequence.h @@ -9,11 +9,11 @@ namespace SOH { typedef struct { char* seqData; - int32_t seqDataSize; + uint32_t seqDataSize; uint16_t seqNumber; uint8_t medium; uint8_t cachePolicy; - int32_t numFonts; + uint32_t numFonts; uint8_t fonts[16]; } Sequence; diff --git a/soh/soh/resource/type/AudioSoundFont.cpp b/soh/soh/resource/type/AudioSoundFont.cpp index 12218cb648b..7f323ddf480 100644 --- a/soh/soh/resource/type/AudioSoundFont.cpp +++ b/soh/soh/resource/type/AudioSoundFont.cpp @@ -1,6 +1,23 @@ #include "AudioSoundFont.h" namespace SOH { + +AudioSoundFont::~AudioSoundFont() { + for (auto i : instrumentAddresses) { + if (i != nullptr) { + delete[] i->envelope; + delete i; + } + } + + for (auto d : drumAddresses) { + if (d != nullptr) { + delete[] d->envelope; + delete d; + } + } +} + SoundFont* AudioSoundFont::GetPointer() { return &soundFont; } diff --git a/soh/soh/resource/type/AudioSoundFont.h b/soh/soh/resource/type/AudioSoundFont.h index 12d66b2d244..be4a86c21b9 100644 --- a/soh/soh/resource/type/AudioSoundFont.h +++ b/soh/soh/resource/type/AudioSoundFont.h @@ -17,7 +17,7 @@ typedef struct { /* 0x00 */ Sample* sample; /* 0x04 */ union { u32 tuningAsU32; - f32 tuning;// frequency scale factor + f32 tuning; // frequency scale factor }; } SoundFontSound; // size = 0x8 @@ -53,10 +53,12 @@ typedef struct { } SoundFont; // size = 0x14 class AudioSoundFont : public Ship::Resource { -public: + public: using Resource::Resource; - AudioSoundFont() : Resource(std::shared_ptr()) {} + AudioSoundFont() : Resource(std::shared_ptr()) { + } + ~AudioSoundFont(); SoundFont* GetPointer(); size_t GetPointerSize(); @@ -67,18 +69,15 @@ class AudioSoundFont : public Ship::Resource { uint16_t data2; uint16_t data3; - std::vector drums; std::vector drumAddresses; std::vector drumEnvelopeCounts; std::vector> drumEnvelopeArrays; - std::vector instruments; std::vector instrumentAddresses; - std::vector instrumentEnvelopeCounts; std::vector> instrumentEnvelopeArrays; std::vector soundEffects; SoundFont soundFont; }; -}; // namespace LUS +}; // namespace SOH diff --git a/soh/src/code/audio_heap.c b/soh/src/code/audio_heap.c index 8d0e97fa373..5e51b4c7873 100644 --- a/soh/src/code/audio_heap.c +++ b/soh/src/code/audio_heap.c @@ -41,7 +41,7 @@ void func_800DDE3C(void) { void AudioHeap_ResetLoadStatus(void) { s32 i; - for (i = 0; i < 0x30; i++) { + for (i = 0; i < fontMapSize; i++) { if (gAudioContext.fontLoadStatus[i] != 5) { gAudioContext.fontLoadStatus[i] = 0; } @@ -940,7 +940,7 @@ void AudioHeap_Init(void) { reverb->sample.sampleAddr = (u8*)reverb->leftRingBuf; reverb->loop.start = 0; reverb->loop.count = 1; - reverb->loop.end = reverb->windowSize; + reverb->loop.loopEnd = reverb->windowSize; if (reverb->downsampleRate != 1) { reverb->unk_0E = 0x8000 / reverb->downsampleRate; diff --git a/soh/src/code/audio_load.c b/soh/src/code/audio_load.c index cb7ba60bb88..4139acfe4f3 100644 --- a/soh/src/code/audio_load.c +++ b/soh/src/code/audio_load.c @@ -8,6 +8,11 @@ #include "soh/Enhancements/audio/AudioCollection.h" #include "soh/Enhancements/audio/AudioEditor.h" +// Windows deprecated the use of `strdup` it uses _strdup. Linux/Unix doesn't have _strdup. +#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)) #define ASYNC_ID(v) ((u8)(v >> 8)) @@ -80,9 +85,10 @@ s32 gAudioContextInitalized = false; char** sequenceMap; size_t sequenceMapSize; +size_t fontMapSize; // A map of authentic sequence IDs to their cache policies, for use with sequence swapping. u8 seqCachePolicyMap[MAX_AUTHENTIC_SEQID]; -char* fontMap[256]; +char** fontMap; uintptr_t fontStart; uint32_t fontOffsets[8192]; @@ -706,7 +712,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; @@ -767,7 +773,7 @@ uintptr_t AudioLoad_SyncLoad(u32 tableType, u32 id, s32* didAllocate) { } else if (tableType == FONT_TABLE) { - fnt = ResourceMgr_LoadAudioSoundFont(fontMap[id]); + fnt = ResourceMgr_LoadAudioSoundFontByName(fontMap[id]); size = sizeof(SoundFont); medium = 2; cachePolicy = 0; @@ -910,7 +916,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; @@ -1353,19 +1359,39 @@ void AudioLoad_Init(void* heap, size_t heapSize) { sequenceMap = malloc(sequenceMapSize * sizeof(char*)); gAudioContext.seqLoadStatus = malloc(sequenceMapSize * sizeof(char*)); - for (size_t i = 0; i < seqListSize; i++) - { + 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); + // SOH [Port] [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); + fontMapSize = customFntListSize + fntListSize; + fontMap = calloc(customFntListSize + fntListSize, sizeof(char*)); + 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); + int startingSeqNum = MAX_AUTHENTIC_SEQID; // 109 is the highest vanilla sequence qsort(customSeqList, customSeqListSize, sizeof(char*), strcmp_sort); @@ -1377,40 +1403,49 @@ 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]); + AudioCollection_AddToCollection(customSeqList[j], seqNum); - sequenceMap[sDat.seqNumber] = str; + sDat->seqNumber = 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); - gAudioContext.soundFonts = AudioHeap_Alloc(&gAudioContext.audioInitPool, numFonts * sizeof(SoundFont)); + numFonts = fntListSize; + gAudioContext.soundFonts = AudioHeap_Alloc(&gAudioContext.audioInitPool, numFonts * sizeof(SoundFont)); if (temp_v0_3 = AudioHeap_Alloc(&gAudioContext.audioInitPool, D_8014A6C4.permanentPoolSize), temp_v0_3 == NULL) { @@ -2079,7 +2114,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; @@ -2209,7 +2244,7 @@ void AudioLoad_LoadPermanentSamples(void) { fontId = AudioLoad_GetRealTableIndex(FONT_TABLE, gAudioContext.permanentCache[i].id); //fontId = gAudioContext.permanentCache[i].id; - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); + SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]); relocInfo.sampleBankId1 = sf->sampleBankId1; relocInfo.sampleBankId2 = sf->sampleBankId2; diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c index 39be0c3a75f..bc236063e95 100644 --- a/soh/src/code/audio_playback.c +++ b/soh/src/code/audio_playback.c @@ -292,13 +292,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; @@ -335,7 +328,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; @@ -363,7 +356,7 @@ Drum* Audio_GetDrum(s32 fontId, s32 drumId) { } - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); + SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]); if (drumId < sf->numDrums) { drum = sf->drums[drumId]; } @@ -387,7 +380,7 @@ SoundFontSound* Audio_GetSfx(s32 fontId, s32 sfxId) { return NULL; } - SoundFont* sf = ResourceMgr_LoadAudioSoundFont(fontMap[fontId]); + SoundFont* sf = ResourceMgr_LoadAudioSoundFontByName(fontMap[fontId]); if (sfxId < sf->numSfx) { sfx = &sf->soundEffects[sfxId]; } diff --git a/soh/src/code/audio_seqplayer.c b/soh/src/code/audio_seqplayer.c index 8d484575db7..d025c164192 100644 --- a/soh/src/code/audio_seqplayer.c +++ b/soh/src/code/audio_seqplayer.c @@ -788,7 +788,7 @@ s32 AudioSeq_SeqLayerProcessScriptStep4(SequenceLayer* layer, s32 cmd) { layer->freqScale *= layer->unk_34; if (layer->delay == 0) { if (layer->sound != NULL) { - time = (f32)layer->sound->sample->loop->end; + time = (f32)layer->sound->sample->loop->loopEnd; } else { time = 0.0f; } diff --git a/soh/src/code/audio_synthesis.c b/soh/src/code/audio_synthesis.c index 7044a48c464..ad07bd6b594 100644 --- a/soh/src/code/audio_synthesis.c +++ b/soh/src/code/audio_synthesis.c @@ -761,7 +761,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS audioFontSample = noteSubEu->sound.soundFontSound->sample; loopInfo = audioFontSample->loop; - loopEndPos = loopInfo->end; + loopEndPos = loopInfo->loopEnd; sampleAddr = audioFontSample->sampleAddr; resampledTempLen = 0; @@ -854,14 +854,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)); - + // SoH [Port] [Custom Audio] This was not finished originally. + AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, (samplesLenAdjusted + 16) * 2); flags = A_CONTINUE; skipBytes = 0; - nSamplesProcessed = samplesLenAdjusted; - s5 = samplesLenAdjusted; + nSamplesProcessed += samplesLenAdjusted; + phi_s4 = samplesLenAdjusted; + size_t bytesToRead; + + if (((synthState->samplePosInt * 2) + (samplesLenAdjusted + 16) * 2) < + audioFontSample->size) { + bytesToRead = (samplesLenAdjusted + 16) * 2; + } else { + bytesToRead = audioFontSample->size - (synthState->samplePosInt * 2); + } + // SoH [Port] [Custom audio] + // TLDR samples are loaded async and might be null the first time they are played. + // See note in AudioSampleFactory.cpp + if (sampleAddr != NULL) { + aLoadBuffer(cmd++, sampleAddr + (synthState->samplePosInt * 2), DMEM_UNCOMPRESSED_NOTE, + bytesToRead); + } + goto skip; case CODEC_REVERB: break; @@ -895,7 +909,7 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS } if (synthState->restart) { - aSetLoop(cmd++, audioFontSample->loop->state); + aSetLoop(cmd++, audioFontSample->loop->predictorState); flags = A_LOOP; synthState->restart = false; } diff --git a/soh/src/code/code_800E4FE0.c b/soh/src/code/code_800E4FE0.c index 6eb3ae3695b..e3d590eac19 100644 --- a/soh/src/code/code_800E4FE0.c +++ b/soh/src/code/code_800E4FE0.c @@ -794,7 +794,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; }