K1Guitar/tools/gen_local_song_names.py

100 lines
3.1 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Generate project/inc/LocalSongNames.h from Doc/音色文件/0903/local_songs.csv.
CSV columns: index,code,name
index preset index in 2.bin (0-based)
code ASCII code in bin preset name (e.g. HKTK from 1.HKTK)
name display name (Unicode); written as GBK byte escapes for the LCD font
Run standalone or from pack_extflash_tone_0903.py after packing 2.bin.
"""
from __future__ import annotations
import csv
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REPO = ROOT.parent.parent
CSV_PATH = REPO / "Doc" / "音色文件" / "0903" / "local_songs.csv"
OUT_HDR = ROOT / "project" / "inc" / "LocalSongNames.h"
def gbk_c_string(text: str) -> str:
raw = text.encode("gbk")
return "".join(f"\\x{b:02X}" for b in raw)
def load_rows(path: Path) -> list[dict]:
if not path.is_file():
raise FileNotFoundError(f"missing {path}")
with path.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
if not rows:
raise SystemExit(f"{path}: no song rows")
out = []
for i, row in enumerate(rows):
try:
idx = int((row.get("index") or "").strip())
except ValueError as e:
raise SystemExit(f"{path}: bad index on row {i + 2}: {row!r}") from e
code = (row.get("code") or "").strip()
name = (row.get("name") or "").strip()
if not code or not name:
raise SystemExit(f"{path}: empty code/name on row {i + 2}")
out.append({"index": idx, "code": code, "name": name})
out.sort(key=lambda r: r["index"])
for expect, row in enumerate(out):
if row["index"] != expect:
raise SystemExit(
f"{path}: indices must be contiguous from 0; "
f"expected {expect}, got {row['index']}"
)
return out
def write_header(rows: list[dict], path: Path) -> None:
n = len(rows)
name_lines = []
code_lines = []
for r in rows:
esc = gbk_c_string(r["name"])
name_lines.append(f' "{esc}", /* {r["index"]}: {r["name"]} */')
code_lines.append(f' "{r["code"]}", /* {r["index"]} */')
body = f"""#ifndef __LOCAL_SONG_NAMES_H
#define __LOCAL_SONG_NAMES_H
/* Auto-generated by tools/gen_local_song_names.py from local_songs.csv do not hand-edit. */
/* Display names are GBK for LCD_ShowMixedString; codes match 2.bin preset ASCII (e.g. HKTK). */
#define LOCAL_SONG_COUNT {n}
static const char * const LocalSongNameGbk[LOCAL_SONG_COUNT] = {{
{chr(10).join(name_lines)}
}};
static const char * const LocalSongCode[LOCAL_SONG_COUNT] = {{
{chr(10).join(code_lines)}
}};
#endif /* __LOCAL_SONG_NAMES_H */
"""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body, encoding="utf-8", newline="\n")
def main() -> int:
rows = load_rows(CSV_PATH)
write_header(rows, OUT_HDR)
print(f"Wrote {OUT_HDR} ({len(rows)} song(s) from {CSV_PATH.name})")
for r in rows:
print(f" [{r['index']}] {r['code']} {r['name']}")
return 0
if __name__ == "__main__":
sys.exit(main())