diff options
| author | KiritoDv <kiritodev01@gmail.com> | 2023-09-01 00:51:51 -0600 |
|---|---|---|
| committer | KiritoDv <kiritodev01@gmail.com> | 2023-09-01 00:51:51 -0600 |
| commit | 113472302904ac84d4ddf70df32ca245ad156bf2 (patch) | |
| tree | 9a82a79c2542749d26db93b414dc52e0ec72ed7f | |
| parent | 3bb2a8a13c4ff3b637bf02b9c01d632585b269bb (diff) | |
Migrated disassemble audio into audio manager class
| -rw-r--r-- | assets.json | 2 | ||||
| -rw-r--r-- | lib/binarytools/BinaryReader.cpp | 23 | ||||
| -rw-r--r-- | lib/binarytools/BinaryReader.h | 2 | ||||
| -rw-r--r-- | lib/hj/pyutils.h | 23 | ||||
| -rw-r--r-- | lib/hj/zip.h | 60 | ||||
| -rw-r--r-- | src/Companion.cpp | 4 | ||||
| -rw-r--r-- | src/audio/AudioManager.cpp | 442 | ||||
| -rw-r--r-- | src/audio/AudioManager.h | 135 | ||||
| -rw-r--r-- | src/factories/AudioFactory.cpp | 312 |
9 files changed, 693 insertions, 310 deletions
diff --git a/assets.json b/assets.json index 7ee9491..81bcd62 100644 --- a/assets.json +++ b/assets.json @@ -1121,7 +1121,7 @@ "sound/sequences/us/20_cutscene_ending.m64": [1882,{"us":[8174192]}], "sound/sequences/us/21_menu_file_select.m64": [781,{"us":[8176080]}], "sound/sequences/us/22_cutscene_lakitu.m64": [313,{"us":[8176864]}], -"@sound.aiff": { +"@sound_data": { "us": { "bank_sets": [8177184, 160], "sound_ctl": [5748512, 97856], diff --git a/lib/binarytools/BinaryReader.cpp b/lib/binarytools/BinaryReader.cpp index 3aff001..75044b0 100644 --- a/lib/binarytools/BinaryReader.cpp +++ b/lib/binarytools/BinaryReader.cpp @@ -2,6 +2,7 @@ #include "MemoryStream.h" #include <cmath> #include <stdexcept> +#include <locale> LUS::BinaryReader::BinaryReader(char* nBuffer, size_t nBufferSize) { mStream = std::make_shared<MemoryStream>(nBuffer, nBufferSize); @@ -113,6 +114,28 @@ uint64_t LUS::BinaryReader::ReadUInt64() { return result; } +unsigned short LUS::BinaryReader::ReadUShort() { + unsigned short result = 0; + mStream->Read((char*)&result, sizeof(unsigned short)); + + if (mEndianness != Endianness::Native) { + result = BSWAP16(result); + } + + return result; +} + +short LUS::BinaryReader::ReadShort() { + short result = 0; + mStream->Read((char*)&result, sizeof(short)); + + if (mEndianness != Endianness::Native) { + result = BSWAP16(result); + } + + return result; +} + float LUS::BinaryReader::ReadFloat() { float result = NAN; diff --git a/lib/binarytools/BinaryReader.h b/lib/binarytools/BinaryReader.h index 59eeb52..91e2a40 100644 --- a/lib/binarytools/BinaryReader.h +++ b/lib/binarytools/BinaryReader.h @@ -34,6 +34,8 @@ class BinaryReader { uint16_t ReadUInt16(); uint32_t ReadUInt32(); uint64_t ReadUInt64(); + unsigned short ReadUShort(); + short ReadShort(); float ReadFloat(); double ReadDouble(); std::string ReadString(); diff --git a/lib/hj/pyutils.h b/lib/hj/pyutils.h new file mode 100644 index 0000000..c5da4ee --- /dev/null +++ b/lib/hj/pyutils.h @@ -0,0 +1,23 @@ +#pragma once + +#include <vector> + +namespace PyUtils { +template<typename T> +std::vector<T> slice(std::vector<T> const &v, uint32_t m, uint32_t n = -1) { + auto first = v.cbegin() + m; + auto last = n == -1 ? v.cend() : v.cbegin() + n; + + std::vector<T> vec(first, last); + return vec; +} + +std::vector<uint32_t> range(uint32_t start, uint32_t end) { + std::vector<uint32_t> result; + for (uint32_t i = start; i < end; ++i) { + result.push_back(i); + } + return result; +} + +}
\ No newline at end of file diff --git a/lib/hj/zip.h b/lib/hj/zip.h new file mode 100644 index 0000000..0cc7ee2 --- /dev/null +++ b/lib/hj/zip.h @@ -0,0 +1,60 @@ +#pragma once +#include <algorithm> +#include <any> +#include <iostream> +#include <vector> +#include <tuple> + +template<class TupType, size_t... I> +void print_tuple(const TupType& _tup, std::index_sequence<I...>) +{ + std::cout << "("; + (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); + std::cout << ")\n"; +} + +template<class... T> +void print_tuple(const std::tuple<T...>& _tup) +{ + print_tuple(_tup, std::make_index_sequence<sizeof...(T)>()); +} + +template<typename T> +void variatic_min(std::vector<T> v, int &m) { + if (m > v.size()) m = v.size(); +} + +template<int N, typename... Ts> +using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type; + +template <typename... Result, std::size_t... Indices> +auto vec_to_tup_helper(const std::vector<std::any>& v, std::index_sequence<Indices...>) { + return std::make_tuple( + std::any_cast<NthTypeOf<Indices, Result...>>(v[Indices])... + ); +} + +template <typename ...Result> +std::tuple<Result...> vec_to_tup(std::vector<std::any>& values) { + return vec_to_tup_helper<Result...>(values, std::make_index_sequence<sizeof...(Result)>()); +} + +template <typename T> +void celem_at(std::vector<T> v, int i, std::vector<std::any> &r) { + r.push_back(v[i]); +} + +template<typename... Types> +std::vector<std::tuple<Types...>> zip(std::vector<Types>... args) { + int m = 0xFFFFFFFF; + std::vector<std::tuple<Types...>> res; + + (variatic_min(args, m), ...); + + for (int i = 0; i < m; i++) { + std::vector<std::any> vaux; + (celem_at(args, i, vaux), ...); + res.push_back(vec_to_tup<Types...>(vaux)); + } + return res; +}
\ No newline at end of file diff --git a/src/Companion.cpp b/src/Companion.cpp index 218f018..fe47842 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -2,6 +2,7 @@ #include "storm/SWrapper.h" #include "utils/MIODecoder.h" +#include "audio/AudioManager.h" #include "factories/RawFactory.h" #include "factories/BlobFactory.h" #include "factories/AudioFactory.h" @@ -36,6 +37,9 @@ void Companion::Start() { std::cout << "Detected Rom Size: " << this->gRomData.size() << '\n'; + AudioManager::Instance = new AudioManager(); + AudioManager::Instance->initialize(this->gRomData); + this->ProcessAssets(); } diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp new file mode 100644 index 0000000..1f3e382 --- /dev/null +++ b/src/audio/AudioManager.cpp @@ -0,0 +1,442 @@ +#include "AudioManager.h" + +#include <vector> +#include <sstream> +#include <fstream> +#include <iostream> +#include <filesystem> +#include "hj/pyutils.h" +#include "binarytools/BinaryReader.h" +#include "hj/zip.h" +#include <nlohmann/json.hpp> + +std::unordered_map<std::string, uint32_t> name_table; +AudioManager* AudioManager::Instance; + +std::string gen_name(const std::string& prefix){ + if(!name_table.contains(prefix)){ + name_table[prefix] = 0; + } + name_table[prefix] += 1; + return prefix + "-" + std::to_string(name_table[prefix]); +} + +AifcEntry SampleBank::AddSample(uint32_t addr, size_t sampleSize, const Book& book, const Loop& loop){ + assert(sampleSize % 2 == 0); + if(sampleSize % 9 != 0){ + assert(sampleSize % 9 == 1); + sampleSize -= 1; + } + + AifcEntry entry; + + if(this->entries.contains(addr)){ + entry = this->entries[addr]; + assert(entry.book == book); + assert(entry.loop == loop); + assert(entry.data.size() == sampleSize); + } else { + entry = { gen_name("aifc"), PyUtils::slice(this->data, addr, addr + sampleSize), book, loop }; + this->entries[addr] = entry; + } + + return entry; +} + +void Bank::print() const { + std::cout << "Bank: " << name << std::endl; + std::cout << "Instruments: " << std::to_string(insts.size()) << std::endl; + std::cout << "Drums: " << std::to_string(drums.size()) << std::endl; + std::cout << "Samples: " << std::to_string(samples.size()) << std::endl; + std::cout << "Envelopes: " << std::to_string(envelopes.size()) << std::endl; + std::cout << "All Instruments: " << std::to_string(allInsts.size()) << std::endl; + std::cout << "Inst Offsets: " << std::to_string(instOffsets.size()) << std::endl; + std::cout << "Sample Bank: " << sampleBank.name << std::endl; + std::cout << "Sample Bank Offset: " << std::to_string(sampleBank.offset) << std::endl; +} + +std::vector<Entry> AudioManager::parse_seq_file(std::vector<uint8_t>& buffer, uint32_t offset, bool isCTL){ + std::vector<Entry> entries; + LUS::BinaryReader reader((char*) buffer.data(), buffer.size()); + reader.SetEndianness(LUS::Endianness::Big); + reader.Seek(offset, LUS::SeekOffsetType::Start); + + unsigned short magic = reader.ReadUInt16(); + unsigned short num_entries = reader.ReadUInt16(); + uint32_t prev = ALIGN(4 + num_entries * 8, 16); + + assert(magic == (isCTL ? 1 : 2)); + + for (int i = 0; i < num_entries; ++i) { + reader.Seek((offset + 4) + (i * 8), LUS::SeekOffsetType::Start); + uint32_t addr = reader.ReadUInt32(); + uint32_t length = reader.ReadUInt32(); + + if(isCTL){ + assert(addr == prev); + } else { + assert(addr <= prev); + } + + prev = std::max(prev, addr + length); + entries.push_back({addr, length}); + } + + reader.Close(); + return entries; +} + +CTLHeader AudioManager::parse_ctl_header(std::vector<uint8_t>& data){ + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(LUS::Endianness::Big); + + CTLHeader header = { reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32() }; + + reader.Close(); + return header; +} + +std::optional<Sound> AudioManager::parse_sound(std::vector<uint8_t> data) { + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(LUS::Endianness::Big); + + uint32_t addr = reader.ReadUInt32(); + float tuning = reader.ReadFloat(); + + if(addr == 0){ + assert(tuning == 0.0f); + return std::nullopt; + } + + Sound sound = { addr, tuning }; + + reader.Close(); + return sound; +} + +Drum AudioManager::parse_drum(std::vector<uint8_t>& data, uint32_t addr) { + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(LUS::Endianness::Big); + std::string name = gen_name("drum"); + + uint8_t releaseRate = reader.ReadInt8(); + uint8_t pan = reader.ReadInt8(); + + reader.Seek(12, LUS::SeekOffsetType::Start); + Sound sound = parse_sound(PyUtils::slice(data, 4, 12)).value(); + uint32_t envOffset = reader.ReadInt32(); + assert(envOffset != 0); + + Drum drum = { name, addr, releaseRate, pan, envOffset, sound }; + + return drum; +} + +Inst AudioManager::parse_inst(std::vector<uint8_t>& data, uint32_t addr) { + std::string name = gen_name("inst"); + uint8_t normalRangeLo = data[1]; + uint8_t normalRangeHi = data[2]; + uint8_t releaseRate = data[3]; + + uint32_t envAddr; + memcpy(&envAddr, (char*) data.data() + 4, 4); + envAddr = BSWAP32(envAddr); + + assert(envAddr != 0); + + auto soundLo = parse_sound(PyUtils::slice(data, 8, 16)); + auto soundMed = parse_sound(PyUtils::slice(data, 16, 24)); + auto soundHi = parse_sound(PyUtils::slice(data, 24)); + + if (soundLo == std::nullopt) { + assert(normalRangeLo == 0); + } + if (soundHi == std::nullopt) { + assert(normalRangeHi == 127); + } + + Inst inst = { name, addr, releaseRate, normalRangeLo, normalRangeHi, envAddr, soundLo, soundMed, soundHi }; + return inst; +} + +Loop AudioManager::parse_loop(uint32_t addr, std::vector<uint8_t>& bankData){ + LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); + reader.SetEndianness(LUS::Endianness::Big); + reader.Seek(addr, LUS::SeekOffsetType::Start); + + std::optional<std::vector<short>> state = std::nullopt; + uint32_t start = reader.ReadUInt32(); + uint32_t end = reader.ReadUInt32(); + int32_t count = reader.ReadInt32(); + uint32_t pad = reader.ReadUInt32(); + if(count != 0){ + state = std::vector<short>(); + for (size_t i = 0; i < 16; ++i) { + state.value().push_back(reader.ReadShort()); + } + } + Loop loop = { start, end, count, pad, state }; + return loop; +} + +Book AudioManager::parse_book(uint32_t addr, std::vector<uint8_t>& bankData){ + LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); + reader.SetEndianness(LUS::Endianness::Big); + reader.Seek(addr, LUS::SeekOffsetType::Start); + + uint32_t order = reader.ReadInt32(); + uint32_t npredictors = reader.ReadInt32(); + std::vector<short> table; + std::vector<uint8_t> tableData = PyUtils::slice(bankData, addr + 8, addr + 8 + 16 * order * npredictors); + for (size_t i = 0; i < ( 16 * order * npredictors ); i += 2) { + short dtable; + memcpy(&dtable, tableData.data() + i, 2); + table.push_back(BSWAP16(dtable)); + } + + Book book = { order, npredictors, table }; + return book; +} + +AifcEntry AudioManager::parse_sample(std::vector<uint8_t>& data, std::vector<uint8_t>& bankData, SampleBank& sampleBank){ + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(LUS::Endianness::Big); + + uint32_t zero = reader.ReadUInt32(); + uint32_t addr = reader.ReadUInt32(); + uint32_t loop = reader.ReadUInt32(); + uint32_t book = reader.ReadUInt32(); + uint32_t sampleSize = reader.ReadUInt32(); + assert(zero == 0); + assert(loop != 0); + assert(book != 0); + + Loop loopData = parse_loop(loop, bankData); + Book bookData = parse_book(book, bankData); + + reader.Close(); + return sampleBank.AddSample(addr, sampleSize, bookData, loopData); +} + + +std::vector<EnvelopeData> AudioManager::parse_envelope(uint32_t addr, std::vector<uint8_t>& dataBank){ + std::vector<EnvelopeData> entries; + LUS::BinaryReader reader((char*) dataBank.data(), dataBank.size()); + reader.SetEndianness(LUS::Endianness::Big); + + while(true){ + reader.Seek(addr, LUS::SeekOffsetType::Start); + unsigned short delay = reader.ReadUShort(); + unsigned short arg = reader.ReadUShort(); + entries.emplace_back(delay, arg); + addr += 4; + if (1 <= (-delay) % ((uint32_t) pow(2, 16)) <= 3.0){ + break; + } + } + + reader.Close(); + return entries; +} + +Bank AudioManager::parse_ctl(CTLHeader header, std::vector<uint8_t> data, SampleBank bank, uint32_t index) { + name_table.clear(); + std::ostringstream ss; + ss << std::hex << std::setw(2) << std::setfill('0') << index; + std::string name = ss.str(); + uint32_t numInstruments = header.instruments; + uint32_t numDrums = header.numDrums; + char* rawData = (char*) data.data(); + + uint32_t drumBaseAddr; + memcpy(&drumBaseAddr, rawData, 4); + drumBaseAddr = BSWAP32(drumBaseAddr); + + std::vector<uint32_t> drumOffsets; + + if(numDrums != 0){ + for (size_t i = 0; i < numDrums; ++i) { + uint32_t drumOffset; + memcpy(&drumOffset, rawData + drumBaseAddr + (i * 4), 4); + drumOffsets.push_back(BSWAP32(drumOffset)); + } + } + + uint32_t instrumentBaseAddr = 4; + std::vector<uint32_t> instrumentOffsets; + std::vector<uint32_t> instrumentList; + + for (size_t i = 0; i < numInstruments; ++i) { + uint32_t instOffset; + memcpy(&instOffset, rawData + (instrumentBaseAddr + (i * 4)), 4); + instOffset = BSWAP32(instOffset); + if(instOffset == 0){ + instrumentList.push_back(NONE); + } else { + instrumentOffsets.push_back(instOffset); + instrumentList.push_back(instOffset); + } + } + + std::sort(instrumentOffsets.begin(), instrumentOffsets.end()); + + std::vector<Inst> insts; + for(auto &offset : instrumentOffsets){ + auto rInst = PyUtils::slice(data, offset, offset + 32); + Inst inst = parse_inst(rInst, offset); + insts.push_back(inst); + } + + std::vector<Drum> drums; + for(auto &offset : drumOffsets){ + auto rDrum = PyUtils::slice(data, offset, offset + 16); + Drum drum = parse_drum(rDrum, offset); + drums.push_back(drum); + } + + auto envOffsets = std::vector<uint32_t>(); + auto sampleOffsets = std::vector<uint32_t>(); + auto tunings = std::unordered_map<uint32_t, float>(); + + for(auto &inst : insts){ + for(auto &sound : {inst.soundLo, inst.soundMed, inst.soundHi}){ + if(sound.has_value()){ + sampleOffsets.push_back(sound.value().offset); + tunings[sound.value().offset] = sound.value().tuning; + } + } + envOffsets.push_back(inst.envelope); + } + for(auto &drum : drums){ + sampleOffsets.push_back(drum.sound.offset); + tunings[drum.sound.offset] = drum.sound.tuning; + envOffsets.push_back(drum.envelope); + } + // Put drums somewhere in the middle of the instruments to make sample + // addresses come in increasing order. (This logic isn't totally right, + // but it works for our purposes.) + std::vector<std::variant<Inst, std::vector<Drum>>> allInsts; + bool needDrums = !drums.empty(); + for(auto &inst : insts){ + std::vector<std::optional<Sound>> sounds = {inst.soundLo, inst.soundMed, inst.soundHi}; + + if(needDrums && std::any_of(sounds.cbegin(), sounds.cend(), [&drums](std::optional<Sound> sound){ return sound.has_value() && sound.value().offset > drums[0].sound.offset; })){ + allInsts.emplace_back(drums); + needDrums = false; + } + + allInsts.emplace_back(inst); + } + + + if(needDrums){ + allInsts.emplace_back(drums); + } + + std::unordered_map<uint32_t, AifcEntry> samples; + std::sort(sampleOffsets.begin(), sampleOffsets.end()); + for(auto &offset : sampleOffsets){ + auto rSample = PyUtils::slice(data, offset, offset + 20); + AifcEntry sample = parse_sample(rSample, data, bank); + for(auto &tuning : tunings){ + sample.tunings.push_back(tuning.second); + } + samples[offset] = sample; + } + + std::unordered_map<uint32_t, std::vector<EnvelopeData>> envData; + std::vector<uint32_t> usedEnvOffsets; + std::sort(envOffsets.begin(), envOffsets.end()); + for(auto &offset : envOffsets){ + auto env = parse_envelope(offset, data); + envData[offset] = env; + for(int i = 0; i < ALIGN(env.size(), 4); i++){ + usedEnvOffsets.push_back(offset + (i * 4)); + } + } + + std::vector<uint32_t> unusedEnvOffsets; + if(!usedEnvOffsets.empty()){ + size_t min = std::min_element(usedEnvOffsets.begin(), usedEnvOffsets.end()) - usedEnvOffsets.begin(); + size_t max = std::max_element(usedEnvOffsets.begin(), usedEnvOffsets.end()) - usedEnvOffsets.begin(); + for(size_t idx = min + 4; idx < max; idx += 4){ + uint32_t addr = usedEnvOffsets[idx]; + if(std::find(usedEnvOffsets.begin(), usedEnvOffsets.end(), addr) == usedEnvOffsets.end()){ + unusedEnvOffsets.push_back(addr); + uint32_t stubMarker; + memcpy(&stubMarker, rawData + addr, 4); + stubMarker = BSWAP32(stubMarker); + assert(stubMarker == 0); + auto env = parse_envelope(addr, data); + envData[addr] = env; + for(int i = 0; i < ALIGN(env.size(), 4); i++){ + usedEnvOffsets.push_back(addr + (i * 4)); + } + } + } + } + + std::unordered_map<uint32_t, Envelope> envelopes; + for(auto &entry : envData){ + Envelope env = { gen_name("envelope"), entry.second }; + envelopes[entry.first] = env; + } + + Bank bankData = { name, bank, insts, drums, allInsts, instrumentList, envelopes, samples }; + return bankData; +} + +TBLFile AudioManager::parse_tbl(std::vector<uint8_t>& data, std::vector<Entry>& entries) { + TBLFile tbl; + std::unordered_map<uint32_t, std::string> cache; + for(auto &entry : entries){ + if(!cache.contains(entry.offset)){ + std::string name = gen_name("sample_bank"); + SampleBank sampleBank = { + name, entry.offset, PyUtils::slice(data, entry.offset, entry.offset + entry.length) + }; + tbl.banks.push_back(sampleBank); + tbl.map[name] = tbl.banks.size() - 1; + cache[entry.offset] = name; + } + tbl.tbls.push_back(cache[entry.offset]); + } + cache.clear(); + return tbl; +} + +void AudioManager::initialize(std::vector<uint8_t>& buffer) { + auto assets = nlohmann::json::parse(std::ifstream( "assets.json" )); + auto sound_data = assets["@sound_data"]["us"]; + auto sound_tbl = sound_data["sound_tbl"]; + auto sound_ctl = sound_data["sound_ctl"]; + + std::vector<Entry> tbl = parse_seq_file(buffer, sound_tbl[0], false); + std::vector<Entry> ctl = parse_seq_file(buffer, sound_ctl[0], true); + + std::cout << "Table entries: " << tbl.size() << std::endl; + std::cout << "Control entries: " << ctl.size() << std::endl; + + std::vector<uint8_t> tbl_data = PyUtils::slice(buffer, sound_tbl[0], (size_t) sound_tbl[0] + (size_t) sound_tbl[1]); + std::vector<uint8_t> ctl_data = PyUtils::slice(buffer, sound_ctl[0], (size_t) sound_ctl[0] + (size_t) sound_ctl[1]); + TBLFile tbl_file = parse_tbl(tbl_data, tbl); + + std::cout << "TBLs: " << tbl_file.tbls.size() << std::endl; + std::cout << "Banks: " << tbl_file.banks.size() << std::endl; + std::cout << "Map: " << tbl_file.map.size() << std::endl; + + auto zipped = zip(PyUtils::range(0, ctl.size()), ctl, tbl_file.tbls); + + std::cout << "================================" << std::endl; + for (const auto& item : zipped) { + auto [index, ctrl, sample_bank_name] = item; + auto sample_bank = tbl_file.map[sample_bank_name]; + auto entry = PyUtils::slice(ctl_data, ctrl.offset, ctrl.offset + ctrl.length); + auto headerRaw = PyUtils::slice(entry, 0, 16); + auto header = parse_ctl_header(headerRaw); + auto bank = parse_ctl(header, PyUtils::slice(entry, 16), tbl_file.banks[sample_bank], index); + banks.push_back(bank); + bank.print(); + std::cout << "================================" << std::endl; + } +} diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h new file mode 100644 index 0000000..decb0b5 --- /dev/null +++ b/src/audio/AudioManager.h @@ -0,0 +1,135 @@ +#pragma once + +#include <vector> +#include <string> +#include <iostream> +#include <unordered_map> + +#define NONE 0xFFFF +#define ALIGN(val, al) (size_t) ((val + (al - 1)) & -al) + +struct Entry { + uint32_t offset; + uint32_t length; +}; + +struct Sound { + uint32_t offset; + float tuning; + bool operator==(const Sound& other) const { + return offset == other.offset && tuning == other.tuning; + } +}; + +struct Inst { + std::string name; + uint32_t offset; + uint8_t releaseRate; + uint8_t normalRangeLo; + uint8_t normalRangeHi; + uint32_t envelope; + std::optional<Sound> soundLo; + std::optional<Sound> soundMed; + std::optional<Sound> soundHi; +}; + +struct Book { + uint32_t order{}; + uint32_t npredictors{}; + std::vector<short> table; + bool operator==(const Book& other) const { + return order == other.order && npredictors == other.npredictors && table == other.table; + } +}; + +struct Drum { + std::string name; + uint32_t offset; + uint8_t releaseRate; + uint8_t pan; + uint32_t envelope; + Sound sound; + bool operator==(const Drum& other) const { + return name == other.name && offset == other.offset && releaseRate == other.releaseRate && pan == other.pan && envelope == other.envelope && sound == other.sound; + } +}; + +struct Loop { + uint32_t start{}; + uint32_t end{}; + int32_t count{}; + uint32_t pad{}; + std::optional<std::vector<short>> state; + bool operator==(const Loop& other) const { + return start == other.start && end == other.end && count == other.count && pad == other.pad && state == other.state; + } +}; + +struct AifcEntry { + std::string name; + std::vector<uint8_t> data; + Book book; + Loop loop; + std::vector<float> tunings; +}; + +struct SampleBank { + std::string name; + uint32_t offset; + std::vector<uint8_t> data; + std::unordered_map<uint32_t, AifcEntry> entries; + + AifcEntry AddSample(uint32_t addr, size_t sampleSize, const Book& book, const Loop& loop); +}; + +struct TBLFile { + std::vector<SampleBank> banks; + std::vector<std::string> tbls; + std::unordered_map<std::string, uint32_t> map; +}; + +struct CTLHeader { + uint32_t instruments; + uint32_t numDrums; + uint32_t shared; +}; + +typedef std::pair<unsigned short, unsigned short> EnvelopeData; + +struct Envelope { + std::string name; + std::vector<EnvelopeData> entries; +}; + +struct Bank { + std::string name; + SampleBank sampleBank; + std::vector<Inst> insts; + std::vector<Drum> drums; + std::vector<std::variant<Inst, std::vector<Drum>>> allInsts; + std::vector<uint32_t> instOffsets; + std::unordered_map<uint32_t, Envelope> envelopes; + std::unordered_map<uint32_t, AifcEntry> samples; + void print() const; +}; + +class AudioManager { +public: + static AudioManager* Instance; + void initialize(std::vector<uint8_t>& buffer); + +private: + static std::vector<Entry> parse_seq_file(std::vector<uint8_t>& buffer, uint32_t offset, bool isCTL); + static CTLHeader parse_ctl_header(std::vector<uint8_t>& data); + static std::optional<Sound> parse_sound(std::vector<uint8_t> data); + static Drum parse_drum(std::vector<uint8_t>& data, uint32_t addr); + static Inst parse_inst(std::vector<uint8_t>& data, uint32_t addr); + static Loop parse_loop(uint32_t addr, std::vector<uint8_t>& bankData); + static Book parse_book(uint32_t addr, std::vector<uint8_t>& bankData); + static AifcEntry parse_sample(std::vector<uint8_t>& data, std::vector<uint8_t>& bankData, SampleBank& sampleBank); + static std::vector<EnvelopeData> parse_envelope(uint32_t addr, std::vector<uint8_t>& dataBank); + static Bank parse_ctl(CTLHeader header, std::vector<uint8_t> data, SampleBank bank, uint32_t index); + static TBLFile parse_tbl(std::vector<uint8_t>& data, std::vector<Entry>& entries); + + std::vector<Bank> banks; +};
\ No newline at end of file diff --git a/src/factories/AudioFactory.cpp b/src/factories/AudioFactory.cpp index 62ff3d3..185d740 100644 --- a/src/factories/AudioFactory.cpp +++ b/src/factories/AudioFactory.cpp @@ -1,313 +1,12 @@ #include "AudioFactory.h" #include <vector> -#include <iostream> -#include <filesystem> -#include <sstream> -#include "Companion.h" -#include "utils/MIODecoder.h" #include "binarytools/BinaryReader.h" -namespace fs = std::filesystem; - -#define NONE 0xFFFF - -std::unordered_map<std::string, uint32_t> nameTable; - -struct ALSeqData { - uint8_t *offset; - uint32_t len; -}; - -struct Entry { - uint32_t offset; - uint32_t length; -}; - -struct SampleBank { - std::string name; - uint32_t offset; - std::vector<uint8_t> data; -}; - -struct TBLFile { - std::vector<SampleBank> banks; - std::vector<std::string> tbls; - std::unordered_map<std::string, uint32_t> map; -}; - -struct CTLHeader { - uint32_t instruments; - uint32_t numDrums; - uint32_t shared; -}; - -struct Sound { - int32_t offset; - float tuning; -}; - -struct Bank { - std::string name; - SampleBank sampleBank; -}; - -struct Inst { - std::string name; - uint32_t offset; - uint8_t releaseRate; - uint8_t normalRangeLo; - uint8_t normalRangeHi; - int32_t envelope; - std::optional<Sound> soundLo; - std::optional<Sound> soundMed; - std::optional<Sound> soundHi; -}; - -struct Drum { - std::string name; - uint32_t offset; - uint8_t releaseRate; - uint8_t pan; - int32_t envelope; - Sound sound; -}; - -template<typename T> -std::vector<T> slice(std::vector<T> const &v, uint32_t m, uint32_t n) { - auto first = v.cbegin() + m; - auto last = v.cbegin() + n; - - std::vector<T> vec(first, last); - return vec; -} - -std::vector<uint32_t> range(uint32_t start, uint32_t end) { - std::vector<uint32_t> result; - for (uint32_t i = start; i < end; ++i) { - result.push_back(i); - } - return result; -} - -template <typename T1, typename T2, typename T3> -std::vector<std::tuple<size_t, T2, T3>> zip(const std::vector<T1>& container1, const std::vector<T2>& container2, const std::vector<T3>& container3) { - std::vector<std::tuple<size_t, T2, T3>> result; - - size_t minSize = std::min({container1.size(), container2.size(), container3.size()}); - - for (size_t i = 0; i < minSize; ++i) { - result.emplace_back(container1[i], container2[i], container3[i]); - } - - return result; -} - -std::vector<Entry> parseSeqFile(std::vector<uint8_t>& buffer, int32_t offset){ - std::vector<Entry> entries; - LUS::BinaryReader reader((char*) buffer.data(), buffer.size()); - reader.SetEndianness(LUS::Endianness::Big); - reader.Seek(offset, LUS::SeekOffsetType::Start); - - uint16_t magic = reader.ReadUInt16(); - uint16_t num_entries = reader.ReadUInt16(); - - for (int i = 0; i < num_entries; ++i) { - reader.Seek((offset + 4) + (i * 8), LUS::SeekOffsetType::Start); - entries.push_back({reader.ReadUInt32(), reader.ReadUInt32()}); - } - - reader.Close(); - return entries; -} - -CTLHeader parseCTLHeader(std::vector<uint8_t>& data){ - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(LUS::Endianness::Big); - - CTLHeader header = { reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32() }; - - reader.Close(); - return header; -} - -std::string gen_name(const std::string& prefix){ - if(!nameTable.contains(prefix)){ - nameTable[prefix] = 0; - } - nameTable[prefix] += 1; - return prefix + "-" + std::to_string(nameTable[prefix]); -} - -std::optional<Sound> parseSound(std::vector<uint8_t> data) { - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(LUS::Endianness::Big); - - int32_t addr = reader.ReadInt32(); - float tuning = reader.ReadFloat(); - - if(addr == 0){ - assert(tuning == 0.0f); - return std::nullopt; - } - - Sound sound = { addr, tuning }; - - reader.Close(); - return sound; -} - -Drum parseDrum(std::vector<uint8_t>& data, uint32_t addr) { - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(LUS::Endianness::Big); - std::string name = gen_name("drum"); - - uint8_t releaseRate = reader.ReadInt8(); - uint8_t pan = reader.ReadInt8(); - - reader.Seek(12, LUS::SeekOffsetType::Start); - Sound sound = parseSound(slice(data, 4, 12)).value(); - int32_t envOffset = reader.ReadInt32(); - assert(envOffset != 0); - - Drum drum = { name, addr, releaseRate, pan, envOffset, sound }; - - return drum; -} - -Inst parseInst(std::vector<uint8_t>& data, uint32_t addr) { - std::string name = gen_name("inst"); - char* rawData = (char*) data.data(); - uint8_t normalRangeLo = data[1]; - uint8_t normalRangeHi = data[2]; - uint8_t releaseRate = data[3]; - - int32_t envAddr; - memcpy(&envAddr, rawData + 4, 4); - envAddr = BSWAP32(envAddr); - - auto soundLo = parseSound(slice(data, 8, 16)); - auto soundMed = parseSound(slice(data, 16, 24)); - auto soundHi = parseSound(slice(data, 24, 32)); - - if (soundLo == std::nullopt) { - assert(normalRangeLo == 0); - } - if (soundHi == std::nullopt) { - assert(normalRangeHi == 127); - } - - Inst inst = { name, addr, releaseRate, normalRangeLo, normalRangeHi, envAddr, soundLo, soundMed, soundHi }; - return inst; -} - -void parseCTL(CTLHeader header, std::vector<uint8_t>& data, SampleBank bank, uint32_t index) { - nameTable.clear(); - std::ostringstream ss; - ss << std::hex << std::setw(2) << std::setfill('0') << index; - std::string name = ss.str(); - uint32_t numInstruments = header.instruments; - uint32_t numDrums = header.numDrums; - char* rawData = (char*) data.data(); - - uint32_t drumBaseAddr; - memcpy(&drumBaseAddr, rawData, 4); - drumBaseAddr = BSWAP32(drumBaseAddr); - - std::vector<uint32_t> drumOffsets; - - if(numDrums != 0){ - for (size_t i = 0; i < numDrums; ++i) { - uint32_t drumOffset; - memcpy(&drumOffset, rawData + drumBaseAddr + (i * 4), 4); - drumOffsets.push_back(BSWAP32(drumOffset)); - } - } - - uint32_t instrumentBaseAddr = 4; - std::vector<uint32_t> instrumentOffsets; - std::vector<uint32_t> instrumentList; - - for (size_t i = 0; i < numInstruments; ++i) { - uint32_t instOffset; - memcpy(&instOffset, rawData + instrumentBaseAddr + (i * 4), 4); - instOffset = BSWAP32(instOffset); - if(instOffset == 0){ - instrumentList.push_back(NONE); - } else { - instrumentOffsets.push_back(instOffset); - instrumentList.push_back(instOffset); - } - } - - std::sort(instrumentOffsets.begin(), instrumentOffsets.end()); - - std::vector<Inst> insts; - for(auto &offset : instrumentOffsets){ - auto rInst = slice(data, offset, offset + 32); - Inst inst = parseInst(rInst, offset); - insts.push_back(inst); - } - - std::vector<Drum> drums; - for(auto &offset : drumOffsets){ - auto rDrum = slice(data, offset, offset + 16); - Drum drum = parseDrum(rDrum, offset); - drums.push_back(drum); - } -} - -TBLFile parseTBL(std::vector<uint8_t>& data, std::vector<Entry>& entries) { - TBLFile tbl; - std::unordered_map<uint32_t, std::string> cache; - for(auto &entry : entries){ - if(!cache.contains(entry.offset)){ - std::string name = gen_name("sample_bank"); - tbl.banks.push_back({name, entry.offset, slice(data, entry.offset, entry.offset + entry.length)}); - tbl.map[name] = tbl.banks.size() - 1; - cache[entry.offset] = name; - } - tbl.tbls.push_back(cache[entry.offset]); - } - cache.clear(); - return tbl; -} - bool AudioFactory::process(LUS::BinaryWriter* writer, nlohmann::json& data, std::vector<uint8_t>& buffer) { auto metadata = data["offsets"]; std::string path = data["path"]; - if(path.find("@sound") != std::string::npos){ - auto offsets = metadata["us"]; - std::vector<Entry> tbl = parseSeqFile(buffer, offsets["sound_tbl"][0]); - std::vector<Entry> ctl = parseSeqFile(buffer, offsets["sound_ctl"][0]); - - std::cout << "Table entries: " << tbl.size() << std::endl; - std::cout << "Control entries: " << ctl.size() << std::endl; - - std::vector<uint8_t> tblData = slice(buffer, offsets["sound_tbl"][0], (size_t) offsets["sound_tbl"][0] + (size_t) offsets["sound_tbl"][1]); - std::vector<uint8_t> ctlData = slice(buffer, offsets["sound_ctl"][0], (size_t) offsets["sound_ctl"][0] + (size_t) offsets["sound_ctl"][1]); - TBLFile tblFile = parseTBL(tblData, tbl); - - std::cout << "TBLs: " << tblFile.tbls.size() << std::endl; - std::cout << "Banks: " << tblFile.banks.size() << std::endl; - std::cout << "Map: " << tblFile.map.size() << std::endl; - - auto zipped = zip(range(0, ctl.size()), ctl, tblFile.tbls); - - for (const auto& item : zipped) { - auto [index, ctrl, sample_bank_name] = item; - auto sample_bank = tblFile.map[sample_bank_name]; - auto entry = slice(ctlData, ctrl.offset, ctrl.offset + ctrl.length); - auto headerRaw = slice(entry, 0, 16); - auto header = parseCTLHeader(headerRaw); - std::cout << "Instruments: " << header.instruments << std::endl; - std::cout << "Drums: " << header.numDrums << std::endl; - } - - exit(0); - } - if(path.find("bank_sets") != std::string::npos){ WRITE_HEADER(LUS::ResourceType::Blob, 0); char* raw = (char*) (buffer.data() + metadata[1]["us"][0]); @@ -320,14 +19,9 @@ bool AudioFactory::process(LUS::BinaryWriter* writer, nlohmann::json& data, std: writer->Write(raw + 0x46, 0x5A); } - if(path.find("sound_tbl") != std::string::npos){ - // WRITE_HEADER(LUS::ResourceType::Blob, 0); - // std::vector<Entry> entries = parseSeqFile(buffer, metadata[1]["us"][0]); - // for(auto & entry : entries){ - // std::cout << "Offset: " << entrie.offset << std::endl; - // std::cout << "Len: " << entrie.length << std::endl; - // } - } + if(path.ends_with(".aiff")){ + + } return false; } |
