- 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
Replaces the April wreckage (fifteen 70-byte 'working' MIDIs, archived)
with a proper pipeline:
- lib/midikit.py: stdlib-only SMF format-1 writer + reader/verifier
- lib/songkit.py: composition helpers (bar-grid score DSL with a 4/4
bar linter, OPL arps, driving bass, vibrato, drum patterns)
- tunes/: three original tunes - DOS adventure theme (Dm/132),
N64 ambient (G/72), chiptune racing (Em/152) - all with LOOP_START/
LOOP_END markers for game engines
- tools/build_all.sh + tools/check_wav.py: generate, verify, render
via timidity+FluidR3, fail on clipping/dead air
- tests/smoke_test.py: round-trip verified against mido
All three tunes render clean: no clipping, no dead air, durations
match, chromaticism audited against the chord chart.