mirror of
https://github.com/izzy2lost/Torch.git
synced 2026-07-06 00:18:35 -07:00
Merge pull request #161 from HarbourMasters/audio_extraction
Custom samples support and XML support
This commit is contained in:
+14
-5
@@ -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)
|
||||
|
||||
+68
-107
@@ -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);
|
||||
if(node["type"]){
|
||||
const auto type = GetSafeNode<std::string>(node, "type");
|
||||
if(type == "NAUDIO:V0:SAMPLE"){
|
||||
AudioManager::Instance->bind_sample(node, output);
|
||||
}
|
||||
} 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(gCurrentSegmentNumber) {
|
||||
if (IS_SEGMENTED(node["offset"].as<uint32_t>()) == false) {
|
||||
node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as<uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
if(!gCurrentVirtualPath.empty()) {
|
||||
node["path"] = gCurrentVirtualPath;
|
||||
}
|
||||
|
||||
this->gAddrMap[this->gCurrentFile][node["offset"].as<uint32_t>()] = std::make_tuple(output, node);
|
||||
}
|
||||
|
||||
if(!node["offset"]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(gCurrentSegmentNumber) {
|
||||
if (IS_SEGMENTED(node["offset"].as<uint32_t>()) == false) {
|
||||
node["offset"] = (gCurrentSegmentNumber << 24) | node["offset"].as<uint32_t>();
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user