// Copyright: 2021 - 2024, Ziemas // SPDX-License-Identifier: ISC #include "loader.h" #include #include #include "sfxblock.h" #include "common/log/log.h" #include "common/util/BinaryReader.h" #include "game/sound/989snd/musicbank.h" #include "fmt/format.h" namespace snd { enum chunk : u32 { bank, samples, midi }; inline static constexpr u32 fourcc(std::string_view p) { return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; } void FileAttributes::Read(BinaryReader& data) { type = data.read(); num_chunks = data.read(); where.resize(num_chunks); for (size_t i = 0; i < num_chunks; i++) { where[i].offset = data.read(); where[i].size = data.read(); } } static Tone ReadTone(BinaryReader& data, u8* samples) { Tone tone{}; tone.Priority = data.read(); tone.Vol = data.read(); tone.CenterNote = data.read(); tone.CenterFine = data.read(); tone.Pan = data.read(); tone.MapLow = data.read(); tone.MapHigh = data.read(); tone.PBLow = data.read(); tone.PBHigh = data.read(); tone.ADSR1 = data.read(); tone.ADSR2 = data.read(); tone.Flags = data.read(); u32 SampleOffset = data.read(); tone.Sample = &samples[SampleOffset]; data.read(); // reserved1 return tone; }; static Midi ReadMidi(BinaryReader& data) { Midi mid; u8* base = const_cast(data.here()); mid.DataID = data.read(); mid.Version = data.read(); mid.Flags = data.read(); data.read(); mid.ID = data.read(); data.read(); mid.BankID = data.read(); data.read(); u32 DataStart = data.read(); mid.DataStart = base + DataStart; data.read(); mid.Tempo = data.read(); mid.PPQ = data.read(); return mid; } static MultiMidi ReadMultiMidi(BinaryReader& data) { MultiMidi mmid; mmid.DataID = data.read(); mmid.Version = data.read(); mmid.Flags = data.read(); s8 NumSegments = data.read(); mmid.midi.resize(NumSegments); mmid.ID = data.read(); data.read(); for (auto& midi : mmid.midi) { u32 offset = data.read(); auto mr = data.at(offset); midi.Midi::operator=(ReadMidi(mr)); midi.SoundHandle = mr.read(); } return mmid; } static Grain ReadGrainV1(BinaryReader& data, u8* samples) { Grain grain{}; u32 pos = data.get_seek(); grain.Type = static_cast(data.read()); grain.Delay = data.read(); switch (grain.Type) { case GrainType::TONE: case GrainType::TONE2: { grain.data = ReadTone(data, samples); } break; case GrainType::LFO_SETTINGS: { grain.data = data.read(); } break; case GrainType::BRANCH: case GrainType::STARTCHILDSOUND: case GrainType::STOPCHILDSOUND: grain.data = data.read(); break; case GrainType::PLUGIN_MESSAGE: grain.data = data.read(); break; case GrainType::RAND_DELAY: grain.data = data.read(); break; default: grain.data = data.read(); break; } data.set_seek(pos); data.ffwd(0x28); return grain; }; static Grain ReadGrainV2(BinaryReader& data, BinaryReader grainData, u8* samples) { union OpcodeData { struct { s8 arg[3]; u8 type; }; u32 Opcode; } op; Grain grain{}; op = data.read(); grain.Type = static_cast(op.type); grain.Delay = data.read(); u32 value = op.Opcode & 0xFFFFFF; switch (grain.Type) { case GrainType::TONE: case GrainType::TONE2: { grainData.set_seek(value); grain.data = ReadTone(grainData, samples); } break; case GrainType::LFO_SETTINGS: { grainData.set_seek(value); grain.data = grainData.read(); } break; case GrainType::BRANCH: case GrainType::STARTCHILDSOUND: case GrainType::STOPCHILDSOUND: grainData.set_seek(value); grain.data = grainData.read(); break; case GrainType::PLUGIN_MESSAGE: grainData.set_seek(value); grain.data = grainData.read(); break; case GrainType::RAND_DELAY: RandDelayParams rp; rp.Amount = (s32)value + 1; grain.data = rp; break; default: ControlParams p; p.param[0] = op.arg[0]; p.param[1] = op.arg[1]; p.param[2] = op.arg[2]; grain.data = p; break; } return grain; }; SFXBlock* SFXBlock::ReadBlock(std::span bank_data, std::span samples) { BinaryReader data(bank_data); // auto block = std::make_unique(); auto block = new SFXBlock(); block->DataID = data.read(); if (block->DataID != fourcc("SBlk")) { return nullptr; } block->SampleData = std::make_unique(samples.size()); std::copy(samples.begin(), samples.end(), block->SampleData.get()); block->Version = data.read(); block->Flags.flags = data.read(); block->BankID = data.read(); block->BankNum = data.read(); // discard padding data.read(); data.read(); data.read(); s16 NumSounds = data.read(); block->Sounds.resize(NumSounds); [[maybe_unused]] s16 NumGrains = data.read(); [[maybe_unused]] s16 NumVAGs = data.read(); u32 FirstSound = data.read(); u32 FirstGrain = data.read(); [[maybe_unused]] u32 VagsInSR = data.read(); [[maybe_unused]] u32 VagDataSize = data.read(); [[maybe_unused]] u32 SRAMAllocSize = data.read(); [[maybe_unused]] u32 NextBlock = data.read(); u32 GrainData = 0; if (block->Version >= 2) { GrainData = data.read(); } u32 BlockNames = data.read(); u32 SFXUD = data.read(); data.set_seek(FirstSound); for (auto& sfx : block->Sounds) { sfx.Vol = data.read(); sfx.VolGroup = data.read(); sfx.Pan = data.read(); s8 NumGrains = data.read(); sfx.Grains.resize(NumGrains); sfx.InstanceLimit = data.read(); sfx.Flags.flags = data.read(); u32 FirstSFXGrain = data.read(); if (NumGrains) { auto grains = data.at(FirstGrain + FirstSFXGrain); for (auto& grain : sfx.Grains) { if (block->Version < 2) { grain = ReadGrainV1(grains, block->SampleData.get()); } else { grain = ReadGrainV2(grains, data.at(GrainData), block->SampleData.get()); } } } } if (block->Flags.hasNames()) { struct SFXBlockNames { // 0x98 /* 0x00 */ u32 BlockName[2]; /* 0x08 */ u32 SFXNameTableOffset; /* 0x0c */ u32 VAGNameTableOffset; /* 0x10 */ u32 VAGImportsTableOffset; /* 0x14 */ u32 VAGExportsTableOffset; /* 0x18 */ s16 SFXHashOffsets[32]; /* 0x58 */ s16 VAGHashOffsets[32]; }; struct SFXName { // 0x14 /* 0x00 */ u32 Name[4]; /* 0x10 */ s16 Index; /* 0x12 */ s16 reserved; }; data.set_seek(BlockNames); auto names = data.read(); char buf[17]; buf[16] = 0; strncpy(buf, (char*)names.BlockName, 8); block->Name = buf; data.set_seek(BlockNames + names.SFXNameTableOffset); auto name_table = (SFXName*)(data.here()); for (auto SFXHashOffset : names.SFXHashOffsets) { auto name = &name_table[SFXHashOffset]; while (name->Name[0] != 0) { strncpy(buf, (char*)name->Name, 16); std::string str(buf); if (block->Names.find(str) == block->Names.end()) { block->Names[str] = name->Index; } name++; } } } if (block->Flags.hasUserdata()) { data.set_seek(SFXUD); for (auto& sfx : block->Sounds) { sfx.UserData.data[0] = data.read(); sfx.UserData.data[1] = data.read(); sfx.UserData.data[2] = data.read(); sfx.UserData.data[3] = data.read(); } } return block; } MusicBank* MusicBank::ReadBank(std::span bank_data, std::span samples, std::span midi_data) { BinaryReader data(bank_data); // auto bank = std::make_unique(); auto bank = new MusicBank(); bank->DataID = data.read(); if (bank->DataID != fourcc("SBv2")) { return nullptr; } bank->SampleData = std::make_unique(samples.size_bytes()); std::copy(samples.begin(), samples.end(), bank->SampleData.get()); bank->SeqData = std::make_unique(midi_data.size_bytes()); std::copy(midi_data.begin(), midi_data.end(), bank->SeqData.get()); bank->Version = data.read(); bank->Flags.flags = data.read(); bank->BankID = data.read(); bank->BankNum = data.read(); // discard padding data.read(); data.read(); s16 NumSounds = data.read(); bank->Sounds.resize(NumSounds); s16 NumProgs = data.read(); bank->Progs.resize(NumProgs); [[maybe_unused]] s16 NumTones = data.read(); [[maybe_unused]] s16 NumVAGs = data.read(); u32 FirstSound = data.read(); u32 FirstProg = data.read(); [[maybe_unused]] u32 FirstTone = data.read(); // vagsinsr data.read(); // vagdatasize data.read(); data.set_seek(FirstSound); for (auto& sound : bank->Sounds) { sound.Type = data.read(); data.read(); // bankptr data.read(); // ogbankptr sound.MIDIID = data.read(); sound.Vol = data.read(); sound.Repeats = data.read(); sound.VolGroup = data.read(); sound.Pan = data.read(); sound.Index = data.read(); sound.Flags = data.read(); data.read(); // midiblockptr sound.Bank = bank; sound.OrigBank = bank; } data.set_seek(FirstProg); for (auto& prog : bank->Progs) { s8 NumTones = data.read(); prog.Vol = data.read(); prog.Pan = data.read(); u32 FirstTone = data.read(); prog.Tones.resize(NumTones); auto tones = data.at(FirstTone); for (auto& tone : prog.Tones) { tone = ReadTone(tones, bank->SampleData.get()); } } auto seq_buf = std::span(bank->SeqData.get(), midi_data.size_bytes()); BinaryReader seq_data(seq_buf); FileAttributes fa; fa.Read(seq_data); BinaryReader midi(seq_buf.subspan(fa.where[0].offset)); u32 id = midi.read(); midi.set_seek(0); if (id == fourcc("MID ")) { bank->MidiData = ReadMidi(midi); } else if (id == fourcc("MMID")) { bank->MidiData = ReadMultiMidi(midi); } else { lg::error("Invalid midi\n"); } return bank; } BankHandle Loader::BankLoad(std::span bank) { BinaryReader reader(bank); FileAttributes fa; fa.Read(reader); if (fa.type != 1 && fa.type != 3) { fmt::print("bad file type\n"); return nullptr; } reader.set_seek(fa.where[0].offset); u32 fourcc = reader.read(); std::span bank_data(std::span(bank).subspan(fa.where[0].offset, fa.where[0].size)); std::span sample_data(std::span(bank).subspan(fa.where[1].offset, fa.where[1].size)); if (fourcc == snd::fourcc("SBv2")) { if (fa.num_chunks != 3) { fmt::print("SBv2 without midi data not supported\n"); return 0; } std::span midi_data(std::span(bank).subspan(fa.where[2].offset, fa.where[2].size)); auto bank = MusicBank::ReadBank(bank_data, sample_data, midi_data); mBanks.emplace_back(bank); return bank; } else if (fourcc == snd::fourcc("SBlk")) { auto block = SFXBlock::ReadBlock(bank_data, sample_data); mBanks.emplace_back(block); return block; } return nullptr; } SoundBank* Loader::GetBankByHandle(BankHandle handle) { auto bank = std::find_if(mBanks.begin(), mBanks.end(), [handle](auto& bank) { return bank.get() == handle; }); if (bank == mBanks.end()) { return nullptr; } return bank->get(); } SoundBank* Loader::GetBankByName(const char* name) { for (auto& b : mBanks) { auto bankname = b->GetName(); if (bankname.has_value()) { if (bankname->compare(name) == 0) { return b.get(); } } } return nullptr; } SoundBank* Loader::GetBankWithSound(const char* name) { for (auto& b : mBanks) { auto sound = b->GetSoundByName(name); if (sound.has_value()) { return b.get(); } } return nullptr; } void Loader::UnloadBank(BankHandle handle) { auto bank = std::find_if(mBanks.begin(), mBanks.end(), [handle](auto& bank) { return bank.get() == handle; }); if (bank != mBanks.end()) { mBanks.erase(bank); } } } // namespace snd