Make audio respect the asset stack (#1107)

This commit is contained in:
Ethan Roseman 2023-08-01 00:18:27 +09:00 committed by GitHub
parent 6e80d188e7
commit a3f35fe27a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 179 additions and 158 deletions

View File

@ -1,25 +1,183 @@
#! /usr/bin/python3 #! /usr/bin/python3
from sys import argv, path import argparse
from sys import path
from pathlib import Path from pathlib import Path
import sys
from typing import Tuple
import yaml
sys.path.append(str(Path(__file__).parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent / "splat"))
from common import get_asset_path
# allow importing splat_ext # allow importing splat_ext
path.append(str(Path(__file__).parent.parent.parent)) path.append(str(Path(__file__).parent.parent.parent))
from splat_ext.pm_sbn import SBN from splat_ext.pm_sbn import SBN, BufferEntry, InitSongEntry, SBNFile
if __name__ == "__main__":
out = argv[1]
inputs = [Path(p) for p in argv[2:]]
# Sorts a list of dicts by their "id" key. If "id" is "auto" or missing, it is sorted to the end.
def sort_by_id_or_auto(list):
def get_id(item):
id = item.get("id")
return 0xFFFF if id == "auto" else id
list.sort(key=get_id)
def from_yaml(yaml_path: Path, asset_stack: Tuple[Path, ...]) -> SBN:
sbn = SBN() sbn = SBN()
for input in inputs: with yaml_path.open("r") as f:
# if input is a sbn.yaml, read its directory config = yaml.safe_load(f)
if input.suffix == ".yaml":
sbn.read(input.parent) unknown_bin_path = get_asset_path("audio/unknown.bin", asset_stack)
with open(unknown_bin_path, "rb") as f:
sbn.unknown_data = f.read()
files = config.get("files", {})
songs = config.get("songs", [])
mseqs = config.get("mseqs", [])
banks = config.get("banks", [])
sort_by_id_or_auto(files)
sort_by_id_or_auto(songs)
sort_by_id_or_auto(mseqs)
sort_by_id_or_auto(banks)
# Read files
for file_dict in files:
id = file_dict.get("id", "auto")
if id == "auto":
id = len(sbn.files)
assert type(id) == int
filename = file_dict.get("file")
assert type(filename) == str
fakesize = file_dict.get("fakesize")
assert type(fakesize) == int or fakesize is None
sbn_file = SBNFile()
sbn_file_path = get_asset_path(f"audio/{filename}", asset_stack)
sbn_file.read(sbn_file_path)
if fakesize is not None:
sbn_file.fakesize = fakesize
else:
sbn_file.fakesize = sbn_file.size
# Replace sbn.files[id]
if id < len(sbn.files):
print("Overwriting file ID {:02X}", id)
sbn.files[id] = sbn_file
elif id == len(sbn.files):
sbn.files.append(sbn_file)
else:
raise Exception(f"Invalid file ID: 0x{id:02X} - cannot have gaps")
# Read INIT songs
for song in songs:
id = song.get("id", "auto")
if id == "auto":
id = len(sbn.init.song_entries)
assert type(id) == int
# Lookup file ID
file = song.get("file")
assert type(file) == str
file_id = sbn.lookup_file_id(file)
# Lookup BK file IDs
bk_files = song.get("bk_files", [])
assert type(bk_files) == list
bk_file_ids = []
for bk_file in bk_files:
if bk_file is None:
bk_file_ids.append(0)
else:
assert type(bk_file) == str
bk_file_ids.append(sbn.lookup_file_id(bk_file))
init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2])
# Replace sbn.init.song_entries[id]
if id < len(sbn.init.song_entries):
print(f"Overwriting song ID {id:02X}")
sbn.init.song_entries[id] = init_song_entry
elif id == len(sbn.init.song_entries):
sbn.init.song_entries.append(init_song_entry)
else:
raise Exception(f"Invalid song ID: 0x{id:02X} - cannot have gaps")
# Read INIT mseqs
for mseq in mseqs:
id = mseq.get("id", "auto")
if id == "auto":
id = len(sbn.init.mseq_entries)
assert type(id) == int
# Lookup file ID
file = mseq.get("file")
assert type(file) == str
file_id = sbn.lookup_file_id(file)
# Replace sbn.init.mseq_entries[id]
if id < len(sbn.init.mseq_entries):
print(f"Overwriting MSEQ ID {id:02X}")
sbn.init.mseq_entries[id] = file_id
elif id == len(sbn.init.mseq_entries):
sbn.init.mseq_entries.append(file_id)
else:
raise Exception(f"Invalid MSEQ ID: 0x{id:02X} - cannot have gaps")
# Read INIT banks
for bank in banks:
id = bank.get("id", "auto")
if id == "auto":
id = len(sbn.init.bk_entries)
assert type(id) == int
file = bank.get("file")
assert type(file) == str
file_id = sbn.lookup_file_id(file)
bank_index = bank.get("bank_index")
assert type(bank_index) == int
bank_group = bank.get("bank_group")
assert type(bank_group) == int
entry = BufferEntry(file_id, bank_index, bank_group)
# Replace sbn.init.bk_entries[id]
if id < len(sbn.init.bk_entries):
print(f"Overwriting bank ID {id:02X}")
sbn.init.bk_entries[id] = entry
elif id == len(sbn.init.bk_entries):
sbn.init.bk_entries.append(entry)
else:
raise Exception(f"Invalid bank ID: {id:02X} - cannot have gaps")
return sbn
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Builds SBN")
parser.add_argument("out")
parser.add_argument("asset_stack")
args = parser.parse_args()
out_path = Path(args.out)
asset_stack = tuple(Path(d) for d in args.asset_stack.split(","))
yaml_path = get_asset_path(Path("audio/sbn.yaml"), asset_stack)
sbn = from_yaml(yaml_path, asset_stack)
data = sbn.encode() data = sbn.encode()
with open(out, "wb") as f: with open(out_path, "wb") as f:
f.write(data) f.write(data)

