Harden for agent users: spec validation, key-sig accidentals, N64 chord qualities
- make_tune: validate_spec() reports every spec problem in one run (missing keys, unknown style, bad qualities, bpm<=0, out-of-range loop/fills, melody longer than chart, unsafe name) — no raw KeyErrors - make_tune: key_sig_for handles accidentals in minor and major keys, any case (Bb minor, F# minor, bb major); real spellings kept (C# major) - make_tune: n64 pad uses QUALITIES table (dim/aug/sus now correct) - make_tune: WARNING on stderr for fields a style can't use (counter for non-DOS, fills_at for n64) instead of silent drops - make_tune: name sanitised ([A-Za-z0-9][A-Za-z0-9._-]*), no traversal - make_tune: render errors actionable (timidity presence check, stderr surfaced, sys.executable instead of bare python3) - songkit: parse_score lints the FINAL bar too (was unchecked); this caught a real 5-beat bar in 01_service_station_2am COUNTER_A (fixed) - build_all.sh: dependency + soundfont checks, removes stale midi/wav outputs with no current source, fails if nothing was produced - smoke_test: runs every spec, checks loop marker tick positions, feeds malformed bars to the linter, covers validation + key sigs - AGENTS.md: documents 0-based bars and exclusive end_bar, validation behaviour; SKILL.md added so the repo loads as an agent skill
This commit is contained in:
1 parent
f5bd7ae13c
commit
810f127d01
7 files changed
+482
-50
No files matched your search
+8
-3
@@ -33,9 +33,9 @@ def parse_score(text, tpq=TPQ):
|
||||
rests: r4 / r8 / r2 / r16 / r1
|
||||
|
||||
Bars may be separated with '|'. If any '|' is present, every bar
|
||||
segment must total exactly 4 quarter notes — a free bar-length
|
||||
linter that has saved me from more off-by-one-eighth bugs than
|
||||
I care to admit.
|
||||
segment — including the final one — must total exactly 4 quarter
|
||||
notes — a free bar-length linter that has saved me from more
|
||||
off-by-one-eighth bugs than I care to admit.
|
||||
|
||||
Example: 'D4 A4-8 D5-8 F5-8 E5-4 D5-2'
|
||||
"""
|
||||
@@ -43,6 +43,7 @@ def parse_score(text, tpq=TPQ):
|
||||
tick = 0
|
||||
bar_ticks = 0
|
||||
bar_no = 1
|
||||
saw_sep = False
|
||||
for tok in text.split():
|
||||
if tok == "|":
|
||||
if bar_ticks != tpq * 4:
|
||||
@@ -50,6 +51,7 @@ def parse_score(text, tpq=TPQ):
|
||||
% (bar_no, bar_ticks, tpq * 4))
|
||||
bar_ticks = 0
|
||||
bar_no += 1
|
||||
saw_sep = True
|
||||
continue
|
||||
if tok.startswith("r"):
|
||||
dur = {"r1": tpq * 4, "r2": tpq * 2, "r4": tpq, "r8": tpq // 2,
|
||||
@@ -69,6 +71,9 @@ def parse_score(text, tpq=TPQ):
|
||||
out.append((tick, pitch, dur, vel))
|
||||
tick += dur
|
||||
bar_ticks += dur
|
||||
if saw_sep and bar_ticks != tpq * 4:
|
||||
raise ValueError("bar %d totals %d ticks, expected %d (4/4)"
|
||||
% (bar_no, bar_ticks, tpq * 4))
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user