Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make audio respect the asset stack #1107

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 168 additions & 10 deletions tools/build/audio/sbn.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,183 @@
#! /usr/bin/python3

from sys import argv, path
import argparse
from sys 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
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()

for input in inputs:
# if input is a sbn.yaml, read its directory
if input.suffix == ".yaml":
sbn.read(input.parent)
with yaml_path.open("r") as f:
config = yaml.safe_load(f)

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()

with open(out, "wb") as f:
with open(out_path, "wb") as f:
f.write(data)
12 changes: 10 additions & 2 deletions tools/build/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("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:
f.write(f"compiler_command = \"{cc} {CPPFLAGS.replace('$version', 'pal')} {cflags} -DPERMUTER -fforce-addr\"\n")
Expand Down Expand Up @@ -1060,7 +1060,15 @@ def build(
build(entry.object_path, [entry.object_path.with_suffix("")], "bin")
elif seg.type == "pm_sbn":
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")
elif seg.type == "linker" or seg.type == "linker_offset":
pass
Expand Down
147 changes: 1 addition & 146 deletions tools/splat_ext/pm_sbn.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,139 +274,6 @@ def write(self, path: Path):
f.write(f" bank_group: 0x{entry.bankGroup:02X}\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:
for id, sbn_file in enumerate(self.files):
if sbn_file.file_name() == filename:
Expand Down Expand Up @@ -689,13 +556,10 @@ def get_linker_entries(self):
dir = options.opts.asset_path / self.dir / self.name
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 [
LinkerEntry(
self,
inputs,
[dir],
out,
".data",
),
Expand Down Expand Up @@ -849,12 +713,3 @@ def get_song_symbol_name(song_id: int) -> str:
0x00000096: "SONG_NEW_PARTNER_JP",
}
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)
Loading