View File

@ -301,7 +301,7 @@ def write_ninja_rules(
ninja.rule("effect_data", command=f"$python {BUILD_TOOLS}/effects.py $in_yaml $out_dir") ninja.rule("effect_data", command=f"$python {BUILD_TOOLS}/effects.py $in_yaml $out_dir")
ninja.rule("pm_sbn", command=f"$python {BUILD_TOOLS}/audio/sbn.py $out $in") ninja.rule("pm_sbn", command=f"$python {BUILD_TOOLS}/audio/sbn.py $out $asset_stack")
with Path("tools/permuter_settings.toml").open("w") as f: with Path("tools/permuter_settings.toml").open("w") as f:
f.write(f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n") f.write(f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n")
@ -1060,7 +1060,15 @@ class Configure:
build(entry.object_path, [entry.object_path.with_suffix("")], "bin") build(entry.object_path, [entry.object_path.with_suffix("")], "bin")
elif seg.type == "pm_sbn": elif seg.type == "pm_sbn":
sbn_path = entry.object_path.with_suffix("") sbn_path = entry.object_path.with_suffix("")
build(sbn_path, entry.src_paths, "pm_sbn") # could have non-yaml inputs be implicit build(
sbn_path,
entry.src_paths,
"pm_sbn",
variables={
"asset_stack": ",".join(self.asset_stack),
},
asset_deps=entry.src_paths,
)
build(entry.object_path, [sbn_path], "bin") build(entry.object_path, [sbn_path], "bin")
elif seg.type == "linker" or seg.type == "linker_offset": elif seg.type == "linker" or seg.type == "linker_offset":
pass pass

View File

