summaryrefslogtreecommitdiff
path: root/src/audio/AudioManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/AudioManager.cpp')
-rw-r--r--src/audio/AudioManager.cpp664
1 files changed, 664 insertions, 0 deletions
diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp
new file mode 100644
index 0000000..2316141
--- /dev/null
+++ b/src/audio/AudioManager.cpp
@@ -0,0 +1,664 @@
+#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::vector<uint32_t> PyUtils::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;
+}
+
+std::string gen_name(const std::string& prefix){
+ if(!name_table.contains(prefix)){
+ name_table[prefix] = 0;
+ }
+ return prefix + std::to_string(name_table[prefix]++);
+}
+
+AudioBankSample* SampleBank::AddSample(uint32_t addr, size_t sampleSize, const AdpcmBook& book, const AdpcmLoop& loop){
+ assert(sampleSize % 2 == 0);
+ if(sampleSize % 9 != 0){
+ assert(sampleSize % 9 == 1);
+ sampleSize -= 1;
+ }
+
+ AudioBankSample* 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 = new AudioBankSample{ 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);
+
+ uint16_t magic = reader.ReadUInt16();
+ uint16_t 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;
+}
+
+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){
+ assert(drumBaseAddr != 0);
+ for (size_t i = 0; i < numDrums; ++i) {
+ uint32_t drumOffset;
+ memcpy(&drumOffset, rawData + drumBaseAddr + i * 4, 4);
+ drumOffset = BSWAP32(drumOffset);
+ assert(drumOffset != 0);
+ drumOffsets.push_back(drumOffset);
+ }
+ } else {
+ assert(drumBaseAddr == 0);
+ }
+
+ 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);
+ instrumentOffsets.push_back(NONE);
+ } else {
+ instrumentOffsets.push_back(instOffset);
+ instrumentList.push_back(instOffset);
+ }
+ }
+
+// std::sort(instrumentOffsets.begin(), instrumentOffsets.end());
+
+ std::vector<Instrument> insts;
+ for(auto &offset : instrumentOffsets){
+ if(offset == NONE){
+ Instrument invalid = { .valid = false };
+ insts.push_back(invalid);
+ continue;
+ }
+ auto rInst = PyUtils::slice(data, offset, offset + 32);
+ Instrument 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<Instrument, std::vector<Drum>>> allInsts;
+ bool needDrums = !drums.empty();
+ for(auto &inst : insts){
+ std::vector<std::optional<AudioBankSound>> sounds = {inst.soundLo, inst.soundMed, inst.soundHi};
+
+ if(needDrums && std::any_of(sounds.cbegin(), sounds.cend(), [&drums](std::optional<AudioBankSound> 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::map<uint32_t, AudioBankSample*> samples;
+ std::sort(sampleOffsets.begin(), sampleOffsets.end());
+ for(auto &offset : sampleOffsets){
+ auto rSample = PyUtils::slice(data, offset, offset + 20);
+ AudioBankSample* 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<AdsrEnvelope>> 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::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;
+}
+
+std::optional<AudioBankSound> 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;
+ }
+
+ AudioBankSound 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);
+ AudioBankSound 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;
+}
+
+Instrument 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);
+ }
+
+ Instrument inst = { true, name, addr, releaseRate, normalRangeLo, normalRangeHi, envAddr, soundLo, soundMed, soundHi };
+ return inst;
+}
+
+AdpcmLoop 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<int16_t>> 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<int16_t>();
+ for (size_t i = 0; i < 16; ++i) {
+ state.value().push_back(reader.ReadInt16());
+ }
+ }
+ AdpcmLoop loop = { start, end, count, pad, state };
+ return loop;
+}
+
+AdpcmBook 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);
+
+ int32_t order = reader.ReadInt32();
+ int32_t npredictors = reader.ReadInt32();
+
+ assert(order == 2);
+ assert(npredictors == 2);
+
+ std::vector<int16_t> 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) {
+ int16_t dtable;
+ memcpy(&dtable, tableData.data() + i, 2);
+ table.push_back(BSWAP16(dtable));
+ }
+
+ AdpcmBook book = { order, npredictors, table };
+ return book;
+}
+
+AudioBankSample* 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);
+
+ AdpcmLoop loopData = parse_loop(loop, bankData);
+ AdpcmBook bookData = parse_book(book, bankData);
+
+ reader.Close();
+ return sampleBank->AddSample(addr, sampleSize, bookData, loopData);
+}
+
+
+std::vector<AdsrEnvelope> AudioManager::parse_envelope(uint32_t addr, std::vector<uint8_t>& dataBank){
+ std::vector<AdsrEnvelope> entries;
+ LUS::BinaryReader reader((char*) dataBank.data(), dataBank.size());
+ reader.SetEndianness(LUS::Endianness::Big);
+
+ while(true){
+ reader.Seek(addr, LUS::SeekOffsetType::Start);
+ int16_t delay = reader.ReadInt16();
+ int16_t arg = reader.ReadInt16();
+ AdsrEnvelope entry = { delay, arg };
+ entries.push_back(entry);
+ addr += 4;
+ if (1 <= (-delay) % (1 << 16) && (-delay) % (1 << 16) <= 3){
+ break;
+ }
+ }
+
+ reader.Close();
+ return entries;
+}
+
+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");
+ auto* sampleBank = new SampleBank{
+ name, entry.offset, PyUtils::slice(data, entry.offset, entry.offset + entry.length)
+ };
+ tbl.banks.push_back(sampleBank);
+ tbl.map[name] = sampleBank;
+ 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]);
+ this->loaded_tbl = parse_tbl(tbl_data, tbl);
+
+ std::cout << "TBLs: " << this->loaded_tbl.tbls.size() << std::endl;
+ std::cout << "Banks: " << this->loaded_tbl.banks.size() << std::endl;
+ std::cout << "Map: " << this->loaded_tbl.map.size() << std::endl;
+
+ auto zipped = zip(PyUtils::range(0, ctl.size()), ctl, this->loaded_tbl.tbls);
+
+ for (const auto& item : zipped) {
+ auto [index, ctrl, sample_bank_name] = item;
+ auto sample_bank = this->loaded_tbl.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), sample_bank, index);
+ banks[index] = bank;
+ }
+
+ int32_t idx = -1;
+ for(auto &sample_bank : this->loaded_tbl.banks){
+ auto offsets = PyUtils::keys(sample_bank->entries);
+ std::sort(offsets.begin(), offsets.end());
+
+ for(auto &offset : offsets){
+ this->sampleMap[sample_bank->entries[offset]] = ++idx;
+ }
+ }
+}
+
+void serialize_f80(double num, LUS::BinaryWriter &writer) {
+ // Convert the input double to an uint64_t
+ std::uint64_t f64;
+ std::memcpy(&f64, &num, sizeof(double));
+
+ std::uint64_t f64_sign_bit = f64 & (std::uint64_t) pow(2, 63);
+ if (num == 0.0) {
+ if (f64_sign_bit) {
+ writer.Write(0x80000000);
+ } else {
+ writer.Write(0x00000000);
+ }
+ }
+
+ std::uint64_t exponent = ((f64 ^ f64_sign_bit) >> 52);
+
+ assert(exponent != 0);
+ assert(exponent != 0x7FF);
+
+ exponent -= 1023;
+ uint64_t f64_mantissa_bits = f64 & (uint64_t) pow(2, 52) - 1;
+ uint64_t f80_sign_bit = f64_sign_bit << (80 - 64);
+ uint64_t f80_exponent = (exponent + 0x3FFF) << 64;
+ uint64_t f80_mantissa_bits = (uint64_t) pow(2, 63) | (f64_mantissa_bits << (63 - 52));
+ uint64_t f80 = f80_sign_bit | f80_exponent | f80_mantissa_bits;
+
+ // Split the f80 representation into two parts (high and low)
+ uint16_t high = BSWAP16((uint16_t) f80 >> 64);
+ writer.Write((char*) &high, 2);
+ uint64_t low = BSWAP64(f80 & ((uint64_t) pow(2, 64) - 1));
+ writer.Write((char*) &low, 8);
+}
+
+#define START_SECTION(section) \
+ { \
+ out.Write(BSWAP32(section)); \
+ LUS::BinaryWriter tmp = LUS::BinaryWriter(); \
+ tmp.SetEndianness(LUS::Endianness::Big); \
+
+#define START_CUSTOM_SECTION(section) \
+ { \
+ LUS::BinaryWriter tmp = LUS::BinaryWriter(); \
+ tmp.SetEndianness(LUS::Endianness::Big); \
+ out.Write(BSWAP32(AIFC::MagicValues::AAPL)); \
+ tmp.Write(AIFC::MagicValues::stoc); \
+ tmp.Write(section, false); \
+
+#define END_SECTION() \
+ auto odata = tmp.ToVector(); \
+ size_t size = odata.size(); \
+ len += ALIGN(size, 2) + 8; \
+ out.Write(BSWAP32((uint32_t) size)); \
+ out.Write(odata.data(), odata.size()); \
+ if(size % 2){ \
+ out.WriteByte(0); \
+ } \
+ } \
+
+void AudioManager::write_aifc(AudioBankSample* entry, LUS::BinaryWriter &out) {
+ int16_t num_channels = 1;
+ auto data = entry->data;
+ size_t len = 0;
+ assert(data.size() % 9 == 0);
+ if(data.size() % 2 == 1){
+ data.push_back('\0');
+ }
+ uint32_t num_frames = data.size() * 16 / 9;
+ int16_t sample_size = 16;
+
+ uint32_t sample_rate = -1;
+ if(entry->tunings.size() == 1){
+ sample_rate = 32000 * entry->tunings[0];
+ } else {
+ float tmin = PyUtils::min(entry->tunings);
+ float tmax = PyUtils::max(entry->tunings);
+
+ if(tmin <= 0.5f <= tmax){
+ sample_rate = 16000;
+ } else if(tmin <= 1.0f <= tmax){
+ sample_rate = 32000;
+ } else if(tmin <= 1.5f <= tmax){
+ sample_rate = 48000;
+ } else if(tmin <= 2.5f <= tmax){
+ sample_rate = 80000;
+ } else {
+ sample_rate = 16000 * (tmin + tmax);
+ }
+ }
+
+ out.Write(BSWAP32(AIFC::MagicValues::FORM));
+ // This should be where the size is, but we need to write it later
+ out.Write((uint32_t) 0);
+ out.Write(BSWAP32(AIFC::MagicValues::AIFC));
+
+ START_SECTION(AIFC::MagicValues::COMM);
+
+ tmp.Write((uint16_t) num_channels);
+ tmp.Write((uint32_t) num_frames);
+
+ tmp.Write((uint16_t) sample_size);
+ serialize_f80(sample_rate, tmp);
+ tmp.Write(AIFC::MagicValues::VAPC);
+ tmp.Write("\x0bVADPCM ~4-1", false);
+
+ END_SECTION();
+
+ START_SECTION(AIFC::MagicValues::INST)
+ tmp.Write(std::string(20, '\0'), false);
+ END_SECTION();
+
+ START_CUSTOM_SECTION("\x0bVADPCMCODES")
+ tmp.Write((uint16_t) 1);
+ tmp.Write((uint16_t) entry->book.order);
+ tmp.Write((uint16_t) entry->book.npredictors);
+
+ for(auto x : entry->book.table){
+ tmp.Write((int16_t) x);
+ }
+ END_SECTION();
+
+ START_SECTION(AIFC::MagicValues::SSND)
+ uint32_t zero = 0;
+ tmp.Write((char*) &zero, 4);
+ tmp.Write((char*) &zero, 4);
+ tmp.Write((char*) data.data(), data.size());
+ END_SECTION();
+
+ if(entry->loop.count != 0){
+ START_CUSTOM_SECTION("\x0bVADPCMLOOPS")
+ uint16_t one = BSWAP16(1);
+ tmp.Write((char*) &one, 2);
+ tmp.Write((char*) &one, 2);
+ tmp.Write((uint32_t) entry->loop.start);
+ tmp.Write((uint32_t) entry->loop.end);
+ tmp.Write((int32_t) entry->loop.count);
+ for(size_t i = 0; i < 16; i++){
+ int16_t loop = BSWAP16(entry->loop.state.value()[i]);
+ tmp.Write((char*) &loop, 2);
+ }
+ END_SECTION();
+ }
+
+ len += 4;
+ out.Seek(4, LUS::SeekOffsetType::Start);
+ out.Write((uint32_t) BSWAP32(len));
+
+}
+
+void AudioManager::create_aifc(int32_t index, LUS::BinaryWriter &out) {
+ int32_t idx = -1;
+ for(auto &sample_bank : this->loaded_tbl.banks){
+ auto offsets = PyUtils::keys(sample_bank->entries);
+ std::sort(offsets.begin(), offsets.end());
+
+ for(auto &offset : offsets){
+ if(++idx == index){
+ write_aifc(sample_bank->entries[offset], out);
+ return;
+ }
+ }
+ }
+}
+
+AudioBankSample AudioManager::get_aifc(int32_t index) {
+ int32_t idx = -1;
+ for(auto &sample_bank : this->loaded_tbl.banks){
+ auto offsets = PyUtils::keys(sample_bank->entries);
+ std::sort(offsets.begin(), offsets.end());
+
+ for(auto &offset : offsets){
+ if(++idx == index){
+ return *sample_bank->entries[offset];
+ }
+ }
+ }
+
+ throw std::runtime_error("Invalid index");
+}
+
+uint32_t AudioManager::get_index(AudioBankSample* entry) {
+ if(!this->sampleMap.contains(entry)){
+ return -1;
+ }
+ return this->sampleMap[entry];
+}
+
+std::map<uint32_t, Bank> AudioManager::get_banks() {
+ return this->banks;
+} \ No newline at end of file