diff options
| author | Lywx <kiritodev01@gmail.com> | 2024-12-25 13:13:01 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-25 13:13:01 -0600 |
| commit | 16e4a74ff448c0b99499348667e714df71d5efb1 (patch) | |
| tree | 44405631454d3244f352d3bf10c63d75acc4b3d0 | |
| parent | abb74ef940afe35ddf7bea5ea2e03c45484a95a3 (diff) | |
| parent | a3fcaf8ee2247c2add6e89cc1e6379819a193b67 (diff) | |
Merge pull request #161 from HarbourMasters/audio_extraction
Custom samples support and XML support
| -rw-r--r-- | CMakeLists.txt | 19 | ||||
| -rw-r--r-- | src/Companion.cpp | 167 | ||||
| -rw-r--r-- | src/Companion.h | 2 | ||||
| -rw-r--r-- | src/factories/BaseFactory.h | 6 | ||||
| -rw-r--r-- | src/factories/naudio/v1/AudioContext.cpp | 58 | ||||
| -rw-r--r-- | src/factories/naudio/v1/AudioContext.h | 4 | ||||
| -rw-r--r-- | src/factories/naudio/v1/AudioTableFactory.cpp | 9 | ||||
| -rw-r--r-- | src/factories/naudio/v1/InstrumentFactory.cpp | 63 | ||||
| -rw-r--r-- | src/factories/naudio/v1/InstrumentFactory.h | 4 | ||||
| -rw-r--r-- | src/factories/naudio/v1/SampleFactory.cpp | 63 | ||||
| -rw-r--r-- | src/factories/naudio/v1/SampleFactory.h | 5 | ||||
| -rw-r--r-- | src/factories/naudio/v1/SoundFontFactory.cpp | 135 | ||||
| -rw-r--r-- | src/factories/naudio/v1/SoundFontFactory.h | 5 | ||||
| -rw-r--r-- | src/main.cpp | 8 |
14 files changed, 435 insertions, 113 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 365feca..fdbe317 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,20 +165,17 @@ endif() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/binarytools) add_dependencies(${PROJECT_NAME} BinaryTools) -target_link_libraries(${PROJECT_NAME} PRIVATE BinaryTools) # Link n64graphics add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/n64graphics) add_dependencies(${PROJECT_NAME} N64Graphics) -target_link_libraries(${PROJECT_NAME} PRIVATE N64Graphics) # Link StormLib if (BUILD_STORMLIB) set(STORMLIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/StormLib) add_subdirectory(${STORMLIB_DIR}) - target_link_libraries(${PROJECT_NAME} PRIVATE storm) endif() # Link YamlCpp @@ -193,7 +190,8 @@ FetchContent_Declare( ) set(YAML_CPP_BUILD_TESTS OFF) FetchContent_MakeAvailable(yaml-cpp) -target_link_libraries(${PROJECT_NAME} PRIVATE yaml-cpp) + +#Link Spdlog FetchContent_Declare( spdlog @@ -202,7 +200,18 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(spdlog) -target_link_libraries(${PROJECT_NAME} PRIVATE spdlog) + +# Link TinyXML2 +set(tinyxml2_BUILD_TESTING OFF) +FetchContent_Declare( + tinyxml2 + GIT_REPOSITORY https://github.com/leethomason/tinyxml2.git + GIT_TAG 10.0.0 + OVERRIDE_FIND_PACKAGE +) +FetchContent_MakeAvailable(tinyxml2) + +target_link_libraries(${PROJECT_NAME} PRIVATE spdlog tinyxml2 yaml-cpp storm N64Graphics BinaryTools) if((CMAKE_SYSTEM_NAME MATCHES "Windows") AND ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) include(../cmake/HandleCompilerRT.cmake) diff --git a/src/Companion.cpp b/src/Companion.cpp index 199d649..7181cd1 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -458,6 +458,7 @@ std::string ExportTypeToString(ExportType type) { case ExportType::Header: return "Header"; case ExportType::Code: return "Code"; case ExportType::Modding: return "Modding"; + case ExportType::XML: return "XML"; default: throw std::runtime_error("Invalid ExportType"); } @@ -562,66 +563,31 @@ void Companion::ProcessFile(YAML::Node root) { for(auto asset = root.begin(); asset != root.end(); ++asset){ auto node = asset->second; auto entryName = asset->first.as<std::string>(); + auto output = (this->gCurrentDirectory / entryName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); - // Parse horizontal assets - if(node["files"]){ - auto segment = node["segment"] ? node["segment"].as<uint8_t>() : -1; - const auto files = node["files"]; - for (const auto& file : files) { - auto assetNode = file.as<YAML::Node>(); - auto childName = assetNode["name"].as<std::string>(); - auto output = (this->gCurrentDirectory / entryName / childName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - - if(assetNode["type"]){ - const auto type = GetSafeNode<std::string>(assetNode, "type"); - if(type == "NAUDIO:V0:SAMPLE"){ - AudioManager::Instance->bind_sample(assetNode, output); - } - } - - if(!assetNode["offset"]) { - continue; - } - - if(segment != -1 || gCurrentSegmentNumber) { - assetNode["offset"] = (segment << 24) | assetNode["offset"].as<uint32_t>(); - } - - if(!gCurrentVirtualPath.empty()) { - node["path"] = gCurrentVirtualPath; - } - - this->gAddrMap[this->gCurrentFile][assetNode["offset"].as<uint32_t>()] = std::make_tuple(output, assetNode); - } - } else { - - auto output = (this->gCurrentDirectory / entryName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - - if(node["type"]){ - const auto type = GetSafeNode<std::string>(node, "type"); - if(type == "NAUDIO:V0:SAMPLE"){ - AudioManager::Instance->bind_sample(node, output); - } - } - - if(!node["offset"]) { - continue; + if(node["type"]){ + const auto type = GetSafeNode<std::string>(node, "type"); + if(type == "NAUDIO:V0:SAMPLE"){ + AudioManager::Instance->bind_sample(node, output); } + } - if(gCurrentSegmentNumber) { - if (IS_SEGMENTED(node["offset"].as<uint32_t>()) == false) { - node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as<uint32_t>(); - } - } + if(!node["offset"]) { + continue; + } - if(!gCurrentVirtualPath.empty()) { - node["path"] = gCurrentVirtualPath; + if(gCurrentSegmentNumber) { + if (IS_SEGMENTED(node["offset"].as<uint32_t>()) == false) { + node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as<uint32_t>(); } + } - this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = std::make_tuple(output, node); + if(!gCurrentVirtualPath.empty()) { + node["path"] = gCurrentVirtualPath; } + + this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = std::make_tuple(output, node); } // Stupid hack because the iteration broke the assets @@ -659,53 +625,23 @@ void Companion::ProcessFile(YAML::Node root) { continue; } - // Parse horizontal assets - if(assetNode["files"]){ - auto segment = assetNode["segment"] ? assetNode["segment"].as<uint8_t>() : -1; - auto files = assetNode["files"]; - for (const auto& file : files) { - auto node = file.as<YAML::Node>(); - auto childName = node["name"].as<std::string>(); - - if(!node["offset"]) { - continue; - } - - if(segment != -1 || gCurrentFileOffset) { - node["offset"] = (segment << 24) | node["offset"].as<uint32_t>(); - } - - if(!gCurrentVirtualPath.empty()) { - node["path"] = gCurrentVirtualPath; - } - - auto output = (this->gCurrentDirectory / entryName / childName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - this->gConfig.segment.temporal.clear(); - auto result = this->ParseNode(node, output); - if(result.has_value()) { - this->gParseResults[this->gCurrentFile].push_back(result.value()); - } - } - } else { - if(gCurrentFileOffset && assetNode["offset"]) { - const auto offset = assetNode["offset"].as<uint32_t>(); - if (!IS_SEGMENTED(offset)) { - assetNode["offset"] = (gCurrentSegmentNumber << 24) | offset; - } + if(gCurrentFileOffset && assetNode["offset"]) { + const auto offset = assetNode["offset"].as<uint32_t>(); + if (!IS_SEGMENTED(offset)) { + assetNode["offset"] = (gCurrentSegmentNumber << 24) | offset; } + } - if(!gCurrentVirtualPath.empty()) { - assetNode["path"] = gCurrentVirtualPath; - } + if(!gCurrentVirtualPath.empty()) { + assetNode["path"] = gCurrentVirtualPath; + } - std::string output = (this->gCurrentDirectory / entryName).string(); - std::replace(output.begin(), output.end(), '\\', '/'); - this->gConfig.segment.temporal.clear(); - auto result = this->ParseNode(assetNode, output); - if(result.has_value()) { - this->gParseResults[this->gCurrentFile].push_back(result.value()); - } + std::string output = (this->gCurrentDirectory / entryName).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + this->gConfig.segment.temporal.clear(); + auto result = this->ParseNode(assetNode, output); + if(result.has_value()) { + this->gParseResults[this->gCurrentFile].push_back(result.value()); } spdlog::set_pattern(regular); @@ -733,8 +669,16 @@ void Companion::ProcessFile(YAML::Node root) { exporter->get()->Export(stream, data, result.name, result.node, &result.name); auto data = stream.str(); this->gCurrentWrapper->CreateFile(result.name, std::vector(data.begin(), data.end())); + + for(auto& entry : this->gCompanionFiles){ + auto output = (this->gCurrentDirectory / entry.first).string(); + std::replace(output.begin(), output.end(), '\\', '/'); + this->gCurrentWrapper->CreateFile(output, entry.second); + } + break; } + case ExportType::XML: case ExportType::Modding: { stream.str(""); stream.clear(); @@ -751,12 +695,24 @@ void Companion::ProcessFile(YAML::Node root) { create_directories(fs::path(dpath).parent_path()); } - SPDLOG_INFO(ogname); this->gModdedAssetPaths[ogname] = result.name; std::ofstream file(dpath, std::ios::binary); file.write(data.c_str(), data.size()); file.close(); + + for(auto& entry : this->gCompanionFiles){ + auto cpath = (Instance->GetOutputPath() / this->gCurrentDirectory / entry.first).string(); + std::replace(cpath.begin(), cpath.end(), '\\', '/'); + if(!exists(fs::path(cpath).parent_path())){ + create_directories(fs::path(cpath).parent_path()); + } + + std::ofstream cfile(cpath, std::ios::binary); + cfile.write(entry.second.data(), entry.second.size()); + cfile.close(); + } + break; } default: { @@ -765,6 +721,8 @@ void Companion::ProcessFile(YAML::Node root) { } } + this->gCompanionFiles.clear(); + if(result.node["offset"]) { auto alignment = GetSafeNode<uint32_t>(result.node, "alignment", impl->GetAlignment()); if(!endptr.has_value()) { @@ -810,7 +768,7 @@ void Companion::ProcessFile(YAML::Node root) { auto fsout = fs::path(this->gConfig.outputPath); - if(this->gConfig.exporterType == ExportType::Modding) { + if(this->gConfig.exporterType == ExportType::Modding || this->gConfig.exporterType == ExportType::XML) { fsout /= "modding.yml"; YAML::Node modding; @@ -1087,6 +1045,7 @@ void Companion::Process() { this->gConfig.outputPath = opath && opath["code"] ? opath["code"].as<std::string>() : "code"; break; } + case ExportType::XML: case ExportType::Modding: { this->gConfig.outputPath = modding_path; break; @@ -1310,9 +1269,7 @@ std::optional<std::tuple<std::string, YAML::Node>> Companion::RegisterAsset(cons auto entry = std::make_tuple(output, node); this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = entry; - - - + return entry; } @@ -1459,6 +1416,11 @@ std::optional<std::vector<std::tuple<std::string, YAML::Node>>> Companion::GetNo } +void Companion::RegisterCompanionFile(const std::string path, std::vector<char> data) { + this->gCompanionFiles[path] = data; + SPDLOG_TRACE("Registered companion file {}", path); +} + std::string Companion::NormalizeAsset(const std::string& name) const { auto path = fs::path(this->gCurrentFile).stem().string() + "_" + name; return path; @@ -1493,7 +1455,6 @@ std::optional<YAML::Node> Companion::AddAsset(YAML::Node asset) { const auto symbol = GetSafeNode<std::string>(asset, "symbol", ""); const auto decl = this->GetNodeByAddr(offset); - if(decl.has_value()) { return std::get<1>(decl.value()); } diff --git a/src/Companion.h b/src/Companion.h index eb51783..6f14708 100644 --- a/src/Companion.h +++ b/src/Companion.h @@ -154,6 +154,7 @@ public: static void Pack(const std::string& folder, const std::string& output); std::string NormalizeAsset(const std::string& name) const; std::string RelativePath(const std::string& path) const; + void RegisterCompanionFile(const std::string path, std::vector<char> data); TorchConfig& GetConfig() { return this->gConfig; } SWrapper* GetCurrentWrapper() { return this->gCurrentWrapper; } @@ -190,6 +191,7 @@ private: std::vector<std::string> gCurrentExternalFiles; std::unordered_set<std::string> gProcessedFiles; + std::unordered_map<std::string, std::vector<char>> gCompanionFiles; std::unordered_map<std::string, std::vector<ParseResultData>> gParseResults; std::unordered_map<std::string, std::string> gModdedAssetPaths; diff --git a/src/factories/BaseFactory.h b/src/factories/BaseFactory.h index b8cecc6..3fdb1cf 100644 --- a/src/factories/BaseFactory.h +++ b/src/factories/BaseFactory.h @@ -11,10 +11,13 @@ #include <variant> #include <optional> #include <yaml-cpp/yaml.h> +#include <filesystem> #include <strhash64/StrHash64.h> #include "lib/binarytools/BinaryWriter.h" #include "lib/binarytools/BinaryReader.h" +namespace fs = std::filesystem; + #define REGISTER(type, c) { ExportType::type, std::make_shared<c>() }, #define SEGMENT_OFFSET(a) ((uint32_t)(a) & 0x00FFFFFF) @@ -37,7 +40,8 @@ enum class ExportType { Header, Code, Binary, - Modding + Modding, + XML }; template<typename T> diff --git a/src/factories/naudio/v1/AudioContext.cpp b/src/factories/naudio/v1/AudioContext.cpp index 7954aef..b4f72e2 100644 --- a/src/factories/naudio/v1/AudioContext.cpp +++ b/src/factories/naudio/v1/AudioContext.cpp @@ -62,8 +62,9 @@ TunedSample AudioContext::LoadTunedSample(LUS::BinaryReader& reader, uint32_t pa node["parent"] = parent; node["offset"] = parent + sampleAddr; node["tuning"] = tuning; + SPDLOG_INFO("Tuning {}", tuning); node["sampleBankId"] = sampleBankId; - node["symbol"] = StringHelper::Sprintf("Sample_P_%X_O_%X_B_%d", parent, parent + sampleAddr, sampleBankId); + // node["symbol"] = StringHelper::Sprintf("%d_Sample_P_%X_B_%d", parent + sampleAddr, parent, sampleBankId); Companion::Instance->AddAsset(node); return { parent + sampleAddr, sampleBankId, tuning }; @@ -84,4 +85,59 @@ uint64_t AudioContext::GetPathByAddr(uint32_t addr) { SPDLOG_INFO("Failed to find path for 0x{:X}", addr); throw std::runtime_error("Failed to find node by addr"); } +} + +const char* AudioContext::GetMediumStr(uint8_t medium) { + switch (medium) { + case 0: + return "Ram"; + case 1: + return "Unk"; + case 2: + return "Cart"; + case 3: + return "Disk"; + case 5: + return "RamUnloaded"; + default: + return "ERROR"; + } +} + +const char* AudioContext::GetCachePolicyStr(uint8_t policy) { + switch (policy) { + case 0: + return "Temporary"; + case 1: + return "Persistent"; + case 2: + return "Either"; + case 3: + return "Permanent"; + default: + return "ERROR"; + } +} + +const char* AudioContext::GetCodecStr(uint8_t codec) { + switch (codec) { + case 0: + return "ADPCM"; + case 1: + return "S8"; + case 2: + return "S16MEM"; + case 3: + return "ADPCMSMALL"; + case 4: + return "REVERB"; + case 5: + return "S16"; + case 6: + return "UNK6"; + case 7: + return "UNK7"; + default: + return "ERROR"; + } }
\ No newline at end of file diff --git a/src/factories/naudio/v1/AudioContext.h b/src/factories/naudio/v1/AudioContext.h index 84e5337..a965f10 100644 --- a/src/factories/naudio/v1/AudioContext.h +++ b/src/factories/naudio/v1/AudioContext.h @@ -20,6 +20,10 @@ public: static LUS::BinaryReader MakeReader(AudioTableType type, uint32_t offset); static TunedSample LoadTunedSample(LUS::BinaryReader& reader, uint32_t parent, uint32_t sampleBankId); static uint64_t GetPathByAddr(uint32_t addr); + + static const char* GetMediumStr(uint8_t medium); + static const char* GetCachePolicyStr(uint8_t policy); + static const char* GetCodecStr(uint8_t codec); }; class AudioContextFactory : public BaseFactory { diff --git a/src/factories/naudio/v1/AudioTableFactory.cpp b/src/factories/naudio/v1/AudioTableFactory.cpp index 96d6954..00f0f5c 100644 --- a/src/factories/naudio/v1/AudioTableFactory.cpp +++ b/src/factories/naudio/v1/AudioTableFactory.cpp @@ -124,7 +124,6 @@ std::optional<std::shared_ptr<IParsedData>> AudioTableFactory::parse(std::vector SPDLOG_INFO("addr: {}", baddr); std::vector<AudioTableEntry> entries; - auto parent = AudioContext::tableOffsets[AudioTableType::SEQ_TABLE]; for(size_t i = 0; i < count; i++){ auto addr = reader.ReadUInt32(); @@ -141,12 +140,20 @@ std::optional<std::shared_ptr<IParsedData>> AudioTableFactory::parse(std::vector YAML::Node font; font["type"] = "NAUDIO:V1:SOUND_FONT"; font["offset"] = addr; + font["id"] = (uint32_t) i; + font["medium"] = (int32_t) medium; + font["policy"] = (int32_t) policy; + font["sd1"] = (int32_t) sd1; + font["sd2"] = (int32_t) sd2; + font["sd3"] = (int32_t) sd3; + Companion::Instance->AddAsset(font); std::string path = font["vpath"].as<std::string>(); crc = CRC64(path.c_str()); break; } case AudioTableType::SEQ_TABLE: { + auto parent = AudioContext::tableOffsets[AudioTableType::SEQ_TABLE]; if(size != 0){ YAML::Node seq; seq["type"] = "BLOB"; diff --git a/src/factories/naudio/v1/InstrumentFactory.cpp b/src/factories/naudio/v1/InstrumentFactory.cpp index acfada1..10ba852 100644 --- a/src/factories/naudio/v1/InstrumentFactory.cpp +++ b/src/factories/naudio/v1/InstrumentFactory.cpp @@ -1,6 +1,8 @@ #include "InstrumentFactory.h" #include "utils/Decompressor.h" #include "Companion.h" +#include "EnvelopeFactory.h" +#include <tinyxml2.h> ExportResult InstrumentHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { const auto symbol = GetSafeNode(node, "symbol", entryName); @@ -40,6 +42,67 @@ ExportResult InstrumentBinaryExporter::Export(std::ostream &write, std::shared_p return std::nullopt; } +ExportResult InstrumentXMLExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto writer = LUS::BinaryWriter(); + auto instrument = std::static_pointer_cast<InstrumentData>(raw); + + auto envelopeData = std::static_pointer_cast<EnvelopeData>(Companion::Instance->GetParseDataByAddr(instrument->envelope)->data.value()); + + auto path = fs::path(*replacement); + + *replacement += ".meta"; + + tinyxml2::XMLDocument inst; + tinyxml2::XMLElement* root = inst.NewElement("Instrument"); + root->SetAttribute("NormalRangeLo", instrument->normalRangeLo); + root->SetAttribute("NormalRangeHi", instrument->normalRangeLo); + root->SetAttribute("ReleaseRate", instrument->adsrDecayIndex); + + tinyxml2::XMLElement* envelopes = inst.NewElement("Envelopes"); + for(auto& point : envelopeData->points){ + tinyxml2::XMLElement* pointEntry = envelopes->InsertNewChildElement("Point"); + pointEntry->SetAttribute("Delay", point.delay); + pointEntry->SetAttribute("Arg", point.arg); + envelopes->InsertEndChild(pointEntry); + } + + auto lowSample = instrument->lowPitchTunedSample; + auto normSample = instrument->normalPitchTunedSample; + auto highSample = instrument->highPitchTunedSample; + + if(lowSample.sample != 0 && lowSample.tuning != 0.0f){ + tinyxml2::XMLElement* low = inst.NewElement("LowPitchSample"); + low->SetAttribute("Tuning", lowSample.tuning); + low->SetAttribute("Path", std::get<std::string>(Companion::Instance->GetNodeByAddr(lowSample.sample).value()).c_str()); + + root->InsertEndChild(low); + } + + if(normSample.sample != 0 && normSample.tuning != 0.0f) { + tinyxml2::XMLElement* normal = inst.NewElement("NormalPitchSample"); + normal->SetAttribute("Tuning", normSample.tuning); + normal->SetAttribute("Path", std::get<std::string>(Companion::Instance->GetNodeByAddr(normSample.sample).value()).c_str()); + + root->InsertEndChild(normal); + } + + if(highSample.sample != 0 && highSample.tuning != 0.0f) { + tinyxml2::XMLElement* high = inst.NewElement("HighPitchSample"); + high->SetAttribute("Tuning", highSample.tuning); + high->SetAttribute("Path", std::get<std::string>(Companion::Instance->GetNodeByAddr(highSample.sample).value()).c_str()); + + root->InsertEndChild(high); + } + + inst.InsertEndChild(root); + + tinyxml2::XMLPrinter printer; + inst.Accept(&printer); + write.write(printer.CStr(), printer.CStrSize() - 1); + + return std::nullopt; +} + std::optional<std::shared_ptr<IParsedData>> InstrumentFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { auto offset = GetSafeNode<uint32_t>(node, "offset"); auto parent = GetSafeNode<uint32_t>(node, "parent"); diff --git a/src/factories/naudio/v1/InstrumentFactory.h b/src/factories/naudio/v1/InstrumentFactory.h index 9af9295..245b6d9 100644 --- a/src/factories/naudio/v1/InstrumentFactory.h +++ b/src/factories/naudio/v1/InstrumentFactory.h @@ -27,6 +27,10 @@ class InstrumentCodeExporter : public BaseExporter { ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; }; +class InstrumentXMLExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + class InstrumentFactory : public BaseFactory { public: std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; diff --git a/src/factories/naudio/v1/SampleFactory.cpp b/src/factories/naudio/v1/SampleFactory.cpp index 47e91b7..e8c7fc9 100644 --- a/src/factories/naudio/v1/SampleFactory.cpp +++ b/src/factories/naudio/v1/SampleFactory.cpp @@ -1,6 +1,9 @@ #include "SampleFactory.h" #include "AudioConverter.h" #include "Companion.h" +#include <tinyxml2.h> +#include "LoopFactory.h" +#include "BookFactory.h" #include <factories/naudio/v0/AIFCDecode.h> ExportResult NSampleHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { @@ -58,10 +61,68 @@ ExportResult NSampleModdingExporter::Export(std::ostream &write, std::shared_ptr return std::nullopt; } +ExportResult NSampleXMLExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto entry = std::static_pointer_cast<NSampleData>(raw); + auto loop = std::static_pointer_cast<ADPCMLoopData>(Companion::Instance->GetParseDataByAddr(entry->loop)->data.value()); + auto book = std::static_pointer_cast<ADPCMBookData>(Companion::Instance->GetParseDataByAddr(entry->book)->data.value()); + + auto path = fs::path(*replacement); + + *replacement += ".meta"; + + tinyxml2::XMLDocument sample; + tinyxml2::XMLElement* root = sample.NewElement("Sample"); + root->SetAttribute("Version", 0); + root->SetAttribute("Codec", AudioContext::GetCodecStr(entry->codec)); + root->SetAttribute("Medium", AudioContext::GetMediumStr(entry->medium)); + root->SetAttribute("bit26", entry->unk); + root->SetAttribute("Tuning", entry->tuning); + root->SetAttribute("Size", entry->size); + root->SetAttribute("Relocated", 0); + root->SetAttribute("Path", path.c_str()); + + tinyxml2::XMLElement* adpcmLoop = sample.NewElement("ADPCMLoop"); + adpcmLoop->SetAttribute("Start", loop->start); + adpcmLoop->SetAttribute("End", loop->end); + adpcmLoop->SetAttribute("Count", loop->count); + if (loop->count != 0) { + for (auto& state : loop->predictorState) { + tinyxml2::XMLElement* loopEntry = adpcmLoop->InsertNewChildElement("Predictor"); + loopEntry->SetAttribute("State", state); + adpcmLoop->InsertEndChild(loopEntry); + } + } + root->InsertEndChild(adpcmLoop); + + tinyxml2::XMLElement* adpcmBook = sample.NewElement("ADPCMBook"); + adpcmBook->SetAttribute("Order", book->order); + adpcmBook->SetAttribute("Npredictors", book->numPredictors); + + for (auto& page : book->book) { + tinyxml2::XMLElement* bookEntry = adpcmBook->InsertNewChildElement("Book"); + bookEntry->SetAttribute("Page", page); + adpcmBook->InsertEndChild(bookEntry); + } + root->InsertEndChild(adpcmBook); + sample.InsertEndChild(root); + + tinyxml2::XMLPrinter printer; + sample.Accept(&printer); + write.write(printer.CStr(), printer.CStrSize() - 1); + + auto tableEntry = AudioContext::tableData[AudioTableType::SAMPLE_TABLE]->entries[entry->sampleBankId]; + auto buffer = AudioContext::data[AudioTableType::SAMPLE_TABLE]; + auto sampleData = buffer.data() + tableEntry.addr + entry->sampleAddr; + std::vector<char> data(sampleData, sampleData + entry->size); + Companion::Instance->RegisterCompanionFile(path.filename().string(), data); + + return std::nullopt; +} + std::optional<std::shared_ptr<IParsedData>> NSampleFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { auto offset = GetSafeNode<uint32_t>(node, "offset"); auto parent = GetSafeNode<uint32_t>(node, "parent"); - auto tuning = GetSafeNode<float>(node, "tuning", 1.0f); + auto tuning = GetSafeNode<float>(node, "tuning", 0.0f); auto sampleRate = GetSafeNode<uint32_t>(node, "sampleRate", 0); auto sampleBankId = GetSafeNode<uint32_t>(node, "sampleBankId"); diff --git a/src/factories/naudio/v1/SampleFactory.h b/src/factories/naudio/v1/SampleFactory.h index d447cc2..eb7cff5 100644 --- a/src/factories/naudio/v1/SampleFactory.h +++ b/src/factories/naudio/v1/SampleFactory.h @@ -36,6 +36,10 @@ class NSampleModdingExporter : public BaseExporter { ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; }; +class NSampleXMLExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + class NSampleFactory : public BaseFactory { public: std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; @@ -45,6 +49,7 @@ public: REGISTER(Binary, NSampleBinaryExporter) REGISTER(Code, NSampleCodeExporter) REGISTER(Modding, NSampleModdingExporter) + REGISTER(XML, NSampleXMLExporter) }; } bool SupportModdedAssets() override { return true; } diff --git a/src/factories/naudio/v1/SoundFontFactory.cpp b/src/factories/naudio/v1/SoundFontFactory.cpp index ca84065..81419fa 100644 --- a/src/factories/naudio/v1/SoundFontFactory.cpp +++ b/src/factories/naudio/v1/SoundFontFactory.cpp @@ -3,6 +3,10 @@ #include "Companion.h" #include "spdlog/spdlog.h" #include "utils/Decompressor.h" +#include <tinyxml2.h> +#include "DrumFactory.h" +#include "EnvelopeFactory.h" +#include "InstrumentFactory.h" ExportResult SoundFontHeaderExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement) { const auto symbol = GetSafeNode(node, "symbol", entryName); @@ -45,6 +49,137 @@ ExportResult SoundFontBinaryExporter::Export(std::ostream &write, std::shared_pt return std::nullopt; } +void WriteInstrument(tinyxml2::XMLElement* parent, uint32_t offset){ + auto instrument = std::static_pointer_cast<InstrumentData>(Companion::Instance->GetParseDataByAddr(offset)->data.value()); + auto envelopeData = std::static_pointer_cast<EnvelopeData>(Companion::Instance->GetParseDataByAddr(instrument->envelope)->data.value()); + + tinyxml2::XMLElement* root = parent->InsertNewChildElement("Instrument"); + root->SetAttribute("NormalRangeLo", instrument->normalRangeLo); + root->SetAttribute("NormalRangeHi", instrument->normalRangeLo); + root->SetAttribute("ReleaseRate", instrument->adsrDecayIndex); + + tinyxml2::XMLElement* envelopes = root->InsertNewChildElement("Envelopes"); + for(size_t i = 0; i < envelopeData->points.size(); i++){ + auto point = envelopeData->points[i]; + tinyxml2::XMLElement* pointEntry = envelopes->InsertNewChildElement("Envelope"); + pointEntry->SetAttribute("Delay", point.delay); + pointEntry->SetAttribute("Arg", point.arg); + envelopes->InsertEndChild(pointEntry); + } + + auto lowSample = instrument->lowPitchTunedSample; + auto normSample = instrument->normalPitchTunedSample; + auto highSample = instrument->highPitchTunedSample; + + if(lowSample.sample != 0 && lowSample.tuning != 0.0f){ + tinyxml2::XMLElement* low = root->InsertNewChildElement("LowPitchSample"); + low->SetAttribute("Tuning", lowSample.tuning); + low->SetAttribute("SampleRef", std::get<std::string>(Companion::Instance->GetNodeByAddr(lowSample.sample).value()).c_str()); + + root->InsertEndChild(low); + } + + if(normSample.sample != 0 && normSample.tuning != 0.0f) { + tinyxml2::XMLElement* normal = root->InsertNewChildElement("NormalPitchSample"); + normal->SetAttribute("Tuning", normSample.tuning); + normal->SetAttribute("SampleRef", std::get<std::string>(Companion::Instance->GetNodeByAddr(normSample.sample).value()).c_str()); + + root->InsertEndChild(normal); + } + + if(highSample.sample != 0 && highSample.tuning != 0.0f) { + tinyxml2::XMLElement* high = root->InsertNewChildElement("HighPitchSample"); + high->SetAttribute("Tuning", highSample.tuning); + high->SetAttribute("SampleRef", std::get<std::string>(Companion::Instance->GetNodeByAddr(highSample.sample).value()).c_str()); + + root->InsertEndChild(high); + } + + parent->InsertEndChild(root); +} + +void WriteDrum(tinyxml2::XMLElement* parent, uint32_t offset){ + auto drum = std::static_pointer_cast<DrumData>(Companion::Instance->GetParseDataByAddr(offset)->data.value()); + auto envelopeData = std::static_pointer_cast<EnvelopeData>(Companion::Instance->GetParseDataByAddr(drum->envelope)->data.value()); + auto sample = drum->tunedSample; + + tinyxml2::XMLElement* root = parent->InsertNewChildElement("Drum"); + root->SetAttribute("ReleaseRate", drum->adsrDecayIndex); + root->SetAttribute("Pan", drum->pan); + root->SetAttribute("Loaded", 0); + root->SetAttribute("SampleRef", std::get<std::string>(Companion::Instance->GetNodeByAddr(sample.sample).value()).c_str()); + root->SetAttribute("Tuning", sample.tuning); + + tinyxml2::XMLElement* envelopes = root->InsertNewChildElement("Envelopes"); + envelopes->SetAttribute("Count", (uint32_t) envelopeData->points.size()); + for(size_t i = 0; i < envelopeData->points.size(); i++){ + auto point = envelopeData->points[i]; + tinyxml2::XMLElement* pointEntry = envelopes->InsertNewChildElement("Envelope"); + pointEntry->SetAttribute("Delay", point.delay); + pointEntry->SetAttribute("Arg", point.arg); + envelopes->InsertEndChild(pointEntry); + } + + parent->InsertEndChild(root); +} + +ExportResult SoundFontXMLExporter::Export(std::ostream &write, std::shared_ptr<IParsedData> raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto font = std::static_pointer_cast<SoundFontData>(raw); + auto id = GetSafeNode<uint32_t>(node, "id"); + auto medium = (int8_t) GetSafeNode<int32_t>(node, "medium"); + auto policy = (int8_t) GetSafeNode<int32_t>(node, "policy"); + auto sd1 = (int16_t) GetSafeNode<int32_t>(node, "sd1"); + auto sd2 = (int16_t) GetSafeNode<int32_t>(node, "sd2"); + auto sd3 = (int16_t) GetSafeNode<int32_t>(node, "sd3"); + + *replacement += ".meta"; + + tinyxml2::XMLDocument doc; + tinyxml2::XMLElement* root = doc.NewElement("SoundFont"); + root->SetAttribute("Version", 0); + root->SetAttribute("Num", id); + root->SetAttribute("Medium", AudioContext::GetMediumStr(medium)); + root->SetAttribute("CachePolicy", AudioContext::GetCachePolicyStr(policy)); + root->SetAttribute("Data1", sd1); + root->SetAttribute("Data2", sd2); + root->SetAttribute("Data3", sd3); + + tinyxml2::XMLElement* drums = doc.NewElement("Drums"); + drums->SetAttribute("Count", font->numDrums); + + for(auto& drum : font->drums){ + if(drum == 0){ + continue; + } + WriteDrum(drums, drum); + } + root->InsertEndChild(drums); + + tinyxml2::XMLElement* insts = doc.NewElement("Instruments"); + insts->SetAttribute("Count", font->numInstruments); + + for(auto& inst : font->instruments){ + if(inst == 0){ + continue; + } + WriteInstrument(insts, inst); + } + root->InsertEndChild(insts); + + // This is ignored since it's an exclusive feature of OoT and MM + tinyxml2::XMLElement* sfx = doc.NewElement("SfxTable"); + sfx->SetAttribute("Count", 0); + root->InsertEndChild(sfx); + + doc.InsertEndChild(root); + + tinyxml2::XMLPrinter printer; + doc.Accept(&printer); + write.write(printer.CStr(), printer.CStrSize() - 1); + + return std::nullopt; +} + std::optional<std::shared_ptr<IParsedData>> SoundFontFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) { auto offset = GetSafeNode<uint32_t>(node, "offset"); auto entry = AudioContext::tables[AudioTableType::FONT_TABLE].at(offset); diff --git a/src/factories/naudio/v1/SoundFontFactory.h b/src/factories/naudio/v1/SoundFontFactory.h index 256f2ce..72e1190 100644 --- a/src/factories/naudio/v1/SoundFontFactory.h +++ b/src/factories/naudio/v1/SoundFontFactory.h @@ -24,6 +24,10 @@ class SoundFontCodeExporter : public BaseExporter { ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; }; +class SoundFontXMLExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr<IParsedData> data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + class SoundFontFactory : public BaseFactory { public: std::optional<std::shared_ptr<IParsedData>> parse(std::vector<uint8_t>& buffer, YAML::Node& data) override; @@ -32,6 +36,7 @@ public: REGISTER(Header, SoundFontHeaderExporter) REGISTER(Binary, SoundFontBinaryExporter) REGISTER(Code, SoundFontCodeExporter) + REGISTER(XML, SoundFontXMLExporter) }; } diff --git a/src/main.cpp b/src/main.cpp index ee1d92a..d3b6ffb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ int main(int argc, char *argv[]) { std::string target; std::string folder; bool otrMode = false; + bool xmlMode = false; bool debug = false; app.require_subcommand(); @@ -98,11 +99,16 @@ int main(int argc, char *argv[]) { } }); + modding_export->add_flag("-x,--xml", xmlMode, "XML Mode"); modding_export->add_option("<baserom.z64>", filename, "")->required()->check(CLI::ExistingFile); modding_export->parse_complete_callback([&] { const auto instance = Companion::Instance = new Companion(filename, false, debug); - instance->Init(ExportType::Modding); + if (xmlMode) { + instance->Init(ExportType::XML); + } else { + instance->Init(ExportType::Modding); + } }); try { |
