#include "OoTAudioFontWriter.h" #include "spdlog/spdlog.h" #include "Companion.h" #include namespace OoT { std::string AudioFontWriter::GetSampleRef(int bankIndex, uint32_t sampleAddr, uint32_t baseOffset, FontWriteContext& ctx) { if (sampleAddr + 16 > ctx.audioBank.Size()) return ""; uint32_t samplePtr = ctx.audioBank.ReadU32(sampleAddr); if (samplePtr == 0) return ""; samplePtr += baseOffset; if (samplePtr + 4 > ctx.audioBank.Size()) return ""; uint32_t dataRelPtr = ctx.audioBank.ReadU32(samplePtr + 4); uint32_t absOffset = dataRelPtr + ctx.sampleBankTable[bankIndex].ptr; if (ctx.sampleMap.count(absOffset)) { return "audio/samples/" + ctx.sampleMap[absOffset].name + "_META"; } return ""; } std::vector> AudioFontWriter::ParseEnvelope(uint32_t envOffset, FontWriteContext& ctx) { std::vector> envs; while (envOffset + 4 <= ctx.audioBank.Size()) { int16_t delay = ctx.audioBank.ReadS16(envOffset); int16_t arg = ctx.audioBank.ReadS16(envOffset + 2); envs.push_back({delay, arg}); envOffset += 4; if (delay < 0) break; } return envs; } void AudioFontWriter::WriteEnvData(LUS::BinaryWriter& w, const std::vector>& envs) { w.Write(static_cast(envs.size())); for (auto& [delay, arg] : envs) { w.Write(delay); w.Write(arg); } } void AudioFontWriter::WriteSFE(LUS::BinaryWriter& w, uint32_t sfeOffset, uint32_t baseOffset, int bankIndex, FontWriteContext& ctx) { if (sfeOffset + 8 > ctx.audioBank.Size()) { w.Write(static_cast(0)); return; } uint32_t samplePtr = ctx.audioBank.ReadU32(sfeOffset); if (samplePtr == 0) { w.Write(static_cast(0)); return; } w.Write(static_cast(1)); // exists w.Write(static_cast(1)); // padding (V2 compat) samplePtr += baseOffset; if (samplePtr + 4 <= ctx.audioBank.Size()) { uint32_t dataRelPtr = ctx.audioBank.ReadU32(samplePtr + 4); uint32_t absOffset = dataRelPtr + ctx.sampleBankTable[bankIndex].ptr; if (ctx.sampleMap.count(absOffset)) { w.Write(std::string("audio/samples/" + ctx.sampleMap[absOffset].name + "_META")); } else { w.Write(std::string("")); } } else { w.Write(std::string("")); } w.Write(ctx.audioBank.ReadFloat(sfeOffset + 4)); } void FontResidue::Reset() { mLoaded = mRangeLo = mRangeHi = mRelease = 0; } // Seed from last drum's stack layout (DrumEntry→InstrumentEntry mapping). void FontResidue::SeedFromDrums(const std::vector& drums) { if (drums.empty()) return; auto& lastDrum = drums.back(); mLoaded = lastDrum.pan; // drum.pan (offset 1) → inst.loaded (offset 1) mRangeLo = lastDrum.loaded; // drum.loaded (offset 2) → inst.normalRangeLo (offset 2) mRangeHi = 0; // padding (offset 3) mRelease = 0; // drum.offset=0 (offset 4) } void FontResidue::ApplyToInstrument(InstEntry& inst) const { inst.loaded = mLoaded; inst.normalRangeLo = mRangeLo; inst.normalRangeHi = mRangeHi; inst.releaseRate = mRelease; } void FontResidue::UpdateFromInstrument(const InstEntry& inst) { mLoaded = inst.loaded; mRangeLo = inst.normalRangeLo; mRangeHi = inst.normalRangeHi; mRelease = inst.releaseRate; } std::vector AudioFontWriter::ParseInstruments(int numInstruments, uint32_t ptr, FontWriteContext& ctx) { std::vector instruments; for (int i = 0; i < numInstruments; i++) { if (ptr + 8 + (i + 1) * 4 > ctx.audioBank.Size()) break; uint32_t instPtr = ctx.audioBank.ReadU32(ptr + 8 + i * 4); InstEntry inst = {}; inst.isValid = (instPtr != 0); mResidue.ApplyToInstrument(inst); if (instPtr != 0) { instPtr += ptr; if (instPtr + 28 <= ctx.audioBank.Size()) { inst.loaded = ctx.audioBank.ReadU8(instPtr); inst.normalRangeLo = ctx.audioBank.ReadU8(instPtr + 1); inst.normalRangeHi = ctx.audioBank.ReadU8(instPtr + 2); inst.releaseRate = ctx.audioBank.ReadU8(instPtr + 3); inst.env = ParseEnvelope(ctx.audioBank.ReadU32(instPtr + 4) + ptr, ctx); inst.lowAddr = instPtr + 8; inst.normalAddr = instPtr + 16; inst.highAddr = instPtr + 24; mResidue.UpdateFromInstrument(inst); } } instruments.push_back(inst); } return instruments; } std::vector AudioFontWriter::ParseSFX(int numSfx, uint32_t ptr, int sampleBankId, FontWriteContext& ctx) { std::vector sfxEntries; if (ptr + 8 > ctx.audioBank.Size()) return sfxEntries; uint32_t sfxListAddr = ctx.audioBank.ReadU32(ptr + 4) + ptr; for (int i = 0; i < numSfx; i++) { uint32_t sfeAddr = sfxListAddr + i * 8; if (sfeAddr + 8 > ctx.audioBank.Size()) break; uint32_t sp = ctx.audioBank.ReadU32(sfeAddr); if (sp != 0) { sp += ptr; std::string ref; if (sp + 4 <= ctx.audioBank.Size()) { uint32_t relPtr = ctx.audioBank.ReadU32(sp + 4); uint32_t absOff = relPtr + ctx.sampleBankTable[sampleBankId].ptr; if (ctx.sampleMap.count(absOff)) ref = "audio/samples/" + ctx.sampleMap[absOff].name + "_META"; } sfxEntries.push_back({true, ref, ctx.audioBank.ReadFloat(sfeAddr + 4)}); } else { sfxEntries.push_back({false, "", ctx.audioBank.ReadFloat(sfeAddr + 4)}); } } return sfxEntries; } std::vector AudioFontWriter::ParseDrums(int numDrums, uint32_t ptr, int sampleBankId, FontWriteContext& ctx) { std::vector drums; if (ptr + 4 > ctx.audioBank.Size()) return drums; uint32_t drumListAddr = ctx.audioBank.ReadU32(ptr) + ptr; for (int i = 0; i < numDrums; i++) { if (drumListAddr + (i + 1) * 4 > ctx.audioBank.Size()) break; uint32_t drumPtr = ctx.audioBank.ReadU32(drumListAddr + i * 4); if (drumPtr != 0) { drumPtr += ptr; if (drumPtr + 16 <= ctx.audioBank.Size()) { DrumEntry d; d.releaseRate = ctx.audioBank.ReadU8(drumPtr); d.pan = ctx.audioBank.ReadU8(drumPtr + 1); d.loaded = ctx.audioBank.ReadU8(drumPtr + 2); d.tuning = ctx.audioBank.ReadFloat(drumPtr + 8); d.env = ParseEnvelope(ctx.audioBank.ReadU32(drumPtr + 12) + ptr, ctx); uint32_t sampleEntryPtr = ctx.audioBank.ReadU32(drumPtr + 4) + ptr; if (sampleEntryPtr + 4 <= ctx.audioBank.Size()) { uint32_t dataRelPtr = ctx.audioBank.ReadU32(sampleEntryPtr + 4); uint32_t absOffset = dataRelPtr + ctx.sampleBankTable[sampleBankId].ptr; if (ctx.sampleMap.count(absOffset)) { d.sampleRef = "audio/samples/" + ctx.sampleMap[absOffset].name + "_META"; } } drums.push_back(d); continue; } } drums.push_back({0, 0, 0, 0.0f, {}, ""}); } mResidue.SeedFromDrums(drums); return drums; } void AudioFontWriter::WriteDrums(LUS::BinaryWriter& w, const std::vector& drums) { for (auto& d : drums) { w.Write(d.releaseRate); w.Write(d.pan); w.Write(d.loaded); WriteEnvData(w, d.env); w.Write(static_cast(d.sampleRef.empty() ? 0 : 1)); w.Write(d.sampleRef); w.Write(d.tuning); } } void AudioFontWriter::WriteInstruments(LUS::BinaryWriter& w, const std::vector& instruments, uint32_t ptr, int sampleBankId, FontWriteContext& ctx) { for (auto& inst : instruments) { w.Write(static_cast(inst.isValid ? 1 : 0)); w.Write(inst.loaded); w.Write(inst.normalRangeLo); w.Write(inst.normalRangeHi); w.Write(inst.releaseRate); WriteEnvData(w, inst.env); if (inst.isValid) { if (ctx.audioBank.ReadU32(inst.lowAddr) != 0) { WriteSFE(w, inst.lowAddr, ptr, sampleBankId, ctx); } else { w.Write(static_cast(0)); } if (ctx.audioBank.ReadU32(inst.normalAddr) != 0) { WriteSFE(w, inst.normalAddr, ptr, sampleBankId, ctx); } else { w.Write(static_cast(0)); } if (ctx.audioBank.ReadU32(inst.highAddr) != 0 && inst.normalRangeHi != 0x7F) { WriteSFE(w, inst.highAddr, ptr, sampleBankId, ctx); } else { w.Write(static_cast(0)); } } else { w.Write(static_cast(0)); w.Write(static_cast(0)); w.Write(static_cast(0)); } } } void AudioFontWriter::WriteSFXEntries(LUS::BinaryWriter& w, const std::vector& sfxEntries) { for (auto& sfx : sfxEntries) { if (sfx.exists) { w.Write(static_cast(1)); w.Write(static_cast(1)); w.Write(sfx.sampleRef); w.Write(sfx.tuning); } else { w.Write(static_cast(0)); } } } void AudioFontWriter::WriteFontCompanion(uint32_t fontIndex, const AudioTableEntry& fontEntry, const std::vector& drums, const std::vector& instruments, const std::vector& sfxEntries, uint32_t ptr, int sampleBankId, FontWriteContext& ctx, const std::map& fontNames) { LUS::BinaryWriter w; BaseExporter::WriteHeader(w, Torch::ResourceType::OoTAudioSoundFont, 2); // Font metadata w.Write(static_cast(fontIndex)); w.Write(fontEntry.medium); w.Write(fontEntry.cachePolicy); w.Write(fontEntry.data1); w.Write(fontEntry.data2); w.Write(fontEntry.data3); w.Write(static_cast(drums.size())); w.Write(static_cast(instruments.size())); w.Write(static_cast(sfxEntries.size())); WriteDrums(w, drums); WriteInstruments(w, instruments, ptr, sampleBankId, ctx); WriteSFXEntries(w, sfxEntries); std::string fontName; if (fontNames.count(fontIndex)) { fontName = fontNames.at(fontIndex); } else { fontName = std::to_string(fontIndex) + "_Font"; } std::stringstream ss; w.Finish(ss); std::string str = ss.str(); Companion::Instance->RegisterCompanionFile( "fonts/" + fontName, std::vector(str.begin(), str.end())); } void AudioFontWriter::Extract(YAML::Node& node, SafeAudioBankReader& audioBank, const std::vector& fontTable, const std::vector& sampleBankTable, std::map& sampleMap) { // Build font name map from YAML std::map fontNames; if (node["fonts"] && node["fonts"].IsSequence()) { auto fontsNode = node["fonts"]; for (size_t i = 0; i < fontsNode.size(); i++) { auto fn = fontsNode[i]; fontNames[fn["index"].as()] = fn["name"].as(); } } FontWriteContext ctx { .audioBank = audioBank, .sampleBankTable = sampleBankTable, .sampleMap = sampleMap, }; mResidue.Reset(); for (uint32_t fi = 0; fi < fontTable.size(); fi++) { auto& fe = fontTable[fi]; uint32_t ptr = fe.ptr; int sampleBankId = (fe.data1 >> 8) & 0xFF; int numInstruments = (fe.data2 >> 8) & 0xFF; int numDrums = fe.data2 & 0xFF; int numSfx = fe.data3; auto drums = ParseDrums(numDrums, ptr, sampleBankId, ctx); auto sfxEntries = ParseSFX(numSfx, ptr, sampleBankId, ctx); auto instruments = ParseInstruments(numInstruments, ptr, ctx); WriteFontCompanion(fi, fe, drums, instruments, sfxEntries, ptr, sampleBankId, ctx, fontNames); } SPDLOG_INFO("OoTAudioFactory: wrote {} font companion files", fontTable.size()); } } // namespace OoT