Add audio sound font XML importer & exporter

This commit is contained in:
Pepe20129 2025-05-28 18:11:58 +02:00
commit f3908c3968
5 changed files with 334 additions and 0 deletions

View file

@ -1,7 +1,9 @@
#include "soh/resource/importer/AudioSoundFontFactory.h" #include "soh/resource/importer/AudioSoundFontFactory.h"
#include "soh/resource/type/AudioSoundFont.h" #include "soh/resource/type/AudioSoundFont.h"
#include "soh/resource/logging/AudioSoundFontLogger.h"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "libultraship/libultraship.h" #include "libultraship/libultraship.h"
#include <tinyxml2.h>
namespace SOH { namespace SOH {
std::shared_ptr<Ship::IResource> std::shared_ptr<Ship::IResource>
@ -72,6 +74,7 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
drum.sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr); drum.sound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
} }
audioSoundFont->drumFileNames.push_back(sampleFileName);
audioSoundFont->drums.push_back(drum); audioSoundFont->drums.push_back(drum);
audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back()); audioSoundFont->drumAddresses.push_back(&audioSoundFont->drums.back());
@ -115,9 +118,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
instrument.lowNotesSound.tuning = reader->ReadFloat(); instrument.lowNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr); instrument.lowNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
audioSoundFont->lowInstrumentFileNames.push_back(sampleFileName);
} else { } else {
instrument.lowNotesSound.sample = nullptr; instrument.lowNotesSound.sample = nullptr;
instrument.lowNotesSound.tuning = 0; instrument.lowNotesSound.tuning = 0;
audioSoundFont->lowInstrumentFileNames.push_back("");
} }
bool hasNormalNoteSoundFontEntry = reader->ReadInt8(); bool hasNormalNoteSoundFontEntry = reader->ReadInt8();
@ -127,9 +132,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
instrument.normalNotesSound.tuning = reader->ReadFloat(); instrument.normalNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr); instrument.normalNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
audioSoundFont->normalInstrumentFileNames.push_back(sampleFileName);
} else { } else {
instrument.normalNotesSound.sample = nullptr; instrument.normalNotesSound.sample = nullptr;
instrument.normalNotesSound.tuning = 0; instrument.normalNotesSound.tuning = 0;
audioSoundFont->normalInstrumentFileNames.push_back("");
} }
bool hasHighNoteSoundFontEntry = reader->ReadInt8(); bool hasHighNoteSoundFontEntry = reader->ReadInt8();
@ -139,9 +146,11 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
instrument.highNotesSound.tuning = reader->ReadFloat(); instrument.highNotesSound.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr); instrument.highNotesSound.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
audioSoundFont->highInstrumentFileNames.push_back(sampleFileName);
} else { } else {
instrument.highNotesSound.sample = nullptr; instrument.highNotesSound.sample = nullptr;
instrument.highNotesSound.tuning = 0; instrument.highNotesSound.tuning = 0;
audioSoundFont->highInstrumentFileNames.push_back("");
} }
audioSoundFont->instruments.push_back(instrument); audioSoundFont->instruments.push_back(instrument);
@ -161,12 +170,216 @@ ResourceFactoryBinaryAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File>
soundEffect.tuning = reader->ReadFloat(); soundEffect.tuning = reader->ReadFloat();
auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str()); auto res = Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
soundEffect.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr); soundEffect.sample = static_cast<Sample*>(res ? res->GetRawPointer() : nullptr);
audioSoundFont->soundEffectFileNames.push_back(sampleFileName);
} }
audioSoundFont->soundEffects.push_back(soundEffect); audioSoundFont->soundEffects.push_back(soundEffect);
} }
audioSoundFont->soundFont.soundEffects = audioSoundFont->soundEffects.data(); audioSoundFont->soundFont.soundEffects = audioSoundFont->soundEffects.data();
if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ResourceLogging"), 0)) {
LogAudioSoundFontAsXML(initData, audioSoundFont);
}
return audioSoundFont;
}
std::shared_ptr<Ship::IResource>
ResourceFactoryXMLAudioSoundFontV2::ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) {
if (!FileHasValidFormatAndReader(file, initData)) {
return nullptr;
}
std::shared_ptr<SOH::AudioSoundFont> audioSoundFont = std::make_shared<AudioSoundFont>(initData);
std::shared_ptr<tinyxml2::XMLDocument> reader = std::get<std::shared_ptr<tinyxml2::XMLDocument>>(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<AdsrEnvelope> 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<Ship::IResource> res =
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
drum.sound.sample = static_cast<Sample*>(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<AdsrEnvelope> 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<Ship::IResource> res =
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.lowNotesSound.sample = static_cast<Sample*>(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<Ship::IResource> res =
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.normalNotesSound.sample = static_cast<Sample*>(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<Ship::IResource> res =
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
instrument.highNotesSound.sample = static_cast<Sample*>(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<Ship::IResource> res =
Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(sampleFileName.c_str());
soundEffect.sample = static_cast<Sample*>(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; return audioSoundFont;
} }
} // namespace SOH } // namespace SOH

View file

@ -2,6 +2,7 @@
#include "Resource.h" #include "Resource.h"
#include "ResourceFactoryBinary.h" #include "ResourceFactoryBinary.h"
#include "ResourceFactoryXML.h"
namespace SOH { namespace SOH {
class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactoryBinary { class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactoryBinary {
@ -9,4 +10,10 @@ class ResourceFactoryBinaryAudioSoundFontV2 final : public Ship::ResourceFactory
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file, std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override; std::shared_ptr<Ship::ResourceInitData> initData) override;
}; };
class ResourceFactoryXMLAudioSoundFontV2 : public Ship::ResourceFactoryXML {
public:
std::shared_ptr<Ship::IResource> ReadResource(std::shared_ptr<Ship::File> file,
std::shared_ptr<Ship::ResourceInitData> initData) override;
};
} // namespace SOH } // namespace SOH

View file

@ -0,0 +1,100 @@
#include "soh/resource/type/AudioSoundFont.h"
#include "spdlog/spdlog.h"
#include <tinyxml2.h>
namespace SOH {
void LogAudioSoundFontAsXML(std::shared_ptr<Ship::ResourceInitData> initData,
std::shared_ptr<SOH::AudioSoundFont> 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<AdsrEnvelope> 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<AdsrEnvelope> 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

View file

@ -0,0 +1,8 @@
#include "Resource.h"
#include "soh/OTRGlobals.h"
#include "soh/cvar_prefixes.h"
namespace SOH {
void LogAudioSoundFontAsXML(std::shared_ptr<Ship::ResourceInitData> initData,
std::shared_ptr<SOH::AudioSoundFont> audioSoundFont);
}

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <string>
#include <vector> #include <vector>
#include "Resource.h" #include "Resource.h"
#include "soh/resource/type/AudioSample.h" #include "soh/resource/type/AudioSample.h"
@ -69,16 +70,21 @@ class AudioSoundFont : public Ship::Resource<SoundFont> {
uint16_t data3; uint16_t data3;
std::vector<Drum> drums; std::vector<Drum> drums;
std::vector<std::string> drumFileNames;
std::vector<Drum*> drumAddresses; std::vector<Drum*> drumAddresses;
std::vector<uint32_t> drumEnvelopeCounts; std::vector<uint32_t> drumEnvelopeCounts;
std::vector<std::vector<AdsrEnvelope>> drumEnvelopeArrays; std::vector<std::vector<AdsrEnvelope>> drumEnvelopeArrays;
std::vector<Instrument> instruments; std::vector<Instrument> instruments;
std::vector<std::string> lowInstrumentFileNames;
std::vector<std::string> normalInstrumentFileNames;
std::vector<std::string> highInstrumentFileNames;
std::vector<Instrument*> instrumentAddresses; std::vector<Instrument*> instrumentAddresses;
std::vector<uint32_t> instrumentEnvelopeCounts; std::vector<uint32_t> instrumentEnvelopeCounts;
std::vector<std::vector<AdsrEnvelope>> instrumentEnvelopeArrays; std::vector<std::vector<AdsrEnvelope>> instrumentEnvelopeArrays;
std::vector<SoundFontSound> soundEffects; std::vector<SoundFontSound> soundEffects;
std::vector<std::string> soundEffectFileNames;
SoundFont soundFont; SoundFont soundFont;
}; };