@ -274,139 +274,6 @@ class SBN:
f.write(f" bank_group: 0x{entry.bankGroup:02X}\n") f.write(f" bank_group: 0x{entry.bankGroup:02X}\n")
f.write("\n") f.write("\n")
def read(self, dir: Path) -> List[Path]:
config_file_path = dir / "sbn.yaml"
with config_file_path.open("r") as f:
config = yaml.safe_load(f)
with open(dir / "unknown.bin", "rb") as f:
self.unknown_data = f.read()
files = config.get("files", {})
songs = config.get("songs", [])
mseqs = config.get("mseqs", [])
banks = config.get("banks", [])
sort_by_id_or_auto(files)
sort_by_id_or_auto(songs)
sort_by_id_or_auto(mseqs)
sort_by_id_or_auto(banks)
# Read files
for file_dict in files:
id = file_dict.get("id", "auto")
if id == "auto":
id = len(self.files)
assert type(id) == int
filename = file_dict.get("file")
assert type(filename) == str
fakesize = file_dict.get("fakesize")
assert type(fakesize) == int or fakesize is None
sbn_file = SBNFile()
sbn_file.read(dir / filename)
if fakesize is not None:
sbn_file.fakesize = fakesize
else:
sbn_file.fakesize = sbn_file.size
# Replace self.files[id]
if id < len(self.files):
print("Overwriting file ID {:02X}", id)
self.files[id] = sbn_file
elif id == len(self.files):
self.files.append(sbn_file)
else:
raise Exception(f"Invalid file ID: 0x{id:02X} - cannot have gaps")
# Read INIT songs
for song in songs:
id = song.get("id", "auto")
if id == "auto":
id = len(self.init.song_entries)
assert type(id) == int
# Lookup file ID
file = song.get("file")
assert type(file) == str
file_id = self.lookup_file_id(file)
# Lookup BK file IDs
bk_files = song.get("bk_files", [])
assert type(bk_files) == list
bk_file_ids = []
for bk_file in bk_files:
if bk_file is None:
bk_file_ids.append(0)
else:
assert type(bk_file) == str
bk_file_ids.append(self.lookup_file_id(bk_file))
init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2])
# Replace self.init.song_entries[id]
if id < len(self.init.song_entries):
print(f"Overwriting song ID {id:02X}")
self.init.song_entries[id] = init_song_entry
elif id == len(self.init.song_entries):
self.init.song_entries.append(init_song_entry)
else:
raise Exception(f"Invalid song ID: 0x{id:02X} - cannot have gaps")
# Read INIT mseqs
for mseq in mseqs:
id = mseq.get("id", "auto")
if id == "auto":
id = len(self.init.mseq_entries)
assert type(id) == int
# Lookup file ID
file = mseq.get("file")
assert type(file) == str
file_id = self.lookup_file_id(file)
# Replace self.init.mseq_entries[id]
if id < len(self.init.mseq_entries):
print(f"Overwriting MSEQ ID {id:02X}")
self.init.mseq_entries[id] = file_id
elif id == len(self.init.mseq_entries):
self.init.mseq_entries.append(file_id)
else:
raise Exception(f"Invalid MSEQ ID: 0x{id:02X} - cannot have gaps")
# Read INIT banks
for bank in banks:
id = bank.get("id", "auto")
if id == "auto":
id = len(self.init.bk_entries)
assert type(id) == int
file = bank.get("file")
assert type(file) == str
file_id = self.lookup_file_id(file)
bank_index = bank.get("bank_index")
assert type(bank_index) == int
bank_group = bank.get("bank_group")
assert type(bank_group) == int
entry = BufferEntry(file_id, bank_index, bank_group)
# Replace self.init.bk_entries[id]
if id < len(self.init.bk_entries):
print(f"Overwriting bank ID {id:02X}")
self.init.bk_entries[id] = entry
elif id == len(self.init.bk_entries):
self.init.bk_entries.append(entry)
else:
raise Exception(f"Invalid bank ID: {id:02X} - cannot have gaps")
return [config_file_path]
def lookup_file_id(self, filename: str) -> int: def lookup_file_id(self, filename: str) -> int:
for id, sbn_file in enumerate(self.files): for id, sbn_file in enumerate(self.files):
if sbn_file.file_name() == filename: if sbn_file.file_name() == filename:
@ -689,13 +556,10 @@ if splat_loaded:
dir = options.opts.asset_path / self.dir / self.name dir = options.opts.asset_path / self.dir / self.name
out = options.opts.asset_path / self.dir / (self.name + ".sbn") out = options.opts.asset_path / self.dir / (self.name + ".sbn")
sbn = SBN()
config_files = sbn.read(dir) # TODO: LayeredFS/AssetsFS read, supporting merges
inputs = config_files + [dir / f.file_name() for f in sbn.files]
return [ return [
LinkerEntry( LinkerEntry(
self, self,
inputs, [dir],
out, out,
".data", ".data",
), ),
@ -849,12 +713,3 @@ def get_song_symbol_name(song_id: int) -> str:
0x00000096: "SONG_NEW_PARTNER_JP", 0x00000096: "SONG_NEW_PARTNER_JP",
} }
return song_names.get(song_id, f"null") return song_names.get(song_id, f"null")
# Sorts a list of dicts by their "id" key. If "id" is "auto" or missing, it is sorted to the end.
def sort_by_id_or_auto(list):
def get_id(item):
id = item.get("id")
return 0xFFFF if id == "auto" else id
list.sort(key=get_id)