From 4451ae28dba815411a39fcda22a3455d1ea69343 Mon Sep 17 00:00:00 2001 From: KiritoDv Date: Sat, 16 Nov 2024 19:44:27 -0600 Subject: Code cleanup and moved current audio system to legacy --- src/Companion.cpp | 16 +- src/audio/AIFCDecode.cpp | 664 ----------------------- src/audio/AIFCDecode.h | 6 - src/audio/AudioManager.cpp | 683 ------------------------ src/audio/AudioManager.h | 166 ------ src/factories/AudioHeaderFactory.cpp | 9 - src/factories/AudioHeaderFactory.h | 27 - src/factories/BankFactory.cpp | 99 ---- src/factories/BankFactory.h | 28 - src/factories/SampleFactory.cpp | 43 -- src/factories/SampleFactory.h | 27 - src/factories/SequenceFactory.cpp | 25 - src/factories/SequenceFactory.h | 29 - src/factories/mk64/CourseMetadata.h | 2 +- src/factories/mk64/CourseVtx.h | 2 +- src/factories/mk64/DrivingBehaviour.h | 2 +- src/factories/mk64/ItemCurve.h | 2 +- src/factories/mk64/SpawnData.h | 2 +- src/factories/mk64/TrackSections.h | 2 +- src/factories/mk64/UnkSpawnData.h | 2 +- src/factories/mk64/Waypoints.h | 2 +- src/factories/sf64/AnimFactory.h | 2 +- src/factories/sf64/ColPolyFactory.h | 2 +- src/factories/sf64/EnvironmentFactory.h | 2 +- src/factories/sf64/HitboxFactory.h | 2 +- src/factories/sf64/MessageFactory.h | 2 +- src/factories/sf64/MessageLookupFactory.h | 2 +- src/factories/sf64/ObjInitFactory.h | 2 +- src/factories/sf64/ScriptFactory.h | 2 +- src/factories/sf64/SkeletonFactory.h | 2 +- src/factories/sm64/AnimationFactory.h | 2 +- src/factories/sm64/CollisionFactory.h | 2 +- src/factories/sm64/DialogFactory.h | 2 +- src/factories/sm64/DictionaryFactory.h | 2 +- src/factories/sm64/GeoLayoutFactory.h | 2 +- src/factories/sm64/TextFactory.h | 2 +- src/factories/sm64/audio/AIFCDecode.cpp | 664 +++++++++++++++++++++++ src/factories/sm64/audio/AIFCDecode.h | 6 + src/factories/sm64/audio/AudioHeaderFactory.cpp | 9 + src/factories/sm64/audio/AudioHeaderFactory.h | 27 + src/factories/sm64/audio/AudioManager.cpp | 683 ++++++++++++++++++++++++ src/factories/sm64/audio/AudioManager.h | 166 ++++++ src/factories/sm64/audio/BankFactory.cpp | 99 ++++ src/factories/sm64/audio/BankFactory.h | 28 + src/factories/sm64/audio/SampleFactory.cpp | 43 ++ src/factories/sm64/audio/SampleFactory.h | 27 + src/factories/sm64/audio/SequenceFactory.cpp | 25 + src/factories/sm64/audio/SequenceFactory.h | 29 + 48 files changed, 1837 insertions(+), 1837 deletions(-) delete mode 100644 src/audio/AIFCDecode.cpp delete mode 100644 src/audio/AIFCDecode.h delete mode 100644 src/audio/AudioManager.cpp delete mode 100644 src/audio/AudioManager.h delete mode 100644 src/factories/AudioHeaderFactory.cpp delete mode 100644 src/factories/AudioHeaderFactory.h delete mode 100644 src/factories/BankFactory.cpp delete mode 100644 src/factories/BankFactory.h delete mode 100644 src/factories/SampleFactory.cpp delete mode 100644 src/factories/SampleFactory.h delete mode 100644 src/factories/SequenceFactory.cpp delete mode 100644 src/factories/SequenceFactory.h create mode 100644 src/factories/sm64/audio/AIFCDecode.cpp create mode 100644 src/factories/sm64/audio/AIFCDecode.h create mode 100644 src/factories/sm64/audio/AudioHeaderFactory.cpp create mode 100644 src/factories/sm64/audio/AudioHeaderFactory.h create mode 100644 src/factories/sm64/audio/AudioManager.cpp create mode 100644 src/factories/sm64/audio/AudioManager.h create mode 100644 src/factories/sm64/audio/BankFactory.cpp create mode 100644 src/factories/sm64/audio/BankFactory.h create mode 100644 src/factories/sm64/audio/SampleFactory.cpp create mode 100644 src/factories/sm64/audio/SampleFactory.h create mode 100644 src/factories/sm64/audio/SequenceFactory.cpp create mode 100644 src/factories/sm64/audio/SequenceFactory.h (limited to 'src') diff --git a/src/Companion.cpp b/src/Companion.cpp index a896eb3..8059e8c 100644 --- a/src/Companion.cpp +++ b/src/Companion.cpp @@ -11,10 +11,6 @@ #include #include -#include "factories/BankFactory.h" -#include "factories/AudioHeaderFactory.h" -#include "factories/SampleFactory.h" -#include "factories/SequenceFactory.h" #include "factories/GenericArrayFactory.h" #include "factories/VtxFactory.h" #include "factories/MtxFactory.h" @@ -30,6 +26,10 @@ #include "factories/AssetArrayFactory.h" #include "factories/ViewportFactory.h" +#include "factories/sm64/audio/BankFactory.h" +#include "factories/sm64/audio/AudioHeaderFactory.h" +#include "factories/sm64/audio/SampleFactory.h" +#include "factories/sm64/audio/SequenceFactory.h" #include "factories/sm64/AnimationFactory.h" #include "factories/sm64/BehaviorScriptFactory.h" #include "factories/sm64/CollisionFactory.h" @@ -88,10 +88,6 @@ void Companion::Init(const ExportType type) { this->RegisterFactory("INC", std::make_shared()); this->RegisterFactory("LIGHTS", std::make_shared()); this->RegisterFactory("GFX", std::make_shared()); - this->RegisterFactory("AUDIO:HEADER", std::make_shared()); - this->RegisterFactory("SEQUENCE", std::make_shared()); - this->RegisterFactory("SAMPLE", std::make_shared()); - this->RegisterFactory("BANK", std::make_shared()); this->RegisterFactory("VEC3F", std::make_shared()); this->RegisterFactory("VEC3S", std::make_shared()); this->RegisterFactory("ARRAY", std::make_shared()); @@ -99,6 +95,10 @@ void Companion::Init(const ExportType type) { this->RegisterFactory("VP", std::make_shared()); // SM64 specific + this->RegisterFactory("SM64:AUDIO_HEADER", std::make_shared()); + this->RegisterFactory("SM64:SEQUENCE", std::make_shared()); + this->RegisterFactory("SM64:SAMPLE", std::make_shared()); + this->RegisterFactory("SM64:BANK", std::make_shared()); this->RegisterFactory("SM64:DIALOG", std::make_shared()); this->RegisterFactory("SM64:TEXT", std::make_shared()); this->RegisterFactory("SM64:DICTIONARY", std::make_shared()); diff --git a/src/audio/AIFCDecode.cpp b/src/audio/AIFCDecode.cpp deleted file mode 100644 index e627ad0..0000000 --- a/src/audio/AIFCDecode.cpp +++ /dev/null @@ -1,664 +0,0 @@ -/** - * Bruteforcing decoder for converting ADPCM-encoded AIFC into AIFF, in a way - * that roundtrips with vadpcm_enc. - */ -#include "AIFCDecode.h" -#include -#include -#include -#include -#include -#include -#include "binarytools/BinaryWriter.h" -#include "BinaryReader.h" - -typedef signed char s8; -typedef short s16; -typedef int s32; -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; -typedef unsigned long long u64; -typedef float f32; - -#undef BSWAP16 -#undef BSWAP32 -#undef BSWAP64 - -#ifdef _MSC_VER -#define bswap16(x) _byteswap_ushort(x) -#define bswap32(x) _byteswap_ulong(x) -#define BSWAP16(x) x = bswap16(x) -#define BSWAP32(x) x = bswap32(x) -#else -#define bswap16(x) __builtin_bswap16(x) -#define bswap32(x) __builtin_bswap32(x) -#define BSWAP16(x) x = bswap16(x) -#define BSWAP32(x) x = bswap32(x) -#endif - -#define BSWAP16_MANY(x, n) for (s32 _i = 0; _i < n; _i++) BSWAP16((x)[_i]) - -#ifndef WIN32 -#define NORETURN __attribute__((noreturn)) -#define UNUSED __attribute__((unused)) -#else -#define NORETURN -#define UNUSED -#endif - -typedef struct { - u32 ckID; - u32 ckSize; -} ChunkHeader; - -typedef struct { - u32 ckID; - u32 ckSize; - u32 formType; -} Chunk; - -typedef struct { - s16 numChannels; - u16 numFramesH; - u16 numFramesL; - s16 sampleSize; - s16 sampleRate[5]; // 80-bit float - u16 compressionTypeH; - u16 compressionTypeL; -} CommonChunk; - -typedef struct { - s16 MarkerID; - u16 positionH; - u16 positionL; -} Marker; - -typedef struct { - s16 playMode; - s16 beginLoop; - s16 endLoop; -} Loop; - -typedef struct { - s8 baseNote; - s8 detune; - s8 lowNote; - s8 highNote; - s8 lowVelocity; - s8 highVelocity; - s16 gain; - Loop sustainLoop; - Loop releaseLoop; -} InstrumentChunk; - -typedef struct { - s32 offset; - s32 blockSize; -} SoundDataChunk; - -typedef struct { - s16 version; - s16 order; - s16 nEntries; -} CodeChunk; - -typedef struct -{ - u32 start; - u32 end; - u32 count; - s16 state[16]; -} ALADPCMloop; - -#define checked_fread(a, b, c, d) d.Read((char*) a, b * c) - -NORETURN -void fail_parse(const char *fmt, ...) -{ - char *formatted = nullptr; - va_list ap; - va_start(ap, fmt); - int size = vsnprintf(nullptr, 0, fmt, ap); - va_end(ap); - if (size >= 0) { - size++; - formatted = static_cast(malloc(size)); - if (formatted != nullptr) { - va_start(ap, fmt); - size = vsnprintf(formatted, size, fmt, ap); - va_end(ap); - if (size < 0) { - free(formatted); - formatted = nullptr; - } - } - } - - if (formatted != nullptr) { - throw std::runtime_error(formatted); - free(formatted); - } - throw std::runtime_error("Error parsing file"); -} - -s32 myrand() -{ - static u64 state = 1619236481962341ULL; - state *= 3123692312231ULL; - state++; - return state >> 33; -} - -s16 qsample(s32 x, s32 scale) -{ - // Compute x / 2^scale rounded to the nearest integer, breaking ties towards zero. - if (scale == 0) return x; - return (x + (1 << (scale - 1)) - (x > 0)) >> scale; -} - -s16 clamp_to_s16(s32 x) -{ - if (x < -0x8000) return -0x8000; - if (x > 0x7fff) return 0x7fff; - return (s16) x; -} - -s32 toi4(s32 x) -{ - if (x >= 8) return x - 16; - return x; -} - -s32 readaifccodebook(LUS::BinaryReader& fhandle, s32 ****table, s16 *order, s16 *npredictors) -{ - checked_fread(order, sizeof(s16), 1, fhandle); - BSWAP16(*order); - checked_fread(npredictors, sizeof(s16), 1, fhandle); - BSWAP16(*npredictors); - *table = static_cast(malloc(*npredictors * sizeof(s32 **))); - for (s32 i = 0; i < *npredictors; i++) { - (*table)[i] = static_cast(malloc(8 * sizeof(s32 *))); - for (s32 j = 0; j < 8; j++) { - (*table)[i][j] = static_cast(malloc((*order + 8) * sizeof(s32))); - } - } - - for (s32 i = 0; i < *npredictors; i++) { - s32 **table_entry = (*table)[i]; - for (s32 j = 0; j < *order; j++) { - for (s32 k = 0; k < 8; k++) { - s16 ts; - checked_fread(&ts, sizeof(s16), 1, fhandle); - BSWAP16(ts); - table_entry[k][j] = ts; - } - } - - for (s32 k = 1; k < 8; k++) { - table_entry[k][*order] = table_entry[k - 1][*order - 1]; - } - - table_entry[0][*order] = 1 << 11; - - for (s32 k = 1; k < 8; k++) { - s32 j = 0; - for (; j < k; j++) { - table_entry[j][k + *order] = 0; - } - - for (; j < 8; j++) { - table_entry[j][k + *order] = table_entry[j - k][*order]; - } - } - } - return 0; -} - -ALADPCMloop *readlooppoints(LUS::BinaryReader& reader, s16 *nloops) { - BSWAP16(*nloops); - checked_fread(nloops, sizeof(s16), 1, reader); - auto *al = static_cast(malloc(*nloops * sizeof(ALADPCMloop))); - for (s32 i = 0; i < *nloops; i++) { - checked_fread(&al[i], sizeof(ALADPCMloop), 1, reader); - BSWAP32(al[i].start); - BSWAP32(al[i].end); - BSWAP32(al[i].count); - BSWAP16_MANY(al[i].state, 16); - } - return al; -} - -s32 inner_product(s32 length, s32 *v1, s32 *v2) -{ - s32 out = 0; - for (s32 i = 0; i < length; i++) { - out += v1[i] * v2[i]; - } - - // Compute "out / 2^11", rounded down. - s32 dout = out / (1 << 11); - s32 fiout = dout * (1 << 11); - return dout - (out - fiout < 0); -} - -void my_decodeframe(u8 *frame, s32 *state, s32 order, s32 ***coefTable) -{ - s32 ix[16]; - - u8 header = frame[0]; - s32 scale = 1 << (header >> 4); - s32 optimalp = header & 0xf; - - for (s32 i = 0; i < 16; i += 2) { - u8 c = frame[1 + i/2]; - ix[i] = c >> 4; - ix[i + 1] = c & 0xf; - } - - for (s32 i = 0; i < 16; i++) { - if (ix[i] >= 8) ix[i] -= 16; - ix[i] *= scale; - } - - for (s32 j = 0; j < 2; j++) { - s32 in_vec[16]; - if (j == 0) { - for (s32 i = 0; i < order; i++) { - in_vec[i] = state[16 - order + i]; - } - } else { - for (s32 i = 0; i < order; i++) { - in_vec[i] = state[8 - order + i]; - } - } - - for (s32 i = 0; i < 8; i++) { - s32 ind = j * 8 + i; - in_vec[order + i] = ix[ind]; - state[ind] = inner_product(order + i, coefTable[optimalp][i], in_vec) + ix[ind]; - } - } -} - -void my_encodeframe(u8 *out, s16 *inBuffer, s32 *state, s32 ***coefTable, s32 order, s32 npredictors) -{ - s16 ix[16]; - s32 prediction[16]; - s32 inVector[16]; - s32 saveState[16]; - s32 optimalp = 0; - s32 scale; - s32 ie[16]; - s32 e[16]; - f32 min = 1e30; - - for (s32 k = 0; k < npredictors; k++) { - for (s32 j = 0; j < 2; j++) { - for (s32 i = 0; i < order; i++) { - inVector[i] = (j == 0 ? state[16 - order + i] : inBuffer[8 - order + i]); - } - - for (s32 i = 0; i < 8; i++) { - prediction[j * 8 + i] = inner_product(order + i, coefTable[k][i], inVector); - e[j * 8 + i] = inVector[i + order] = inBuffer[j * 8 + i] - prediction[j * 8 + i]; - } - } - - f32 se = 0.0f; - for (s32 j = 0; j < 16; j++) { - se += (f32) e[j] * (f32) e[j]; - } - - if (se < min) { - min = se; - optimalp = k; - } - } - - for (s32 j = 0; j < 2; j++) { - for (s32 i = 0; i < order; i++) { - inVector[i] = (j == 0 ? state[16 - order + i] : inBuffer[8 - order + i]); - } - - for (s32 i = 0; i < 8; i++) { - prediction[j * 8 + i] = inner_product(order + i, coefTable[optimalp][i], inVector); - e[j * 8 + i] = inVector[i + order] = inBuffer[j * 8 + i] - prediction[j * 8 + i]; - } - } - - for (s32 i = 0; i < 16; i++) { - ie[i] = clamp_to_s16(e[i]); - } - - s32 max = 0; - for (s32 i = 0; i < 16; i++) { - if (abs(ie[i]) > abs(max)) { - max = ie[i]; - } - } - - for (scale = 0; scale <= 12; scale++) { - if (max <= 7 && max >= -8) break; - max /= 2; - } - - for (s32 i = 0; i < 16; i++) { - saveState[i] = state[i]; - } - - for (s32 nIter = 0, again = 1; nIter < 2 && again; nIter++) { - again = 0; - if (nIter == 1) scale++; - if (scale > 12) { - scale = 12; - } - - for (s32 j = 0; j < 2; j++) { - s32 base = j * 8; - for (s32 i = 0; i < order; i++) { - inVector[i] = (j == 0 ? - saveState[16 - order + i] : state[8 - order + i]); - } - - for (s32 i = 0; i < 8; i++) { - prediction[base + i] = inner_product(order + i, coefTable[optimalp][i], inVector); - s32 se = inBuffer[base + i] - prediction[base + i]; - ix[base + i] = qsample(se, scale); - s32 cV = clamp_to_s16(ix[base + i]) - ix[base + i]; - if (cV > 1 || cV < -1) again = 1; - ix[base + i] += cV; - inVector[i + order] = ix[base + i] * (1 << scale); - state[base + i] = prediction[base + i] + inVector[i + order]; - } - } - } - - u8 header = (scale << 4) | (optimalp & 0xf); - out[0] = header; - for (s32 i = 0; i < 16; i += 2) { - u8 c = ((ix[i] & 0xf) << 4) | (ix[i + 1] & 0xf); - out[1 + i/2] = c; - } -} - -void permute(s16 *out, s32 *in, s32 scale) -{ - for (s32 i = 0; i < 16; i++) { - out[i] = clamp_to_s16(in[i] - scale / 2 + myrand() % (scale + 1)); - } -} - -void write_header(LUS::BinaryWriter& writer, const char *id, s32 size) { - writer.Write((char*) id, (size_t) 4); - BSWAP32(size); - writer.Write(size); -} - -void write_aiff(std::vector data, LUS::BinaryWriter& writer) { - s16 order = -1; - s16 nloops = 0; - ALADPCMloop *aloops = nullptr; - s16 npredictors = -1; - s32 ***coefTable = nullptr; - s32 state[16]; - s32 soundPointer = -1; - s32 currPos = 0; - s32 nSamples = 0; - Chunk FormChunk; - ChunkHeader Header; - CommonChunk CommChunk; - InstrumentChunk InstChunk; - SoundDataChunk SndDChunk; - - memset(&InstChunk, 0, sizeof(InstChunk)); - LUS::BinaryReader reader((char*)data.data(), data.size()); - - checked_fread(&FormChunk, sizeof(FormChunk), 1, reader); - BSWAP32(FormChunk.ckID); - BSWAP32(FormChunk.formType); - if ((FormChunk.ckID != 0x464f524d) || (FormChunk.formType != 0x41494643)) { // FORM, AIFC - fail_parse("not an AIFF-C file"); - } - - for (;;) { - if (reader.GetBaseAddress() >= reader.GetLength()) { - break; - } - reader.Read((char*) &Header, sizeof(Header)); - u32 ts; - BSWAP32(Header.ckID); - BSWAP32(Header.ckSize); - - Header.ckSize++; - Header.ckSize &= ~1; - s32 offset = reader.GetBaseAddress(); - - if(Header.ckID == 0x434f4d4d) { // COMM - checked_fread(&CommChunk, sizeof(CommChunk), 1, reader); - BSWAP16(CommChunk.numChannels); - BSWAP16(CommChunk.numFramesH); - BSWAP16(CommChunk.numFramesL); - BSWAP16(CommChunk.sampleSize); - BSWAP16(CommChunk.compressionTypeH); - BSWAP16(CommChunk.compressionTypeL); - s32 cType = (CommChunk.compressionTypeH << 16) + CommChunk.compressionTypeL; - if (cType != 0x56415043) { // VAPC - fail_parse("file is of the wrong compression type"); - } - if (CommChunk.numChannels != 1) { - fail_parse("file contains %d channels, only 1 channel supported", CommChunk.numChannels); - } - if (CommChunk.sampleSize != 16) { - fail_parse("file contains %d bit samples, only 16 bit samples supported", CommChunk.sampleSize); - } - - nSamples = (CommChunk.numFramesH << 16) + CommChunk.numFramesL; - - // Allow broken input lengths - if (nSamples % 16) { - nSamples--; - } - - if (nSamples % 16 != 0) { - fail_parse("number of chunks must be a multiple of 16, found %d", nSamples); - } - } - - if(Header.ckID == 0x53534e44) { //SSND - checked_fread(&SndDChunk, sizeof(SndDChunk), 1, reader); - BSWAP32(SndDChunk.offset); - BSWAP32(SndDChunk.blockSize); - assert(SndDChunk.offset == 0); - assert(SndDChunk.blockSize == 0); - soundPointer = reader.GetBaseAddress(); - } - - if(Header.ckID == 0x4150504c){ // APPL - checked_fread(&ts, sizeof(u32), 1, reader); - BSWAP32(ts); - if (ts == 0x73746f63) { // stoc - u8 len; - checked_fread(&len, 1, 1, reader); - if (len == 11) { - char ChunkName[12]; - s16 version; - checked_fread(ChunkName, 11, 1, reader); - ChunkName[11] = '\0'; - if (strcmp("VADPCMCODES", ChunkName) == 0) { - checked_fread(&version, sizeof(s16), 1, reader); - BSWAP16(version); - if (version != 1) { - fail_parse("Unknown codebook chunk version"); - } - readaifccodebook(reader, &coefTable, &order, &npredictors); - } - else if (strcmp("VADPCMLOOPS", ChunkName) == 0) { - checked_fread(&version, sizeof(s16), 1, reader); - BSWAP16(version); - if (version != 1) { - fail_parse("Unknown loop chunk version"); - } - aloops = readlooppoints(reader, &nloops); - BSWAP16(nloops); - if (nloops != 1) { - fail_parse("Only a single loop supported"); - } - } - } - } - } - - reader.Seek(offset + Header.ckSize, LUS::SeekOffsetType::Start); - } - - if (coefTable == nullptr) { - fail_parse("Codebook missing from bitstream"); - } - - for (s32 i = 0; i < order; i++) { - state[15 - i] = 0; - } - - u32 outputBytes = nSamples * sizeof(s16); - - reader.Seek(soundPointer, LUS::SeekOffsetType::Start); - - while (currPos < nSamples) { - u8 input[9]; - u8 encoded[9]; - s32 lastState[16]; - s32 decoded[16]; - s16 guess[16]; - s16 origGuess[16]; - - memcpy(lastState, state, sizeof(lastState)); - checked_fread(input, 9, 1, reader); - - // Decode for real - my_decodeframe(input, state, order, coefTable); - memcpy(decoded, state, sizeof(lastState)); - - // Create a guess from that, by clamping to 16 bits - for (s32 i = 0; i < 16; i++) { - origGuess[i] = clamp_to_s16(state[i]); - } - - // Encode the guess - memcpy(state, lastState, sizeof(lastState)); - memcpy(guess, origGuess, sizeof(guess)); - my_encodeframe(encoded, guess, state, coefTable, order, npredictors); - - // If it doesn't match, randomly round numbers until it does. - if (memcmp(input, encoded, 9) != 0) { - s32 scale = 1 << (input[0] >> 4); - do { - permute(guess, decoded, scale); - memcpy(state, lastState, sizeof(lastState)); - my_encodeframe(encoded, guess, state, coefTable, order, npredictors); - } while (memcmp(input, encoded, 9) != 0); - - // Bring the matching closer to the original decode (not strictly - // necessary, but it will move us closer to the target on average). - for (s32 failures = 0; failures < 50; failures++) { - s32 ind = myrand() % 16; - s32 old = guess[ind]; - if (old == origGuess[ind]) continue; - guess[ind] = origGuess[ind]; - if (myrand() % 2) guess[ind] += (old - origGuess[ind]) / 2; - memcpy(state, lastState, sizeof(lastState)); - my_encodeframe(encoded, guess, state, coefTable, order, npredictors); - if (memcmp(input, encoded, 9) == 0) { - failures = -1; - } - else { - guess[ind] = old; - } - } - } - - memcpy(state, decoded, sizeof(lastState)); - BSWAP16_MANY(guess, 16); - writer.Seek(currPos * 2, LUS::SeekOffsetType::Start); - writer.Write((char*) guess, sizeof(guess)); - currPos += 16; - } - - // Write an incomplete file header. We'll fill in the size later. - writer.Seek(0, LUS::SeekOffsetType::Start); - writer.Write((char*) "FORM\0\0\0\0AIFF", 12); - - // Subtract 4 from the COMM size to skip the compression field. - write_header(writer, "COMM", sizeof(CommonChunk) - 4); - CommChunk.numFramesH = nSamples >> 16; - CommChunk.numFramesL = nSamples & 0xffff; - BSWAP16(CommChunk.numChannels); - BSWAP16(CommChunk.numFramesH); - BSWAP16(CommChunk.numFramesL); - BSWAP16(CommChunk.sampleSize); - writer.Write((char*) &CommChunk, sizeof(CommonChunk) - 4); - - if (nloops > 0) { - s32 startPos = aloops[0].start, endPos = aloops[0].end; - const char *markerNames[2] = {"start", "end"}; - Marker markers[2] = { - {1, static_cast((u16) startPos >> 16), static_cast((u16) startPos & 0xffff)}, - {2, static_cast(endPos >> 16), static_cast(endPos & 0xffff)} - }; - write_header(writer, "MARK", 2 + 2 * sizeof(Marker) + 1 + 5 + 1 + 3); - s16 numMarkers = bswap16(2); - writer.Write(numMarkers); - for (s32 i = 0; i < 2; i++) { - u8 len = (u8) strlen(markerNames[i]); - BSWAP16(markers[i].MarkerID); - BSWAP16(markers[i].positionH); - BSWAP16(markers[i].positionL); - writer.Write(markers[i].MarkerID); - writer.Write(markers[i].positionH); - writer.Write(markers[i].positionL); - writer.Write(len); - writer.Write((char*) markerNames[i], len); - } - - write_header(writer, "INST", sizeof(InstrumentChunk)); - InstChunk.sustainLoop.playMode = bswap16(1); - InstChunk.sustainLoop.beginLoop = bswap16(1); - InstChunk.sustainLoop.endLoop = bswap16(2); - InstChunk.releaseLoop.playMode = 0; - InstChunk.releaseLoop.beginLoop = 0; - InstChunk.releaseLoop.endLoop = 0; - writer.Write((char*) &InstChunk, sizeof(InstrumentChunk)); - } - - // Save the coefficient table for use when encoding. Ideally this wouldn't - // be needed and "tabledesign -s 1" would generate the right table, but in - // practice it's difficult to adjust samples to make that happen. - write_header(writer, "APPL", 4 + 12 + sizeof(CodeChunk) + npredictors * order * 8 * 2); - writer.Write("stoc", false); - CodeChunk cChunk; - cChunk.version = bswap16(1); - cChunk.order = bswap16(order); - cChunk.nEntries = bswap16(npredictors); - writer.Write("\x0bVADPCMCODES", false); - writer.Write((char*) &cChunk, sizeof(CodeChunk)); - for (s32 i = 0; i < npredictors; i++) { - for (s32 j = 0; j < order; j++) { - for (s32 k = 0; k < 8; k++) { - s16 ts = bswap16(coefTable[i][k][j]); - writer.Write(ts); - } - } - } - - write_header(writer, "SSND", outputBytes + 8); - SndDChunk.offset = 0; - SndDChunk.blockSize = 0; - writer.Write((char*) &SndDChunk, sizeof(SoundDataChunk)); - - // Fix the size in the header - s32 fileSize = bswap32(writer.GetLength() - 8); - writer.Seek(4, LUS::SeekOffsetType::Start); - writer.Write(fileSize); - - reader.Close(); -} diff --git a/src/audio/AIFCDecode.h b/src/audio/AIFCDecode.h deleted file mode 100644 index e141a80..0000000 --- a/src/audio/AIFCDecode.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include "BinaryWriter.h" - -void write_aiff(std::vector data, LUS::BinaryWriter& writer); \ No newline at end of file diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp deleted file mode 100644 index c168a39..0000000 --- a/src/audio/AudioManager.cpp +++ /dev/null @@ -1,683 +0,0 @@ -#include "AudioManager.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "hj/zip.h" -#include "hj/pyutils.h" -#include "spdlog/spdlog.h" -#include "binarytools/BinaryReader.h" -#include "spdlog/spdlog.h" - -std::unordered_map name_table; -std::unordered_map sample_table; -AudioManager* AudioManager::Instance; - -std::vector PyUtils::range(uint32_t start, uint32_t end) { - std::vector 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 { - SPDLOG_DEBUG("Bank: {}", name); - SPDLOG_DEBUG("Instruments: {}", std::to_string(insts.size())); - SPDLOG_DEBUG("Drums: {}", std::to_string(drums.size())); - SPDLOG_DEBUG("Samples: {}", std::to_string(samples.size())); - SPDLOG_DEBUG("Envelopes: {}", std::to_string(envelopes.size())); - SPDLOG_DEBUG("All Instruments: {}", std::to_string(allInsts.size())); - SPDLOG_DEBUG("Inst Offsets: {}", std::to_string(instOffsets.size())); - SPDLOG_DEBUG("Sample Bank: {}", sampleBank->name); - SPDLOG_DEBUG("Sample Bank Offset: {}", std::to_string(sampleBank->offset)); -} - -std::vector AudioManager::parse_seq_file(std::vector& buffer, uint32_t offset, bool isCTL){ - std::vector entries; - LUS::BinaryReader reader((char*) buffer.data(), buffer.size()); - reader.SetEndianness(Torch::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& data){ - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(Torch::Endianness::Big); - - CTLHeader header = { reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32() }; - - reader.Close(); - return header; -} - -Bank AudioManager::parse_ctl(CTLHeader header, std::vector 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 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 instrumentOffsets; - std::vector 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 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 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(); - auto sampleOffsets = std::vector(); - auto tunings = std::unordered_map(); - - 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>> allInsts; - bool needDrums = !drums.empty(); - for(auto &inst : insts){ - std::vector> sounds = {inst.soundLo, inst.soundMed, inst.soundHi}; - - if(needDrums && std::any_of(sounds.cbegin(), sounds.cend(), [&drums](std::optional 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 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> envData; - std::vector 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 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 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 AudioManager::parse_sound(std::vector data) { - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(Torch::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& data, uint32_t addr) { - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(Torch::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& 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& bankData){ - LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); - reader.SetEndianness(Torch::Endianness::Big); - reader.Seek(addr, LUS::SeekOffsetType::Start); - - std::optional> 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(); - 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& bankData){ - LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); - reader.SetEndianness(Torch::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 table; - std::vector 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& data, std::vector& bankData, SampleBank* sampleBank){ - LUS::BinaryReader reader((char*) data.data(), data.size()); - reader.SetEndianness(Torch::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 AudioManager::parse_envelope(uint32_t addr, std::vector& dataBank){ - std::vector entries; - LUS::BinaryReader reader((char*) dataBank.data(), dataBank.size()); - reader.SetEndianness(Torch::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& data, std::vector& entries) { - TBLFile tbl; - std::unordered_map 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& buffer, YAML::Node& data) { - - auto ctlOffset = data["ctl"]["offset"].as(); - auto ctlSize = data["ctl"]["size"].as(); - - auto tblOffset = data["tbl"]["offset"].as(); - auto tblSize = data["tbl"]["size"].as(); - - std::vector tbl = parse_seq_file(buffer, tblOffset, false); - std::vector ctl = parse_seq_file(buffer, ctlOffset, true); - - SPDLOG_INFO("Raw TBL Entries: {}", tbl.size()); - SPDLOG_INFO("Raw CTL Entries: {}", ctl.size()); - - std::vector tbl_data = PyUtils::slice(buffer, tblOffset, tblOffset + tblSize); - std::vector ctl_data = PyUtils::slice(buffer, ctlOffset, ctlOffset + ctlSize); - this->loaded_tbl = parse_tbl(tbl_data, tbl); - - SPDLOG_INFO("Processed TBL Entries: {}", this->loaded_tbl.tbls.size()); - SPDLOG_INFO("Processed TBL Banks: {}", this->loaded_tbl.banks.size()); - - 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; - SPDLOG_INFO("Processed Bank {}", 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){ - 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((uint32_t) BSWAP32(section)); \ - LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ - tmp.SetEndianness(Torch::Endianness::Big); \ - -#define START_CUSTOM_SECTION(section) \ - { \ - LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ - tmp.SetEndianness(Torch::Endianness::Big); \ - out.Write((uint32_t) 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((uint32_t) 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((uint32_t) 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((uint32_t) 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(reinterpret_cast(&one), 2); - tmp.Write(reinterpret_cast(&one), 2); - tmp.Write(entry->loop.start); - tmp.Write(entry->loop.end); - tmp.Write(entry->loop.count); - for(size_t i = 0; i < 16; i++){ - int16_t loop = BSWAP16(entry->loop.state.value()[i]); - tmp.Write(reinterpret_cast(&loop), 2); - } - END_SECTION(); - } - - len += 4; - out.Seek(4, LUS::SeekOffsetType::Start); - out.Write((uint32_t) BSWAP32(len)); - -} - -void AudioManager::bind_sample(YAML::Node& node, const std::string& path){ - auto id = GetSafeNode(node, "id"); - sample_table[id] = path; -} - -std::string& AudioManager::get_sample(uint32_t id) { - if(!sample_table.contains(id)) { - throw std::runtime_error("Failed to find sample with id " + std::to_string(id)); - } - return sample_table[id]; -} - -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 = 0; - 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]; - } - } - } - - SPDLOG_ERROR("Invalid Index {}", index); - 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 AudioManager::get_banks() { - return this->banks; -} \ No newline at end of file diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h deleted file mode 100644 index b5a9dcc..0000000 --- a/src/audio/AudioManager.h +++ /dev/null @@ -1,166 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "binarytools/BinaryWriter.h" -#include -#include - -#define NONE 0xFFFF -#define ALIGN(val, al) (size_t) ((val + (al - 1)) & -al) - -namespace AIFC { - enum MagicValues { - FORM = 0x464f524d, - AIFC = 0x41494643, - COMM = 0x434f4d4d, - INST = 0x494e5354, - VAPC = 0x56415043, - SSND = 0x53534e44, - AAPL = 0x4150504c, - stoc = 0x73746f63, - }; -} - -struct Entry { - uint32_t offset; - uint32_t length; -}; - -struct AudioBankSound { - uint32_t offset; - float tuning; - bool operator==(const AudioBankSound& other) const { - return offset == other.offset && tuning == other.tuning; - } -}; - -struct Instrument { - bool valid; - std::string name; - uint32_t offset; - uint8_t releaseRate; - uint8_t normalRangeLo; - uint8_t normalRangeHi; - uint32_t envelope; - std::optional soundLo; - std::optional soundMed; - std::optional soundHi; -}; - -struct AdpcmBook { - int32_t order{}; - int32_t npredictors{}; - std::vector table; - bool operator==(const AdpcmBook& 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; - AudioBankSound 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 AdpcmLoop { - uint32_t start{}; - uint32_t end{}; - int32_t count{}; - uint32_t pad{}; - std::optional> state; - bool operator==(const AdpcmLoop& other) const { - return start == other.start && end == other.end && count == other.count && pad == other.pad && state == other.state; - } -}; - -struct AudioBankSample { - std::string name; - std::vector data; - AdpcmBook book; - AdpcmLoop loop; - std::vector tunings; -}; - -struct SampleBank { - std::string name; - uint32_t offset; - std::vector data; - std::map entries; - - AudioBankSample* AddSample(uint32_t addr, size_t sampleSize, const AdpcmBook& book, const AdpcmLoop& loop); -}; - -struct TBLFile { - std::vector banks; - std::vector tbls; - std::map map; -}; - -struct CTLHeader { - uint32_t instruments; - uint32_t numDrums; - uint32_t shared; -}; - -struct AdsrEnvelope { - int16_t delay; - int16_t arg; -}; - -struct Envelope { - std::string name; - std::vector entries; -}; - -struct Bank { - std::string name; - SampleBank* sampleBank; - std::vector insts; - std::vector drums; - std::vector>> allInsts; - std::vector instOffsets; - std::map envelopes; - std::map samples; - void print() const; -}; - -class AudioManager { -public: - static AudioManager* Instance; - void initialize(std::vector& buffer, YAML::Node& data); - void bind_sample(YAML::Node& node, const std::string& path); - void create_aifc(int32_t index, LUS::BinaryWriter& writer); - std::string& get_sample(uint32_t id); - AudioBankSample get_aifc(int32_t index); - std::map get_banks(); - uint32_t get_index(AudioBankSample* bank); - -private: - std::map banks; - std::map sampleMap; - TBLFile loaded_tbl; - - static std::vector parse_seq_file(std::vector& buffer, uint32_t offset, bool isCTL); - static CTLHeader parse_ctl_header(std::vector& data); - static std::optional parse_sound(std::vector data); - static Drum parse_drum(std::vector& data, uint32_t addr); - static Instrument parse_inst(std::vector& data, uint32_t addr); - static AdpcmLoop parse_loop(uint32_t addr, std::vector& bankData); - static AdpcmBook parse_book(uint32_t addr, std::vector& bankData); - static AudioBankSample* parse_sample(std::vector& data, std::vector& bankData, SampleBank* sampleBank); - static std::vector parse_envelope(uint32_t addr, std::vector& dataBank); - static Bank parse_ctl(CTLHeader header, std::vector data, SampleBank* bank, uint32_t index); - static TBLFile parse_tbl(std::vector& data, std::vector& entries); - - static void write_aifc(AudioBankSample* entry, LUS::BinaryWriter& writer); -}; \ No newline at end of file diff --git a/src/factories/AudioHeaderFactory.cpp b/src/factories/AudioHeaderFactory.cpp deleted file mode 100644 index 1644a35..0000000 --- a/src/factories/AudioHeaderFactory.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "AudioHeaderFactory.h" - -#include -#include "audio/AudioManager.h" - -std::optional> AudioHeaderFactory::parse(std::vector& buffer, YAML::Node& data) { - AudioManager::Instance->initialize(buffer, data); - return std::make_shared(); -} \ No newline at end of file diff --git a/src/factories/AudioHeaderFactory.h b/src/factories/AudioHeaderFactory.h deleted file mode 100644 index ce61413..0000000 --- a/src/factories/AudioHeaderFactory.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "BaseFactory.h" - -class AudioDummyExporter : public BaseExporter { -public: - ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override { - return std::nullopt; - } -}; - -class AudioHeaderFactory : public BaseFactory { -public: - std::optional> parse(std::vector& buffer, YAML::Node& data) override; - std::optional> parse_modding(std::vector& buffer, YAML::Node& data) override { - return std::nullopt; - } - std::unordered_map> GetExporters() override { - return { - REGISTER(Modding, AudioDummyExporter) - REGISTER(Header, AudioDummyExporter) - REGISTER(Binary, AudioDummyExporter) - REGISTER(Code, AudioDummyExporter) - }; - } - bool SupportModdedAssets() override { return true; } -}; \ No newline at end of file diff --git a/src/factories/BankFactory.cpp b/src/factories/BankFactory.cpp deleted file mode 100644 index 6e72c53..0000000 --- a/src/factories/BankFactory.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "BankFactory.h" -#include "Companion.h" - -ExportResult BankBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { - auto writer = LUS::BinaryWriter(); - auto bank = std::static_pointer_cast(raw); - - WriteHeader(writer, Torch::ResourceType::Bank, 0); - writer.Write(bank->mBankId); - writer.Write(static_cast(bank->mBank.insts.size())); - for (auto& instrument : bank->mBank.insts) { - writer.Write(static_cast(instrument.valid)); - if(!instrument.valid) continue; - - writer.Write(instrument.releaseRate); - writer.Write(instrument.normalRangeLo); - writer.Write(instrument.normalRangeHi); - - auto [name, entries] = bank->mBank.envelopes[instrument.envelope]; - if(!entries.empty()){ - writer.Write(static_cast(entries.size())); - for (auto& [delay, arg] : entries) { - writer.Write(static_cast(delay)); - writer.Write(static_cast(arg)); - } - } else { - writer.Write(static_cast(0)); - } - - uint32_t soundFlags = 0; - const bool hasLo = instrument.soundLo.has_value(); - const bool hasMed = instrument.soundMed.has_value(); - const bool hasHi = instrument.soundHi.has_value(); - if(hasLo){ - soundFlags |= 1 << 0; - } - if(hasMed){ - soundFlags |= 1 << 1; - } - if(hasHi){ - soundFlags |= 1 << 2; - } - - writer.Write(soundFlags); - if(hasLo){ - auto [offset, tuning] = instrument.soundLo.value(); - const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); - writer.Write(AudioManager::Instance->get_sample(idx)); - writer.Write(tuning); - } - if(hasMed){ - auto [offset, tuning] = instrument.soundMed.value(); - const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); - writer.Write(AudioManager::Instance->get_sample(idx)); - writer.Write(tuning); - } - if(hasHi){ - auto [offset, tuning] = instrument.soundHi.value(); - const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); - writer.Write(AudioManager::Instance->get_sample(idx)); - writer.Write(tuning); - } - } - - writer.Write(static_cast(bank->mBank.drums.size())); - for(auto &drum : bank->mBank.drums){ - writer.Write(drum.releaseRate); - writer.Write(drum.pan); - auto envelope = bank->mBank.envelopes[drum.envelope]; - if(!envelope.entries.empty()){ - writer.Write(static_cast(envelope.entries.size())); - for(auto &entry : envelope.entries){ - writer.Write(entry.delay); - writer.Write(entry.arg); - } - } else { - writer.Write(static_cast(0)); - } - - const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[drum.sound.offset]); - const auto out = AudioManager::Instance->get_sample(idx); - writer.Write(out); - writer.Write(drum.sound.tuning); - } - - writer.Finish(write); - return std::nullopt; -} - -std::optional> BankFactory::parse(std::vector& buffer, YAML::Node& data) { - auto banks = AudioManager::Instance->get_banks(); - auto bankId = data["id"].as(); - - if(AudioManager::Instance == nullptr){ - throw std::runtime_error("AudioManager not initialized"); - } - - return std::make_shared(banks[bankId], bankId); -} \ No newline at end of file diff --git a/src/factories/BankFactory.h b/src/factories/BankFactory.h deleted file mode 100644 index cb69955..0000000 --- a/src/factories/BankFactory.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include "BaseFactory.h" -#include "audio/AudioManager.h" - -class BankData : public IParsedData { -public: - Bank mBank; - uint32_t mBankId; - - BankData(Bank bank, const uint32_t bankId) : mBank(std::move(bank)), mBankId(bankId) {} -}; - -class BankBinaryExporter : public BaseExporter { - ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; -}; - -class BankFactory : public BaseFactory { -public: - std::optional> parse(std::vector& buffer, YAML::Node& data) override; - inline std::unordered_map> GetExporters() override { - return { - REGISTER(Binary, BankBinaryExporter) - }; - } -}; \ No newline at end of file diff --git a/src/factories/SampleFactory.cpp b/src/factories/SampleFactory.cpp deleted file mode 100644 index b8bc7fa..0000000 --- a/src/factories/SampleFactory.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "SampleFactory.h" - -ExportResult SampleBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { - auto writer = LUS::BinaryWriter(); - auto sample = std::static_pointer_cast(raw)->mSample; - - WriteHeader(writer, Torch::ResourceType::Sample, 0); - writer.Write(sample.loop.start); - writer.Write(sample.loop.end); - writer.Write(sample.loop.count); - writer.Write(sample.loop.pad); - - if(sample.loop.state.has_value()){ - auto state = sample.loop.state.value(); - writer.Write(static_cast(state.size())); - writer.Write(reinterpret_cast(state.data()), state.size() * sizeof(int16_t)); - } else { - writer.Write(static_cast(0)); - } - - writer.Write(sample.book.order); - writer.Write(sample.book.npredictors); - - writer.Write(static_cast(sample.book.table.size())); - writer.Write(reinterpret_cast(sample.book.table.data()), sample.book.table.size() * sizeof(int16_t)); - - writer.Write(static_cast(sample.data.size())); - writer.Write(reinterpret_cast(sample.data.data()), sample.data.size()); - - writer.Write(sample.name); - - writer.Finish(write); - return std::nullopt; -} - -std::optional> SampleFactory::parse(std::vector& buffer, YAML::Node& data) { - const auto id = data["id"].as(); - if(AudioManager::Instance == nullptr){ - throw std::runtime_error("AudioManager not initialized"); - } - AudioBankSample entry = AudioManager::Instance->get_aifc(id); - return std::make_shared(entry); -} \ No newline at end of file diff --git a/src/factories/SampleFactory.h b/src/factories/SampleFactory.h deleted file mode 100644 index 7e647fd..0000000 --- a/src/factories/SampleFactory.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include "BaseFactory.h" -#include "audio/AudioManager.h" - -class SampleData : public IParsedData { -public: - AudioBankSample mSample; - - explicit SampleData(AudioBankSample sample) : mSample(std::move(sample)) {} -}; - -class SampleBinaryExporter : public BaseExporter { - ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; -}; - -class SampleFactory : public BaseFactory { -public: - std::optional> parse(std::vector& buffer, YAML::Node& data) override; - std::unordered_map> GetExporters() override { - return { - REGISTER(Binary, SampleBinaryExporter) - }; - } -}; \ No newline at end of file diff --git a/src/factories/SequenceFactory.cpp b/src/factories/SequenceFactory.cpp deleted file mode 100644 index 1090bac..0000000 --- a/src/factories/SequenceFactory.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "SequenceFactory.h" - -ExportResult SequenceBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { - auto writer = LUS::BinaryWriter(); - const auto sequence = std::static_pointer_cast(raw); - - WriteHeader(writer, Torch::ResourceType::Sequence, 0); - writer.Write(sequence->mId); - writer.Write(static_cast(sequence->mBanks.size())); - for (auto& bank : sequence->mBanks) { - writer.Write(bank); - } - writer.Write( sequence->mSize); - writer.Write(reinterpret_cast(sequence->mBuffer.data()), sequence->mSize); - writer.Finish(write); - return std::nullopt; -} - -std::optional> SequenceFactory::parse(std::vector& buffer, YAML::Node& data) { - auto id = data["id"].as(); - auto size = data["size"].as(); - const auto offset = data["offset"].as(); - auto banks = data["banks"].as>(); - return std::make_shared(id, size, buffer.data() + offset, banks); -} \ No newline at end of file diff --git a/src/factories/SequenceFactory.h b/src/factories/SequenceFactory.h deleted file mode 100644 index 7eb4192..0000000 --- a/src/factories/SequenceFactory.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "BaseFactory.h" - -class SequenceData : public IParsedData { -public: - uint32_t mId; - uint32_t mSize; - std::vector mBuffer; - std::vector mBanks; - - SequenceData(uint32_t id, uint32_t size, uint8_t* data, std::vector& banks) : mId(id), mSize(size), mBanks(banks) { - mBuffer = std::vector(data, data + size); - } -}; - -class SequenceBinaryExporter : public BaseExporter { - ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; -}; - -class SequenceFactory : public BaseFactory { -public: - std::optional> parse(std::vector& buffer, YAML::Node& data) override; - inline std::unordered_map> GetExporters() override { - return { - REGISTER(Binary, SequenceBinaryExporter) - }; - } -}; \ No newline at end of file diff --git a/src/factories/mk64/CourseMetadata.h b/src/factories/mk64/CourseMetadata.h index 1c4bbb8..6394210 100644 --- a/src/factories/mk64/CourseMetadata.h +++ b/src/factories/mk64/CourseMetadata.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/CourseVtx.h b/src/factories/mk64/CourseVtx.h index ffe876b..e348141 100644 --- a/src/factories/mk64/CourseVtx.h +++ b/src/factories/mk64/CourseVtx.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/DrivingBehaviour.h b/src/factories/mk64/DrivingBehaviour.h index c4fd46e..57d84b0 100644 --- a/src/factories/mk64/DrivingBehaviour.h +++ b/src/factories/mk64/DrivingBehaviour.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/ItemCurve.h b/src/factories/mk64/ItemCurve.h index c58e825..1077b57 100644 --- a/src/factories/mk64/ItemCurve.h +++ b/src/factories/mk64/ItemCurve.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/SpawnData.h b/src/factories/mk64/SpawnData.h index 168208d..ce43b73 100644 --- a/src/factories/mk64/SpawnData.h +++ b/src/factories/mk64/SpawnData.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/TrackSections.h b/src/factories/mk64/TrackSections.h index 839661c..fe0f369 100644 --- a/src/factories/mk64/TrackSections.h +++ b/src/factories/mk64/TrackSections.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/UnkSpawnData.h b/src/factories/mk64/UnkSpawnData.h index 0467c31..9bd4765 100644 --- a/src/factories/mk64/UnkSpawnData.h +++ b/src/factories/mk64/UnkSpawnData.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/mk64/Waypoints.h b/src/factories/mk64/Waypoints.h index 6e70803..28ec98c 100644 --- a/src/factories/mk64/Waypoints.h +++ b/src/factories/mk64/Waypoints.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace MK64 { diff --git a/src/factories/sf64/AnimFactory.h b/src/factories/sf64/AnimFactory.h index cef770d..f3fec76 100644 --- a/src/factories/sf64/AnimFactory.h +++ b/src/factories/sf64/AnimFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SF64 { diff --git a/src/factories/sf64/ColPolyFactory.h b/src/factories/sf64/ColPolyFactory.h index 4b3dd64..c245d48 100644 --- a/src/factories/sf64/ColPolyFactory.h +++ b/src/factories/sf64/ColPolyFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "types/Vec3D.h" namespace SF64 { diff --git a/src/factories/sf64/EnvironmentFactory.h b/src/factories/sf64/EnvironmentFactory.h index a184113..873c7a0 100644 --- a/src/factories/sf64/EnvironmentFactory.h +++ b/src/factories/sf64/EnvironmentFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "types/Vec3D.h" namespace SF64 { diff --git a/src/factories/sf64/HitboxFactory.h b/src/factories/sf64/HitboxFactory.h index 69d20bf..f269d72 100644 --- a/src/factories/sf64/HitboxFactory.h +++ b/src/factories/sf64/HitboxFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SF64 { diff --git a/src/factories/sf64/MessageFactory.h b/src/factories/sf64/MessageFactory.h index c000c36..8cf78fb 100644 --- a/src/factories/sf64/MessageFactory.h +++ b/src/factories/sf64/MessageFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SF64 { diff --git a/src/factories/sf64/MessageLookupFactory.h b/src/factories/sf64/MessageLookupFactory.h index f647121..5a1f503 100644 --- a/src/factories/sf64/MessageLookupFactory.h +++ b/src/factories/sf64/MessageLookupFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SF64 { diff --git a/src/factories/sf64/ObjInitFactory.h b/src/factories/sf64/ObjInitFactory.h index 55b0171..cb985c8 100644 --- a/src/factories/sf64/ObjInitFactory.h +++ b/src/factories/sf64/ObjInitFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "types/Vec3D.h" namespace SF64 { diff --git a/src/factories/sf64/ScriptFactory.h b/src/factories/sf64/ScriptFactory.h index aa3e22d..759bc6f 100644 --- a/src/factories/sf64/ScriptFactory.h +++ b/src/factories/sf64/ScriptFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SF64 { diff --git a/src/factories/sf64/SkeletonFactory.h b/src/factories/sf64/SkeletonFactory.h index c35e04b..8206766 100644 --- a/src/factories/sf64/SkeletonFactory.h +++ b/src/factories/sf64/SkeletonFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "types/Vec3D.h" namespace SF64 { diff --git a/src/factories/sm64/AnimationFactory.h b/src/factories/sm64/AnimationFactory.h index e5a86dd..9819e5f 100644 --- a/src/factories/sm64/AnimationFactory.h +++ b/src/factories/sm64/AnimationFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SM64 { class AnimationData : public IParsedData { diff --git a/src/factories/sm64/CollisionFactory.h b/src/factories/sm64/CollisionFactory.h index dcf7aba..28272ee 100644 --- a/src/factories/sm64/CollisionFactory.h +++ b/src/factories/sm64/CollisionFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "collision/SpecialPresetNames.h" #include "collision/SurfaceTerrains.h" diff --git a/src/factories/sm64/DialogFactory.h b/src/factories/sm64/DialogFactory.h index ba6579d..2ff9e29 100644 --- a/src/factories/sm64/DialogFactory.h +++ b/src/factories/sm64/DialogFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SM64 { class DialogData : public IParsedData { diff --git a/src/factories/sm64/DictionaryFactory.h b/src/factories/sm64/DictionaryFactory.h index a3cd8d2..c4051ad 100644 --- a/src/factories/sm64/DictionaryFactory.h +++ b/src/factories/sm64/DictionaryFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include namespace SM64 { diff --git a/src/factories/sm64/GeoLayoutFactory.h b/src/factories/sm64/GeoLayoutFactory.h index 7f41a95..8792990 100644 --- a/src/factories/sm64/GeoLayoutFactory.h +++ b/src/factories/sm64/GeoLayoutFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "geo/GeoCommand.h" #include #include diff --git a/src/factories/sm64/TextFactory.h b/src/factories/sm64/TextFactory.h index e7840ac..df05a0d 100644 --- a/src/factories/sm64/TextFactory.h +++ b/src/factories/sm64/TextFactory.h @@ -1,6 +1,6 @@ #pragma once -#include "../BaseFactory.h" +#include #include "../../types/RawBuffer.h" namespace SM64 { diff --git a/src/factories/sm64/audio/AIFCDecode.cpp b/src/factories/sm64/audio/AIFCDecode.cpp new file mode 100644 index 0000000..e627ad0 --- /dev/null +++ b/src/factories/sm64/audio/AIFCDecode.cpp @@ -0,0 +1,664 @@ +/** + * Bruteforcing decoder for converting ADPCM-encoded AIFC into AIFF, in a way + * that roundtrips with vadpcm_enc. + */ +#include "AIFCDecode.h" +#include +#include +#include +#include +#include +#include +#include "binarytools/BinaryWriter.h" +#include "BinaryReader.h" + +typedef signed char s8; +typedef short s16; +typedef int s32; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef float f32; + +#undef BSWAP16 +#undef BSWAP32 +#undef BSWAP64 + +#ifdef _MSC_VER +#define bswap16(x) _byteswap_ushort(x) +#define bswap32(x) _byteswap_ulong(x) +#define BSWAP16(x) x = bswap16(x) +#define BSWAP32(x) x = bswap32(x) +#else +#define bswap16(x) __builtin_bswap16(x) +#define bswap32(x) __builtin_bswap32(x) +#define BSWAP16(x) x = bswap16(x) +#define BSWAP32(x) x = bswap32(x) +#endif + +#define BSWAP16_MANY(x, n) for (s32 _i = 0; _i < n; _i++) BSWAP16((x)[_i]) + +#ifndef WIN32 +#define NORETURN __attribute__((noreturn)) +#define UNUSED __attribute__((unused)) +#else +#define NORETURN +#define UNUSED +#endif + +typedef struct { + u32 ckID; + u32 ckSize; +} ChunkHeader; + +typedef struct { + u32 ckID; + u32 ckSize; + u32 formType; +} Chunk; + +typedef struct { + s16 numChannels; + u16 numFramesH; + u16 numFramesL; + s16 sampleSize; + s16 sampleRate[5]; // 80-bit float + u16 compressionTypeH; + u16 compressionTypeL; +} CommonChunk; + +typedef struct { + s16 MarkerID; + u16 positionH; + u16 positionL; +} Marker; + +typedef struct { + s16 playMode; + s16 beginLoop; + s16 endLoop; +} Loop; + +typedef struct { + s8 baseNote; + s8 detune; + s8 lowNote; + s8 highNote; + s8 lowVelocity; + s8 highVelocity; + s16 gain; + Loop sustainLoop; + Loop releaseLoop; +} InstrumentChunk; + +typedef struct { + s32 offset; + s32 blockSize; +} SoundDataChunk; + +typedef struct { + s16 version; + s16 order; + s16 nEntries; +} CodeChunk; + +typedef struct +{ + u32 start; + u32 end; + u32 count; + s16 state[16]; +} ALADPCMloop; + +#define checked_fread(a, b, c, d) d.Read((char*) a, b * c) + +NORETURN +void fail_parse(const char *fmt, ...) +{ + char *formatted = nullptr; + va_list ap; + va_start(ap, fmt); + int size = vsnprintf(nullptr, 0, fmt, ap); + va_end(ap); + if (size >= 0) { + size++; + formatted = static_cast(malloc(size)); + if (formatted != nullptr) { + va_start(ap, fmt); + size = vsnprintf(formatted, size, fmt, ap); + va_end(ap); + if (size < 0) { + free(formatted); + formatted = nullptr; + } + } + } + + if (formatted != nullptr) { + throw std::runtime_error(formatted); + free(formatted); + } + throw std::runtime_error("Error parsing file"); +} + +s32 myrand() +{ + static u64 state = 1619236481962341ULL; + state *= 3123692312231ULL; + state++; + return state >> 33; +} + +s16 qsample(s32 x, s32 scale) +{ + // Compute x / 2^scale rounded to the nearest integer, breaking ties towards zero. + if (scale == 0) return x; + return (x + (1 << (scale - 1)) - (x > 0)) >> scale; +} + +s16 clamp_to_s16(s32 x) +{ + if (x < -0x8000) return -0x8000; + if (x > 0x7fff) return 0x7fff; + return (s16) x; +} + +s32 toi4(s32 x) +{ + if (x >= 8) return x - 16; + return x; +} + +s32 readaifccodebook(LUS::BinaryReader& fhandle, s32 ****table, s16 *order, s16 *npredictors) +{ + checked_fread(order, sizeof(s16), 1, fhandle); + BSWAP16(*order); + checked_fread(npredictors, sizeof(s16), 1, fhandle); + BSWAP16(*npredictors); + *table = static_cast(malloc(*npredictors * sizeof(s32 **))); + for (s32 i = 0; i < *npredictors; i++) { + (*table)[i] = static_cast(malloc(8 * sizeof(s32 *))); + for (s32 j = 0; j < 8; j++) { + (*table)[i][j] = static_cast(malloc((*order + 8) * sizeof(s32))); + } + } + + for (s32 i = 0; i < *npredictors; i++) { + s32 **table_entry = (*table)[i]; + for (s32 j = 0; j < *order; j++) { + for (s32 k = 0; k < 8; k++) { + s16 ts; + checked_fread(&ts, sizeof(s16), 1, fhandle); + BSWAP16(ts); + table_entry[k][j] = ts; + } + } + + for (s32 k = 1; k < 8; k++) { + table_entry[k][*order] = table_entry[k - 1][*order - 1]; + } + + table_entry[0][*order] = 1 << 11; + + for (s32 k = 1; k < 8; k++) { + s32 j = 0; + for (; j < k; j++) { + table_entry[j][k + *order] = 0; + } + + for (; j < 8; j++) { + table_entry[j][k + *order] = table_entry[j - k][*order]; + } + } + } + return 0; +} + +ALADPCMloop *readlooppoints(LUS::BinaryReader& reader, s16 *nloops) { + BSWAP16(*nloops); + checked_fread(nloops, sizeof(s16), 1, reader); + auto *al = static_cast(malloc(*nloops * sizeof(ALADPCMloop))); + for (s32 i = 0; i < *nloops; i++) { + checked_fread(&al[i], sizeof(ALADPCMloop), 1, reader); + BSWAP32(al[i].start); + BSWAP32(al[i].end); + BSWAP32(al[i].count); + BSWAP16_MANY(al[i].state, 16); + } + return al; +} + +s32 inner_product(s32 length, s32 *v1, s32 *v2) +{ + s32 out = 0; + for (s32 i = 0; i < length; i++) { + out += v1[i] * v2[i]; + } + + // Compute "out / 2^11", rounded down. + s32 dout = out / (1 << 11); + s32 fiout = dout * (1 << 11); + return dout - (out - fiout < 0); +} + +void my_decodeframe(u8 *frame, s32 *state, s32 order, s32 ***coefTable) +{ + s32 ix[16]; + + u8 header = frame[0]; + s32 scale = 1 << (header >> 4); + s32 optimalp = header & 0xf; + + for (s32 i = 0; i < 16; i += 2) { + u8 c = frame[1 + i/2]; + ix[i] = c >> 4; + ix[i + 1] = c & 0xf; + } + + for (s32 i = 0; i < 16; i++) { + if (ix[i] >= 8) ix[i] -= 16; + ix[i] *= scale; + } + + for (s32 j = 0; j < 2; j++) { + s32 in_vec[16]; + if (j == 0) { + for (s32 i = 0; i < order; i++) { + in_vec[i] = state[16 - order + i]; + } + } else { + for (s32 i = 0; i < order; i++) { + in_vec[i] = state[8 - order + i]; + } + } + + for (s32 i = 0; i < 8; i++) { + s32 ind = j * 8 + i; + in_vec[order + i] = ix[ind]; + state[ind] = inner_product(order + i, coefTable[optimalp][i], in_vec) + ix[ind]; + } + } +} + +void my_encodeframe(u8 *out, s16 *inBuffer, s32 *state, s32 ***coefTable, s32 order, s32 npredictors) +{ + s16 ix[16]; + s32 prediction[16]; + s32 inVector[16]; + s32 saveState[16]; + s32 optimalp = 0; + s32 scale; + s32 ie[16]; + s32 e[16]; + f32 min = 1e30; + + for (s32 k = 0; k < npredictors; k++) { + for (s32 j = 0; j < 2; j++) { + for (s32 i = 0; i < order; i++) { + inVector[i] = (j == 0 ? state[16 - order + i] : inBuffer[8 - order + i]); + } + + for (s32 i = 0; i < 8; i++) { + prediction[j * 8 + i] = inner_product(order + i, coefTable[k][i], inVector); + e[j * 8 + i] = inVector[i + order] = inBuffer[j * 8 + i] - prediction[j * 8 + i]; + } + } + + f32 se = 0.0f; + for (s32 j = 0; j < 16; j++) { + se += (f32) e[j] * (f32) e[j]; + } + + if (se < min) { + min = se; + optimalp = k; + } + } + + for (s32 j = 0; j < 2; j++) { + for (s32 i = 0; i < order; i++) { + inVector[i] = (j == 0 ? state[16 - order + i] : inBuffer[8 - order + i]); + } + + for (s32 i = 0; i < 8; i++) { + prediction[j * 8 + i] = inner_product(order + i, coefTable[optimalp][i], inVector); + e[j * 8 + i] = inVector[i + order] = inBuffer[j * 8 + i] - prediction[j * 8 + i]; + } + } + + for (s32 i = 0; i < 16; i++) { + ie[i] = clamp_to_s16(e[i]); + } + + s32 max = 0; + for (s32 i = 0; i < 16; i++) { + if (abs(ie[i]) > abs(max)) { + max = ie[i]; + } + } + + for (scale = 0; scale <= 12; scale++) { + if (max <= 7 && max >= -8) break; + max /= 2; + } + + for (s32 i = 0; i < 16; i++) { + saveState[i] = state[i]; + } + + for (s32 nIter = 0, again = 1; nIter < 2 && again; nIter++) { + again = 0; + if (nIter == 1) scale++; + if (scale > 12) { + scale = 12; + } + + for (s32 j = 0; j < 2; j++) { + s32 base = j * 8; + for (s32 i = 0; i < order; i++) { + inVector[i] = (j == 0 ? + saveState[16 - order + i] : state[8 - order + i]); + } + + for (s32 i = 0; i < 8; i++) { + prediction[base + i] = inner_product(order + i, coefTable[optimalp][i], inVector); + s32 se = inBuffer[base + i] - prediction[base + i]; + ix[base + i] = qsample(se, scale); + s32 cV = clamp_to_s16(ix[base + i]) - ix[base + i]; + if (cV > 1 || cV < -1) again = 1; + ix[base + i] += cV; + inVector[i + order] = ix[base + i] * (1 << scale); + state[base + i] = prediction[base + i] + inVector[i + order]; + } + } + } + + u8 header = (scale << 4) | (optimalp & 0xf); + out[0] = header; + for (s32 i = 0; i < 16; i += 2) { + u8 c = ((ix[i] & 0xf) << 4) | (ix[i + 1] & 0xf); + out[1 + i/2] = c; + } +} + +void permute(s16 *out, s32 *in, s32 scale) +{ + for (s32 i = 0; i < 16; i++) { + out[i] = clamp_to_s16(in[i] - scale / 2 + myrand() % (scale + 1)); + } +} + +void write_header(LUS::BinaryWriter& writer, const char *id, s32 size) { + writer.Write((char*) id, (size_t) 4); + BSWAP32(size); + writer.Write(size); +} + +void write_aiff(std::vector data, LUS::BinaryWriter& writer) { + s16 order = -1; + s16 nloops = 0; + ALADPCMloop *aloops = nullptr; + s16 npredictors = -1; + s32 ***coefTable = nullptr; + s32 state[16]; + s32 soundPointer = -1; + s32 currPos = 0; + s32 nSamples = 0; + Chunk FormChunk; + ChunkHeader Header; + CommonChunk CommChunk; + InstrumentChunk InstChunk; + SoundDataChunk SndDChunk; + + memset(&InstChunk, 0, sizeof(InstChunk)); + LUS::BinaryReader reader((char*)data.data(), data.size()); + + checked_fread(&FormChunk, sizeof(FormChunk), 1, reader); + BSWAP32(FormChunk.ckID); + BSWAP32(FormChunk.formType); + if ((FormChunk.ckID != 0x464f524d) || (FormChunk.formType != 0x41494643)) { // FORM, AIFC + fail_parse("not an AIFF-C file"); + } + + for (;;) { + if (reader.GetBaseAddress() >= reader.GetLength()) { + break; + } + reader.Read((char*) &Header, sizeof(Header)); + u32 ts; + BSWAP32(Header.ckID); + BSWAP32(Header.ckSize); + + Header.ckSize++; + Header.ckSize &= ~1; + s32 offset = reader.GetBaseAddress(); + + if(Header.ckID == 0x434f4d4d) { // COMM + checked_fread(&CommChunk, sizeof(CommChunk), 1, reader); + BSWAP16(CommChunk.numChannels); + BSWAP16(CommChunk.numFramesH); + BSWAP16(CommChunk.numFramesL); + BSWAP16(CommChunk.sampleSize); + BSWAP16(CommChunk.compressionTypeH); + BSWAP16(CommChunk.compressionTypeL); + s32 cType = (CommChunk.compressionTypeH << 16) + CommChunk.compressionTypeL; + if (cType != 0x56415043) { // VAPC + fail_parse("file is of the wrong compression type"); + } + if (CommChunk.numChannels != 1) { + fail_parse("file contains %d channels, only 1 channel supported", CommChunk.numChannels); + } + if (CommChunk.sampleSize != 16) { + fail_parse("file contains %d bit samples, only 16 bit samples supported", CommChunk.sampleSize); + } + + nSamples = (CommChunk.numFramesH << 16) + CommChunk.numFramesL; + + // Allow broken input lengths + if (nSamples % 16) { + nSamples--; + } + + if (nSamples % 16 != 0) { + fail_parse("number of chunks must be a multiple of 16, found %d", nSamples); + } + } + + if(Header.ckID == 0x53534e44) { //SSND + checked_fread(&SndDChunk, sizeof(SndDChunk), 1, reader); + BSWAP32(SndDChunk.offset); + BSWAP32(SndDChunk.blockSize); + assert(SndDChunk.offset == 0); + assert(SndDChunk.blockSize == 0); + soundPointer = reader.GetBaseAddress(); + } + + if(Header.ckID == 0x4150504c){ // APPL + checked_fread(&ts, sizeof(u32), 1, reader); + BSWAP32(ts); + if (ts == 0x73746f63) { // stoc + u8 len; + checked_fread(&len, 1, 1, reader); + if (len == 11) { + char ChunkName[12]; + s16 version; + checked_fread(ChunkName, 11, 1, reader); + ChunkName[11] = '\0'; + if (strcmp("VADPCMCODES", ChunkName) == 0) { + checked_fread(&version, sizeof(s16), 1, reader); + BSWAP16(version); + if (version != 1) { + fail_parse("Unknown codebook chunk version"); + } + readaifccodebook(reader, &coefTable, &order, &npredictors); + } + else if (strcmp("VADPCMLOOPS", ChunkName) == 0) { + checked_fread(&version, sizeof(s16), 1, reader); + BSWAP16(version); + if (version != 1) { + fail_parse("Unknown loop chunk version"); + } + aloops = readlooppoints(reader, &nloops); + BSWAP16(nloops); + if (nloops != 1) { + fail_parse("Only a single loop supported"); + } + } + } + } + } + + reader.Seek(offset + Header.ckSize, LUS::SeekOffsetType::Start); + } + + if (coefTable == nullptr) { + fail_parse("Codebook missing from bitstream"); + } + + for (s32 i = 0; i < order; i++) { + state[15 - i] = 0; + } + + u32 outputBytes = nSamples * sizeof(s16); + + reader.Seek(soundPointer, LUS::SeekOffsetType::Start); + + while (currPos < nSamples) { + u8 input[9]; + u8 encoded[9]; + s32 lastState[16]; + s32 decoded[16]; + s16 guess[16]; + s16 origGuess[16]; + + memcpy(lastState, state, sizeof(lastState)); + checked_fread(input, 9, 1, reader); + + // Decode for real + my_decodeframe(input, state, order, coefTable); + memcpy(decoded, state, sizeof(lastState)); + + // Create a guess from that, by clamping to 16 bits + for (s32 i = 0; i < 16; i++) { + origGuess[i] = clamp_to_s16(state[i]); + } + + // Encode the guess + memcpy(state, lastState, sizeof(lastState)); + memcpy(guess, origGuess, sizeof(guess)); + my_encodeframe(encoded, guess, state, coefTable, order, npredictors); + + // If it doesn't match, randomly round numbers until it does. + if (memcmp(input, encoded, 9) != 0) { + s32 scale = 1 << (input[0] >> 4); + do { + permute(guess, decoded, scale); + memcpy(state, lastState, sizeof(lastState)); + my_encodeframe(encoded, guess, state, coefTable, order, npredictors); + } while (memcmp(input, encoded, 9) != 0); + + // Bring the matching closer to the original decode (not strictly + // necessary, but it will move us closer to the target on average). + for (s32 failures = 0; failures < 50; failures++) { + s32 ind = myrand() % 16; + s32 old = guess[ind]; + if (old == origGuess[ind]) continue; + guess[ind] = origGuess[ind]; + if (myrand() % 2) guess[ind] += (old - origGuess[ind]) / 2; + memcpy(state, lastState, sizeof(lastState)); + my_encodeframe(encoded, guess, state, coefTable, order, npredictors); + if (memcmp(input, encoded, 9) == 0) { + failures = -1; + } + else { + guess[ind] = old; + } + } + } + + memcpy(state, decoded, sizeof(lastState)); + BSWAP16_MANY(guess, 16); + writer.Seek(currPos * 2, LUS::SeekOffsetType::Start); + writer.Write((char*) guess, sizeof(guess)); + currPos += 16; + } + + // Write an incomplete file header. We'll fill in the size later. + writer.Seek(0, LUS::SeekOffsetType::Start); + writer.Write((char*) "FORM\0\0\0\0AIFF", 12); + + // Subtract 4 from the COMM size to skip the compression field. + write_header(writer, "COMM", sizeof(CommonChunk) - 4); + CommChunk.numFramesH = nSamples >> 16; + CommChunk.numFramesL = nSamples & 0xffff; + BSWAP16(CommChunk.numChannels); + BSWAP16(CommChunk.numFramesH); + BSWAP16(CommChunk.numFramesL); + BSWAP16(CommChunk.sampleSize); + writer.Write((char*) &CommChunk, sizeof(CommonChunk) - 4); + + if (nloops > 0) { + s32 startPos = aloops[0].start, endPos = aloops[0].end; + const char *markerNames[2] = {"start", "end"}; + Marker markers[2] = { + {1, static_cast((u16) startPos >> 16), static_cast((u16) startPos & 0xffff)}, + {2, static_cast(endPos >> 16), static_cast(endPos & 0xffff)} + }; + write_header(writer, "MARK", 2 + 2 * sizeof(Marker) + 1 + 5 + 1 + 3); + s16 numMarkers = bswap16(2); + writer.Write(numMarkers); + for (s32 i = 0; i < 2; i++) { + u8 len = (u8) strlen(markerNames[i]); + BSWAP16(markers[i].MarkerID); + BSWAP16(markers[i].positionH); + BSWAP16(markers[i].positionL); + writer.Write(markers[i].MarkerID); + writer.Write(markers[i].positionH); + writer.Write(markers[i].positionL); + writer.Write(len); + writer.Write((char*) markerNames[i], len); + } + + write_header(writer, "INST", sizeof(InstrumentChunk)); + InstChunk.sustainLoop.playMode = bswap16(1); + InstChunk.sustainLoop.beginLoop = bswap16(1); + InstChunk.sustainLoop.endLoop = bswap16(2); + InstChunk.releaseLoop.playMode = 0; + InstChunk.releaseLoop.beginLoop = 0; + InstChunk.releaseLoop.endLoop = 0; + writer.Write((char*) &InstChunk, sizeof(InstrumentChunk)); + } + + // Save the coefficient table for use when encoding. Ideally this wouldn't + // be needed and "tabledesign -s 1" would generate the right table, but in + // practice it's difficult to adjust samples to make that happen. + write_header(writer, "APPL", 4 + 12 + sizeof(CodeChunk) + npredictors * order * 8 * 2); + writer.Write("stoc", false); + CodeChunk cChunk; + cChunk.version = bswap16(1); + cChunk.order = bswap16(order); + cChunk.nEntries = bswap16(npredictors); + writer.Write("\x0bVADPCMCODES", false); + writer.Write((char*) &cChunk, sizeof(CodeChunk)); + for (s32 i = 0; i < npredictors; i++) { + for (s32 j = 0; j < order; j++) { + for (s32 k = 0; k < 8; k++) { + s16 ts = bswap16(coefTable[i][k][j]); + writer.Write(ts); + } + } + } + + write_header(writer, "SSND", outputBytes + 8); + SndDChunk.offset = 0; + SndDChunk.blockSize = 0; + writer.Write((char*) &SndDChunk, sizeof(SoundDataChunk)); + + // Fix the size in the header + s32 fileSize = bswap32(writer.GetLength() - 8); + writer.Seek(4, LUS::SeekOffsetType::Start); + writer.Write(fileSize); + + reader.Close(); +} diff --git a/src/factories/sm64/audio/AIFCDecode.h b/src/factories/sm64/audio/AIFCDecode.h new file mode 100644 index 0000000..e141a80 --- /dev/null +++ b/src/factories/sm64/audio/AIFCDecode.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include "BinaryWriter.h" + +void write_aiff(std::vector data, LUS::BinaryWriter& writer); \ No newline at end of file diff --git a/src/factories/sm64/audio/AudioHeaderFactory.cpp b/src/factories/sm64/audio/AudioHeaderFactory.cpp new file mode 100644 index 0000000..dc4d547 --- /dev/null +++ b/src/factories/sm64/audio/AudioHeaderFactory.cpp @@ -0,0 +1,9 @@ +#include "AudioHeaderFactory.h" + +#include +#include "AudioManager.h" + +std::optional> AudioHeaderFactory::parse(std::vector& buffer, YAML::Node& data) { + AudioManager::Instance->initialize(buffer, data); + return std::make_shared(); +} \ No newline at end of file diff --git a/src/factories/sm64/audio/AudioHeaderFactory.h b/src/factories/sm64/audio/AudioHeaderFactory.h new file mode 100644 index 0000000..1c5c0b1 --- /dev/null +++ b/src/factories/sm64/audio/AudioHeaderFactory.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class AudioDummyExporter : public BaseExporter { +public: + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override { + return std::nullopt; + } +}; + +class AudioHeaderFactory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + std::optional> parse_modding(std::vector& buffer, YAML::Node& data) override { + return std::nullopt; + } + std::unordered_map> GetExporters() override { + return { + REGISTER(Modding, AudioDummyExporter) + REGISTER(Header, AudioDummyExporter) + REGISTER(Binary, AudioDummyExporter) + REGISTER(Code, AudioDummyExporter) + }; + } + bool SupportModdedAssets() override { return true; } +}; \ No newline at end of file diff --git a/src/factories/sm64/audio/AudioManager.cpp b/src/factories/sm64/audio/AudioManager.cpp new file mode 100644 index 0000000..c168a39 --- /dev/null +++ b/src/factories/sm64/audio/AudioManager.cpp @@ -0,0 +1,683 @@ +#include "AudioManager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "hj/zip.h" +#include "hj/pyutils.h" +#include "spdlog/spdlog.h" +#include "binarytools/BinaryReader.h" +#include "spdlog/spdlog.h" + +std::unordered_map name_table; +std::unordered_map sample_table; +AudioManager* AudioManager::Instance; + +std::vector PyUtils::range(uint32_t start, uint32_t end) { + std::vector 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 { + SPDLOG_DEBUG("Bank: {}", name); + SPDLOG_DEBUG("Instruments: {}", std::to_string(insts.size())); + SPDLOG_DEBUG("Drums: {}", std::to_string(drums.size())); + SPDLOG_DEBUG("Samples: {}", std::to_string(samples.size())); + SPDLOG_DEBUG("Envelopes: {}", std::to_string(envelopes.size())); + SPDLOG_DEBUG("All Instruments: {}", std::to_string(allInsts.size())); + SPDLOG_DEBUG("Inst Offsets: {}", std::to_string(instOffsets.size())); + SPDLOG_DEBUG("Sample Bank: {}", sampleBank->name); + SPDLOG_DEBUG("Sample Bank Offset: {}", std::to_string(sampleBank->offset)); +} + +std::vector AudioManager::parse_seq_file(std::vector& buffer, uint32_t offset, bool isCTL){ + std::vector entries; + LUS::BinaryReader reader((char*) buffer.data(), buffer.size()); + reader.SetEndianness(Torch::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& data){ + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(Torch::Endianness::Big); + + CTLHeader header = { reader.ReadUInt32(), reader.ReadUInt32(), reader.ReadUInt32() }; + + reader.Close(); + return header; +} + +Bank AudioManager::parse_ctl(CTLHeader header, std::vector 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 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 instrumentOffsets; + std::vector 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 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 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(); + auto sampleOffsets = std::vector(); + auto tunings = std::unordered_map(); + + 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>> allInsts; + bool needDrums = !drums.empty(); + for(auto &inst : insts){ + std::vector> sounds = {inst.soundLo, inst.soundMed, inst.soundHi}; + + if(needDrums && std::any_of(sounds.cbegin(), sounds.cend(), [&drums](std::optional 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 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> envData; + std::vector 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 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 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 AudioManager::parse_sound(std::vector data) { + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(Torch::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& data, uint32_t addr) { + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(Torch::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& 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& bankData){ + LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); + reader.SetEndianness(Torch::Endianness::Big); + reader.Seek(addr, LUS::SeekOffsetType::Start); + + std::optional> 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(); + 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& bankData){ + LUS::BinaryReader reader((char*) bankData.data(), bankData.size()); + reader.SetEndianness(Torch::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 table; + std::vector 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& data, std::vector& bankData, SampleBank* sampleBank){ + LUS::BinaryReader reader((char*) data.data(), data.size()); + reader.SetEndianness(Torch::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 AudioManager::parse_envelope(uint32_t addr, std::vector& dataBank){ + std::vector entries; + LUS::BinaryReader reader((char*) dataBank.data(), dataBank.size()); + reader.SetEndianness(Torch::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& data, std::vector& entries) { + TBLFile tbl; + std::unordered_map 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& buffer, YAML::Node& data) { + + auto ctlOffset = data["ctl"]["offset"].as(); + auto ctlSize = data["ctl"]["size"].as(); + + auto tblOffset = data["tbl"]["offset"].as(); + auto tblSize = data["tbl"]["size"].as(); + + std::vector tbl = parse_seq_file(buffer, tblOffset, false); + std::vector ctl = parse_seq_file(buffer, ctlOffset, true); + + SPDLOG_INFO("Raw TBL Entries: {}", tbl.size()); + SPDLOG_INFO("Raw CTL Entries: {}", ctl.size()); + + std::vector tbl_data = PyUtils::slice(buffer, tblOffset, tblOffset + tblSize); + std::vector ctl_data = PyUtils::slice(buffer, ctlOffset, ctlOffset + ctlSize); + this->loaded_tbl = parse_tbl(tbl_data, tbl); + + SPDLOG_INFO("Processed TBL Entries: {}", this->loaded_tbl.tbls.size()); + SPDLOG_INFO("Processed TBL Banks: {}", this->loaded_tbl.banks.size()); + + 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; + SPDLOG_INFO("Processed Bank {}", 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){ + 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((uint32_t) BSWAP32(section)); \ + LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ + tmp.SetEndianness(Torch::Endianness::Big); \ + +#define START_CUSTOM_SECTION(section) \ + { \ + LUS::BinaryWriter tmp = LUS::BinaryWriter(); \ + tmp.SetEndianness(Torch::Endianness::Big); \ + out.Write((uint32_t) 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((uint32_t) 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((uint32_t) 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((uint32_t) 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(reinterpret_cast(&one), 2); + tmp.Write(reinterpret_cast(&one), 2); + tmp.Write(entry->loop.start); + tmp.Write(entry->loop.end); + tmp.Write(entry->loop.count); + for(size_t i = 0; i < 16; i++){ + int16_t loop = BSWAP16(entry->loop.state.value()[i]); + tmp.Write(reinterpret_cast(&loop), 2); + } + END_SECTION(); + } + + len += 4; + out.Seek(4, LUS::SeekOffsetType::Start); + out.Write((uint32_t) BSWAP32(len)); + +} + +void AudioManager::bind_sample(YAML::Node& node, const std::string& path){ + auto id = GetSafeNode(node, "id"); + sample_table[id] = path; +} + +std::string& AudioManager::get_sample(uint32_t id) { + if(!sample_table.contains(id)) { + throw std::runtime_error("Failed to find sample with id " + std::to_string(id)); + } + return sample_table[id]; +} + +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 = 0; + 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]; + } + } + } + + SPDLOG_ERROR("Invalid Index {}", index); + 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 AudioManager::get_banks() { + return this->banks; +} \ No newline at end of file diff --git a/src/factories/sm64/audio/AudioManager.h b/src/factories/sm64/audio/AudioManager.h new file mode 100644 index 0000000..b5a9dcc --- /dev/null +++ b/src/factories/sm64/audio/AudioManager.h @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "binarytools/BinaryWriter.h" +#include +#include + +#define NONE 0xFFFF +#define ALIGN(val, al) (size_t) ((val + (al - 1)) & -al) + +namespace AIFC { + enum MagicValues { + FORM = 0x464f524d, + AIFC = 0x41494643, + COMM = 0x434f4d4d, + INST = 0x494e5354, + VAPC = 0x56415043, + SSND = 0x53534e44, + AAPL = 0x4150504c, + stoc = 0x73746f63, + }; +} + +struct Entry { + uint32_t offset; + uint32_t length; +}; + +struct AudioBankSound { + uint32_t offset; + float tuning; + bool operator==(const AudioBankSound& other) const { + return offset == other.offset && tuning == other.tuning; + } +}; + +struct Instrument { + bool valid; + std::string name; + uint32_t offset; + uint8_t releaseRate; + uint8_t normalRangeLo; + uint8_t normalRangeHi; + uint32_t envelope; + std::optional soundLo; + std::optional soundMed; + std::optional soundHi; +}; + +struct AdpcmBook { + int32_t order{}; + int32_t npredictors{}; + std::vector table; + bool operator==(const AdpcmBook& 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; + AudioBankSound 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 AdpcmLoop { + uint32_t start{}; + uint32_t end{}; + int32_t count{}; + uint32_t pad{}; + std::optional> state; + bool operator==(const AdpcmLoop& other) const { + return start == other.start && end == other.end && count == other.count && pad == other.pad && state == other.state; + } +}; + +struct AudioBankSample { + std::string name; + std::vector data; + AdpcmBook book; + AdpcmLoop loop; + std::vector tunings; +}; + +struct SampleBank { + std::string name; + uint32_t offset; + std::vector data; + std::map entries; + + AudioBankSample* AddSample(uint32_t addr, size_t sampleSize, const AdpcmBook& book, const AdpcmLoop& loop); +}; + +struct TBLFile { + std::vector banks; + std::vector tbls; + std::map map; +}; + +struct CTLHeader { + uint32_t instruments; + uint32_t numDrums; + uint32_t shared; +}; + +struct AdsrEnvelope { + int16_t delay; + int16_t arg; +}; + +struct Envelope { + std::string name; + std::vector entries; +}; + +struct Bank { + std::string name; + SampleBank* sampleBank; + std::vector insts; + std::vector drums; + std::vector>> allInsts; + std::vector instOffsets; + std::map envelopes; + std::map samples; + void print() const; +}; + +class AudioManager { +public: + static AudioManager* Instance; + void initialize(std::vector& buffer, YAML::Node& data); + void bind_sample(YAML::Node& node, const std::string& path); + void create_aifc(int32_t index, LUS::BinaryWriter& writer); + std::string& get_sample(uint32_t id); + AudioBankSample get_aifc(int32_t index); + std::map get_banks(); + uint32_t get_index(AudioBankSample* bank); + +private: + std::map banks; + std::map sampleMap; + TBLFile loaded_tbl; + + static std::vector parse_seq_file(std::vector& buffer, uint32_t offset, bool isCTL); + static CTLHeader parse_ctl_header(std::vector& data); + static std::optional parse_sound(std::vector data); + static Drum parse_drum(std::vector& data, uint32_t addr); + static Instrument parse_inst(std::vector& data, uint32_t addr); + static AdpcmLoop parse_loop(uint32_t addr, std::vector& bankData); + static AdpcmBook parse_book(uint32_t addr, std::vector& bankData); + static AudioBankSample* parse_sample(std::vector& data, std::vector& bankData, SampleBank* sampleBank); + static std::vector parse_envelope(uint32_t addr, std::vector& dataBank); + static Bank parse_ctl(CTLHeader header, std::vector data, SampleBank* bank, uint32_t index); + static TBLFile parse_tbl(std::vector& data, std::vector& entries); + + static void write_aifc(AudioBankSample* entry, LUS::BinaryWriter& writer); +}; \ No newline at end of file diff --git a/src/factories/sm64/audio/BankFactory.cpp b/src/factories/sm64/audio/BankFactory.cpp new file mode 100644 index 0000000..6e72c53 --- /dev/null +++ b/src/factories/sm64/audio/BankFactory.cpp @@ -0,0 +1,99 @@ +#include "BankFactory.h" +#include "Companion.h" + +ExportResult BankBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto writer = LUS::BinaryWriter(); + auto bank = std::static_pointer_cast(raw); + + WriteHeader(writer, Torch::ResourceType::Bank, 0); + writer.Write(bank->mBankId); + writer.Write(static_cast(bank->mBank.insts.size())); + for (auto& instrument : bank->mBank.insts) { + writer.Write(static_cast(instrument.valid)); + if(!instrument.valid) continue; + + writer.Write(instrument.releaseRate); + writer.Write(instrument.normalRangeLo); + writer.Write(instrument.normalRangeHi); + + auto [name, entries] = bank->mBank.envelopes[instrument.envelope]; + if(!entries.empty()){ + writer.Write(static_cast(entries.size())); + for (auto& [delay, arg] : entries) { + writer.Write(static_cast(delay)); + writer.Write(static_cast(arg)); + } + } else { + writer.Write(static_cast(0)); + } + + uint32_t soundFlags = 0; + const bool hasLo = instrument.soundLo.has_value(); + const bool hasMed = instrument.soundMed.has_value(); + const bool hasHi = instrument.soundHi.has_value(); + if(hasLo){ + soundFlags |= 1 << 0; + } + if(hasMed){ + soundFlags |= 1 << 1; + } + if(hasHi){ + soundFlags |= 1 << 2; + } + + writer.Write(soundFlags); + if(hasLo){ + auto [offset, tuning] = instrument.soundLo.value(); + const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); + writer.Write(AudioManager::Instance->get_sample(idx)); + writer.Write(tuning); + } + if(hasMed){ + auto [offset, tuning] = instrument.soundMed.value(); + const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); + writer.Write(AudioManager::Instance->get_sample(idx)); + writer.Write(tuning); + } + if(hasHi){ + auto [offset, tuning] = instrument.soundHi.value(); + const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[offset]); + writer.Write(AudioManager::Instance->get_sample(idx)); + writer.Write(tuning); + } + } + + writer.Write(static_cast(bank->mBank.drums.size())); + for(auto &drum : bank->mBank.drums){ + writer.Write(drum.releaseRate); + writer.Write(drum.pan); + auto envelope = bank->mBank.envelopes[drum.envelope]; + if(!envelope.entries.empty()){ + writer.Write(static_cast(envelope.entries.size())); + for(auto &entry : envelope.entries){ + writer.Write(entry.delay); + writer.Write(entry.arg); + } + } else { + writer.Write(static_cast(0)); + } + + const uint32_t idx = AudioManager::Instance->get_index(bank->mBank.samples[drum.sound.offset]); + const auto out = AudioManager::Instance->get_sample(idx); + writer.Write(out); + writer.Write(drum.sound.tuning); + } + + writer.Finish(write); + return std::nullopt; +} + +std::optional> BankFactory::parse(std::vector& buffer, YAML::Node& data) { + auto banks = AudioManager::Instance->get_banks(); + auto bankId = data["id"].as(); + + if(AudioManager::Instance == nullptr){ + throw std::runtime_error("AudioManager not initialized"); + } + + return std::make_shared(banks[bankId], bankId); +} \ No newline at end of file diff --git a/src/factories/sm64/audio/BankFactory.h b/src/factories/sm64/audio/BankFactory.h new file mode 100644 index 0000000..9f4b260 --- /dev/null +++ b/src/factories/sm64/audio/BankFactory.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include "AudioManager.h" + +class BankData : public IParsedData { +public: + Bank mBank; + uint32_t mBankId; + + BankData(Bank bank, const uint32_t bankId) : mBank(std::move(bank)), mBankId(bankId) {} +}; + +class BankBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class BankFactory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + inline std::unordered_map> GetExporters() override { + return { + REGISTER(Binary, BankBinaryExporter) + }; + } +}; \ No newline at end of file diff --git a/src/factories/sm64/audio/SampleFactory.cpp b/src/factories/sm64/audio/SampleFactory.cpp new file mode 100644 index 0000000..b8bc7fa --- /dev/null +++ b/src/factories/sm64/audio/SampleFactory.cpp @@ -0,0 +1,43 @@ +#include "SampleFactory.h" + +ExportResult SampleBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto writer = LUS::BinaryWriter(); + auto sample = std::static_pointer_cast(raw)->mSample; + + WriteHeader(writer, Torch::ResourceType::Sample, 0); + writer.Write(sample.loop.start); + writer.Write(sample.loop.end); + writer.Write(sample.loop.count); + writer.Write(sample.loop.pad); + + if(sample.loop.state.has_value()){ + auto state = sample.loop.state.value(); + writer.Write(static_cast(state.size())); + writer.Write(reinterpret_cast(state.data()), state.size() * sizeof(int16_t)); + } else { + writer.Write(static_cast(0)); + } + + writer.Write(sample.book.order); + writer.Write(sample.book.npredictors); + + writer.Write(static_cast(sample.book.table.size())); + writer.Write(reinterpret_cast(sample.book.table.data()), sample.book.table.size() * sizeof(int16_t)); + + writer.Write(static_cast(sample.data.size())); + writer.Write(reinterpret_cast(sample.data.data()), sample.data.size()); + + writer.Write(sample.name); + + writer.Finish(write); + return std::nullopt; +} + +std::optional> SampleFactory::parse(std::vector& buffer, YAML::Node& data) { + const auto id = data["id"].as(); + if(AudioManager::Instance == nullptr){ + throw std::runtime_error("AudioManager not initialized"); + } + AudioBankSample entry = AudioManager::Instance->get_aifc(id); + return std::make_shared(entry); +} \ No newline at end of file diff --git a/src/factories/sm64/audio/SampleFactory.h b/src/factories/sm64/audio/SampleFactory.h new file mode 100644 index 0000000..ae940e2 --- /dev/null +++ b/src/factories/sm64/audio/SampleFactory.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include "AudioManager.h" + +class SampleData : public IParsedData { +public: + AudioBankSample mSample; + + explicit SampleData(AudioBankSample sample) : mSample(std::move(sample)) {} +}; + +class SampleBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SampleFactory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + std::unordered_map> GetExporters() override { + return { + REGISTER(Binary, SampleBinaryExporter) + }; + } +}; \ No newline at end of file diff --git a/src/factories/sm64/audio/SequenceFactory.cpp b/src/factories/sm64/audio/SequenceFactory.cpp new file mode 100644 index 0000000..1090bac --- /dev/null +++ b/src/factories/sm64/audio/SequenceFactory.cpp @@ -0,0 +1,25 @@ +#include "SequenceFactory.h" + +ExportResult SequenceBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { + auto writer = LUS::BinaryWriter(); + const auto sequence = std::static_pointer_cast(raw); + + WriteHeader(writer, Torch::ResourceType::Sequence, 0); + writer.Write(sequence->mId); + writer.Write(static_cast(sequence->mBanks.size())); + for (auto& bank : sequence->mBanks) { + writer.Write(bank); + } + writer.Write( sequence->mSize); + writer.Write(reinterpret_cast(sequence->mBuffer.data()), sequence->mSize); + writer.Finish(write); + return std::nullopt; +} + +std::optional> SequenceFactory::parse(std::vector& buffer, YAML::Node& data) { + auto id = data["id"].as(); + auto size = data["size"].as(); + const auto offset = data["offset"].as(); + auto banks = data["banks"].as>(); + return std::make_shared(id, size, buffer.data() + offset, banks); +} \ No newline at end of file diff --git a/src/factories/sm64/audio/SequenceFactory.h b/src/factories/sm64/audio/SequenceFactory.h new file mode 100644 index 0000000..b26242c --- /dev/null +++ b/src/factories/sm64/audio/SequenceFactory.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class SequenceData : public IParsedData { +public: + uint32_t mId; + uint32_t mSize; + std::vector mBuffer; + std::vector mBanks; + + SequenceData(uint32_t id, uint32_t size, uint8_t* data, std::vector& banks) : mId(id), mSize(size), mBanks(banks) { + mBuffer = std::vector(data, data + size); + } +}; + +class SequenceBinaryExporter : public BaseExporter { + ExportResult Export(std::ostream& write, std::shared_ptr data, std::string& entryName, YAML::Node& node, std::string* replacement) override; +}; + +class SequenceFactory : public BaseFactory { +public: + std::optional> parse(std::vector& buffer, YAML::Node& data) override; + inline std::unordered_map> GetExporters() override { + return { + REGISTER(Binary, SequenceBinaryExporter) + }; + } +}; \ No newline at end of file -- cgit v1.2.3