diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.cpp b/soh/soh/resource/importer/AudioSoundFontFactory.cpp index 99f160860..fcc0cf9a7 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.cpp +++ b/soh/soh/resource/importer/AudioSoundFontFactory.cpp @@ -1,7 +1,9 @@ #include "soh/resource/importer/AudioSoundFontFactory.h" #include "soh/resource/type/AudioSoundFont.h" +#include "soh/resource/logging/AudioSoundFontLogger.h" #include "spdlog/spdlog.h" #include "libultraship/libultraship.h" +#include namespace SOH { std::shared_ptr @@ -72,6 +74,7 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); } + audioSoundFont->drumFileNames.push_back(sampleFileName); audioSoundFont->drums.push_back(drum); audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back()); @@ -115,9 +118,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr instrument.lowNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->lowInstrumentFileNames.push_back(sampleFileName); } else { instrument.lowNotesSound.sample = nullptr; instrument.lowNotesSound.tuning = 0; + audioSoundFont->lowInstrumentFileNames.push_back(""); } bool hasNormalNoteSoundFontEntry = reader->ReadInt8(); @@ -127,9 +132,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr instrument.normalNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->normalInstrumentFileNames.push_back(sampleFileName); } else { instrument.normalNotesSound.sample = nullptr; instrument.normalNotesSound.tuning = 0; + audioSoundFont->normalInstrumentFileNames.push_back(""); } bool hasHighNoteSoundFontEntry = reader->ReadInt8(); @@ -139,9 +146,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr instrument.highNotesSound.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->highInstrumentFileNames.push_back(sampleFileName); } else { instrument.highNotesSound.sample = nullptr; instrument.highNotesSound.tuning = 0; + audioSoundFont->highInstrumentFileNames.push_back(""); } audioSoundFont->instruments.push_back(instrument); @@ -161,12 +170,216 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr soundEffect.tuning = reader->ReadFloat(); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); soundEffect.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->soundEffectFileNames.push_back(sampleFileName); } audioSoundFont->soundEffects.push_back(soundEffect); } audioSoundFont->soundFont.soundEffects = audioSoundFont->soundEffects.data(); + if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ResourceLogging"), 0)) { + LogAudioSoundFontAsXML(initData, audioSoundFont); + } + + return audioSoundFont; +} + +std::shared_ptr +ResourceFactoryXMLAudioSoundFontV2::ReadResource(std::shared_ptr file, + std::shared_ptr initData) { + if (!FileHasValidFormatAndReader(file, initData)) { + return nullptr; + } + + std::shared_ptr audioSoundFont = std::make_shared(initData); + std::shared_ptr reader = std::get>(file->Reader); + + tinyxml2::XMLElement* root = reader->RootElement(); + + if (root->Name() != "SoundFont") { + LUSLOG_ERROR("Tried to load malformed sound font"); + assert(false); + return nullptr; + } + + audioSoundFont->soundFont.fntIndex = root->IntAttribute("FntIndex"); + audioSoundFont->medium = root->IntAttribute("Medium"); + audioSoundFont->cachePolicy = root->IntAttribute("CachePolicy"); + + audioSoundFont->soundFont.sampleBankId1 = root->IntAttribute("SampleBankId1"); + audioSoundFont->soundFont.sampleBankId2 = root->IntAttribute("SampleBankId2"); + audioSoundFont->data1 = (audioSoundFont->soundFont.sampleBankId1 << 8) & audioSoundFont->soundFont.sampleBankId2; + + audioSoundFont->data2 = root->IntAttribute("Data2"); + audioSoundFont->data3 = root->IntAttribute("Data3"); + + tinyxml2::XMLElement* rootChild = root->FirstChildElement(); + + while (rootChild != nullptr) { + if (rootChild->Name() == "Drum") { // πŸ₯ DRUMS πŸ₯ + Drum drum; + drum.releaseRate = rootChild->IntAttribute("ReleaseRate"); + drum.pan = rootChild->IntAttribute("Pan"); + + // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + // drum.loaded = rootChild->IntAttribute("Loaded"); + drum.loaded = 0; + + std::vector drumEnvelopes; + tinyxml2::XMLElement* drumChild = rootChild->FirstChildElement(); + + while (drumChild != nullptr) { + if (drumChild->Name() != "Envelope") { + LUSLOG_ERROR("Tried to load malformed sound font drum envelope"); + assert(false); + return nullptr; + } + + AdsrEnvelope env; + + int16_t delay = drumChild->IntAttribute("Delay"); + int16_t arg = drumChild->IntAttribute("Arg"); + + env.delay = BE16SWAP(delay); + env.arg = BE16SWAP(arg); + + drumEnvelopes.push_back(env); + + drumChild = drumChild->NextSiblingElement(); + } + + audioSoundFont->drumEnvelopeArrays.push_back(drumEnvelopes); + drum.envelope = audioSoundFont->drumEnvelopeArrays.back().data(); + audioSoundFont->drumEnvelopeCounts.push_back(drumEnvelopes.size()); + + std::string sampleFileName = std::string(rootChild->Attribute("SampleFileName")); + drum.sound.tuning = rootChild->FloatAttribute("Tuning"); + + if (sampleFileName.empty()) { + drum.sound.sample = nullptr; + } else { + std::shared_ptr res = + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); + drum.sound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + } + audioSoundFont->drumFileNames.push_back(sampleFileName); + + audioSoundFont->drums.push_back(drum); + audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back()); + } else if (rootChild->Name() == "Instrument") { // 🎺🎻🎷🎸🎹 INSTRUMENTS 🎹🎸🎷🎻🎺 + Instrument instrument; + + uint8_t isValidEntry = rootChild->IntAttribute("IsValidEntry"); + // this was always getting set to zero in ResourceMgr_LoadAudioSoundFont + // instrument.loaded = rootChild->IntAttribute("Loaded"); + instrument.loaded = 0; + + instrument.normalRangeLo = rootChild->IntAttribute("NormalRangeLo"); + instrument.normalRangeHi = rootChild->IntAttribute("NormalRangeHi"); + instrument.releaseRate = rootChild->IntAttribute("ReleaseRate"); + + std::vector instrumentEnvelopes; + tinyxml2::XMLElement* instrumentChild = rootChild->FirstChildElement(); + + while (instrumentChild != nullptr) { + if (instrumentChild->Name() != "Envelope") { + LUSLOG_ERROR("Tried to load malformed sound font drum envelope"); + assert(false); + return nullptr; + } + + AdsrEnvelope env; + + int16_t delay = instrumentChild->IntAttribute("Delay"); + int16_t arg = instrumentChild->IntAttribute("Arg"); + + env.delay = BE16SWAP(delay); + env.arg = BE16SWAP(arg); + + instrumentEnvelopes.push_back(env); + + instrumentChild = instrumentChild->NextSiblingElement(); + } + + audioSoundFont->instrumentEnvelopeCounts.push_back(instrumentEnvelopes.size()); + audioSoundFont->instrumentEnvelopeArrays.push_back(instrumentEnvelopes); + instrument.envelope = audioSoundFont->instrumentEnvelopeArrays.back().data(); + + const char* lowNoteSampleFileName = rootChild->Attribute("LowNoteSampleFileName"); + if (lowNoteSampleFileName != nullptr) { + instrument.lowNotesSound.tuning = rootChild->FloatAttribute("Tuning"); + std::string sampleFileName = std::string(lowNoteSampleFileName); + std::shared_ptr res = + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); + instrument.lowNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->lowInstrumentFileNames.push_back(sampleFileName); + } else { + instrument.lowNotesSound.tuning = 0; + instrument.lowNotesSound.sample = nullptr; + audioSoundFont->lowInstrumentFileNames.push_back(""); + } + + const char* normalNoteSampleFileName = rootChild->Attribute("NormalNoteSampleFileName"); + if (normalNoteSampleFileName != nullptr) { + instrument.normalNotesSound.tuning = rootChild->FloatAttribute("Tuning"); + std::string sampleFileName = std::string(normalNoteSampleFileName); + std::shared_ptr res = + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); + instrument.normalNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->normalInstrumentFileNames.push_back(sampleFileName); + } else { + instrument.normalNotesSound.tuning = 0; + instrument.normalNotesSound.sample = nullptr; + audioSoundFont->normalInstrumentFileNames.push_back(""); + } + + const char* highNoteSampleFileName = rootChild->Attribute("HighNoteSampleFileName"); + if (normalNoteSampleFileName != nullptr) { + instrument.highNotesSound.tuning = rootChild->FloatAttribute("Tuning"); + std::string sampleFileName = std::string(normalNoteSampleFileName); + std::shared_ptr res = + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); + instrument.highNotesSound.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->highInstrumentFileNames.push_back(sampleFileName); + } else { + instrument.highNotesSound.tuning = 0; + instrument.highNotesSound.sample = nullptr; + audioSoundFont->highInstrumentFileNames.push_back(""); + } + + audioSoundFont->instruments.push_back(instrument); + audioSoundFont->instrumentAddresses.push_back(isValidEntry ? &audioSoundFont->instruments.back() : nullptr); + } else if (rootChild->Name() == "Sfx") { // πŸ”Š SOUND EFFECTS πŸ”Š + SoundFontSound soundEffect; + + soundEffect.tuning = rootChild->FloatAttribute("Tuning"); + std::string sampleFileName = std::string(rootChild->Attribute("SampleFileName")); + std::shared_ptr res = + Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); + soundEffect.sample = static_cast(res ? res->GetRawPointer() : nullptr); + audioSoundFont->soundEffectFileNames.push_back(sampleFileName); + + audioSoundFont->soundEffects.push_back(soundEffect); + } else { + LUSLOG_ERROR("Tried to load sound font element of unknown type: %s (valid types are \"Drum\", " + "\"Instrument\" & \"Sfx\")", + rootChild->Name()); + assert(false); + return nullptr; + } + + rootChild = rootChild->NextSiblingElement(); + } + + audioSoundFont->soundFont.numDrums = audioSoundFont->drumAddresses.size(); + audioSoundFont->soundFont.drums = audioSoundFont->drumAddresses.data(); + + audioSoundFont->soundFont.numInstruments = audioSoundFont->instrumentAddresses.size(); + audioSoundFont->soundFont.instruments = audioSoundFont->instrumentAddresses.data(); + + audioSoundFont->soundFont.numSfx = audioSoundFont->soundEffects.size(); + audioSoundFont->soundFont.soundEffects = audioSoundFont->soundEffects.data(); + return audioSoundFont; } } // namespace SOH diff --git a/soh/soh/resource/importer/AudioSoundFontFactory.h b/soh/soh/resource/importer/AudioSoundFontFactory.h index b978a14bf..561d907ac 100644 --- a/soh/soh/resource/importer/AudioSoundFontFactory.h +++ b/soh/soh/resource/importer/AudioSoundFontFactory.h @@ -2,6 +2,7 @@ #include "Resource.h" #include "ResourceFactoryBinary.h" +#include "ResourceFactoryXML.h" namespace SOH { class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactoryBinary { @@ -9,4 +10,10 @@ class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactory std::shared_ptr ReadResource(std::shared_ptr file, std::shared_ptr initData) override; }; + +class ResourceFactoryXMLAudioSoundFontV2 : public Ship::ResourceFactoryXML { + public: + std::shared_ptr ReadResource(std::shared_ptr file, + std::shared_ptr initData) override; +}; } // namespace SOH diff --git a/soh/soh/resource/logging/AudioSoundFontLogger.cpp b/soh/soh/resource/logging/AudioSoundFontLogger.cpp new file mode 100644 index 000000000..135e94378 --- /dev/null +++ b/soh/soh/resource/logging/AudioSoundFontLogger.cpp @@ -0,0 +1,100 @@ +#include "soh/resource/type/AudioSoundFont.h" +#include "spdlog/spdlog.h" +#include + +namespace SOH { +void LogAudioSoundFontAsXML(std::shared_ptr initData, + std::shared_ptr audioSoundFont) { + tinyxml2::XMLDocument doc; + tinyxml2::XMLElement* root = doc.NewElement("SoundFont"); + doc.InsertFirstChild(root); + + for (size_t i = 0; i < audioSoundFont->soundFont.numDrums; i += 1) { + tinyxml2::XMLElement* drumElement = doc.NewElement("Drum"); + Drum* drum = audioSoundFont->soundFont.drums[i]; + + drumElement->SetAttribute("ReleaseRate", drum->releaseRate); + drumElement->SetAttribute("Pan", drum->pan); + drumElement->SetAttribute("Loaded", drum->loaded); + drumElement->SetAttribute("Tuning", drum->sound.tuning); + + if (drum->sound.sample != nullptr) { + drumElement->SetAttribute("SampleFileName", audioSoundFont->drumFileNames[i].c_str()); + } + + std::vector envelopes = audioSoundFont->drumEnvelopeArrays[i]; + + for (AdsrEnvelope envelope : envelopes) { + tinyxml2::XMLElement* drumEnvelopeElement = doc.NewElement("Envelope"); + + drumEnvelopeElement->SetAttribute("Delay", BE16SWAP(envelope.delay)); + drumEnvelopeElement->SetAttribute("Arg", BE16SWAP(envelope.arg)); + + drumElement->InsertEndChild(drumEnvelopeElement); + } + + root->InsertEndChild(drumElement); + } + + for (size_t i = 0; i < audioSoundFont->soundFont.numInstruments; i += 1) { + tinyxml2::XMLElement* instrumentElement = doc.NewElement("Instrument"); + Instrument* instrument = audioSoundFont->soundFont.instruments[i]; + + if (instrument == nullptr) { + SPDLOG_INFO("[LogAudioSoundFontAsXML]: Instrument was nullptr (i={})", i); + instrumentElement->SetAttribute("IsValidEntry", 0); + root->InsertEndChild(instrumentElement); + continue; + } + + instrumentElement->SetAttribute("IsValidEntry", "1"); + instrumentElement->SetAttribute("Loaded", instrument->loaded); + instrumentElement->SetAttribute("NormalRangeLo", instrument->normalRangeLo); + instrumentElement->SetAttribute("NormalRangeHi", instrument->normalRangeHi); + instrumentElement->SetAttribute("ReleaseRate", instrument->releaseRate); + + std::vector envelopes = audioSoundFont->instrumentEnvelopeArrays[i]; + + for (AdsrEnvelope envelope : envelopes) { + tinyxml2::XMLElement* instrumentEnvelopeElement = doc.NewElement("Envelope"); + + instrumentEnvelopeElement->SetAttribute("Delay", BE16SWAP(envelope.delay)); + instrumentEnvelopeElement->SetAttribute("Arg", BE16SWAP(envelope.arg)); + + instrumentElement->InsertEndChild(instrumentEnvelopeElement); + } + + if (instrument->lowNotesSound.sample != nullptr) { + instrumentElement->SetAttribute("LowNoteSampleFileName", audioSoundFont->lowInstrumentFileNames[i].c_str()); + } + + if (instrument->lowNotesSound.sample != nullptr) { + instrumentElement->SetAttribute("NormalNoteSampleFileName", + audioSoundFont->normalInstrumentFileNames[i].c_str()); + } + + if (instrument->lowNotesSound.sample != nullptr) { + instrumentElement->SetAttribute("HighNoteSampleFileName", + audioSoundFont->highInstrumentFileNames[i].c_str()); + } + + root->InsertEndChild(instrumentElement); + } + + for (size_t i = 0; i < audioSoundFont->soundFont.numSfx; i += 1) { + tinyxml2::XMLElement* sfxElement = doc.NewElement("Sfx"); + SoundFontSound sfx = audioSoundFont->soundFont.soundEffects[i]; + + sfxElement->SetAttribute("Tuning", sfx.tuning); + + sfxElement->SetAttribute("SampleFileName", audioSoundFont->soundEffectFileNames[i].c_str()); + + root->InsertEndChild(sfxElement); + } + + tinyxml2::XMLPrinter printer; + doc.Accept(&printer); + + SPDLOG_INFO("{}: {}", initData->Path, printer.CStr()); +} +} // namespace SOH \ No newline at end of file diff --git a/soh/soh/resource/logging/AudioSoundFontLogger.h b/soh/soh/resource/logging/AudioSoundFontLogger.h new file mode 100644 index 000000000..6335c42b7 --- /dev/null +++ b/soh/soh/resource/logging/AudioSoundFontLogger.h @@ -0,0 +1,8 @@ +#include "Resource.h" +#include "soh/OTRGlobals.h" +#include "soh/cvar_prefixes.h" + +namespace SOH { +void LogAudioSoundFontAsXML(std::shared_ptr initData, + std::shared_ptr audioSoundFont); +} \ No newline at end of file diff --git a/soh/soh/resource/type/AudioSoundFont.h b/soh/soh/resource/type/AudioSoundFont.h index a7e329519..3a7902461 100644 --- a/soh/soh/resource/type/AudioSoundFont.h +++ b/soh/soh/resource/type/AudioSoundFont.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "Resource.h" #include "soh/resource/type/AudioSample.h" @@ -69,16 +70,21 @@ class AudioSoundFont : public Ship::Resource { uint16_t data3; std::vector drums; + std::vector drumFileNames; std::vector drumAddresses; std::vector drumEnvelopeCounts; std::vector> drumEnvelopeArrays; std::vector instruments; + std::vector lowInstrumentFileNames; + std::vector normalInstrumentFileNames; + std::vector highInstrumentFileNames; std::vector instrumentAddresses; std::vector instrumentEnvelopeCounts; std::vector> instrumentEnvelopeArrays; std::vector soundEffects; + std::vector soundEffectFileNames; SoundFont soundFont; };