|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Generate pcm_presets.generated.js from src/pcm_tiny.h. |
| 3 | +
|
| 4 | +Parses the pcm_map[] table (names from the trailing comments, wavetable |
| 5 | +entries marked "WT") and emits window.AMY_WAVE_PRESETS, keyed by wave type |
| 6 | +(PCM / WAVETABLE from amy/constants.py), for the AMYboard web editor's |
| 7 | +preset knob UI. |
| 8 | +""" |
| 9 | +import argparse |
| 10 | +import json |
| 11 | +import re |
| 12 | +import sys |
| 13 | +from pathlib import Path |
| 14 | + |
| 15 | +ROOT = Path(__file__).resolve().parents[1] |
| 16 | +PCM_TINY_H = ROOT / "src" / "pcm_tiny.h" |
| 17 | +AMY_CONSTANTS = ROOT / "amy" / "constants.py" |
| 18 | +OUTPUT = ROOT / "src" / "pcm_presets.generated.js" |
| 19 | + |
| 20 | +# One pcm_map[] row: /* [11] WT */ {51053, 16384, 0, 16384, 69}, /* 111.WAV */ |
| 21 | +ENTRY_RE = re.compile( |
| 22 | + r'/\*\s*\[(\d+)\]\s*(\S+)\s*\*/\s*\{[^}]*\}\s*,\s*/\*\s*(.+?)\s*\*/' |
| 23 | +) |
| 24 | + |
| 25 | + |
| 26 | +def wave_constant(name: str) -> int: |
| 27 | + m = re.search(r'^%s\s*=\s*(\d+)' % name, AMY_CONSTANTS.read_text(), re.M) |
| 28 | + if not m: |
| 29 | + raise RuntimeError(f"Could not find {name} in amy/constants.py") |
| 30 | + return int(m.group(1)) |
| 31 | + |
| 32 | + |
| 33 | +def parse_presets(source: str): |
| 34 | + """Return {wave_number: [{name, value, wave}, ...]} from pcm_tiny.h.""" |
| 35 | + pcm_wave = wave_constant("PCM") |
| 36 | + wavetable_wave = wave_constant("WAVETABLE") |
| 37 | + presets = {} |
| 38 | + for m in ENTRY_RE.finditer(source): |
| 39 | + index, marker, name = int(m.group(1)), m.group(2), m.group(3) |
| 40 | + wave = wavetable_wave if marker == "WT" else pcm_wave |
| 41 | + presets.setdefault(wave, []).append( |
| 42 | + {"name": name, "value": index, "wave": wave}) |
| 43 | + if not presets: |
| 44 | + raise RuntimeError(f"No pcm_map entries found in {PCM_TINY_H}") |
| 45 | + return {str(k): v for k, v in presets.items()} |
| 46 | + |
| 47 | + |
| 48 | +def generate_js(presets): |
| 49 | + body = json.dumps(presets, indent=2, sort_keys=True) |
| 50 | + return ( |
| 51 | + "// AUTO-GENERATED by scripts/gen_pcm_presets_js.py — do not edit by hand.\n" |
| 52 | + "// Source: src/pcm_tiny.h\n" |
| 53 | + f"window.AMY_WAVE_PRESETS = {body};\n" |
| 54 | + ) |
| 55 | + |
| 56 | + |
| 57 | +def main(): |
| 58 | + ap = argparse.ArgumentParser() |
| 59 | + ap.add_argument('--check', action='store_true', |
| 60 | + help='verify the committed output is up to date') |
| 61 | + opts = ap.parse_args() |
| 62 | + |
| 63 | + presets = parse_presets(PCM_TINY_H.read_text()) |
| 64 | + js = generate_js(presets) |
| 65 | + if opts.check: |
| 66 | + if not OUTPUT.exists() or OUTPUT.read_text() != js: |
| 67 | + print(f"stale {OUTPUT.relative_to(ROOT)} (run: python3 scripts/gen_pcm_presets_js.py)") |
| 68 | + sys.exit(1) |
| 69 | + print(f"{OUTPUT.relative_to(ROOT)} is up to date") |
| 70 | + return |
| 71 | + OUTPUT.write_text(js) |
| 72 | + n = sum(len(v) for v in presets.values()) |
| 73 | + print(f"Generated {OUTPUT.relative_to(ROOT)} ({n} presets, {len(presets)} wave types)") |
| 74 | + |
| 75 | + |
| 76 | +if __name__ == "__main__": |
| 77 | + main() |
0 commit comments