# Notes for AI agents This repo is designed to be driven by agents. Everything is deterministic, self-verifying, and dependency-light. ## Fastest path: generate a tune from a JSON spec ```bash python3 tools/make_tune.py tunes/specs/04_tea_break_at_the_terminal.json --render ``` Copy that spec, change `name`, `bpm`, `key`, `chords`, `melody`, `loop`. The style preset (`dos_adventure`, `n64_ambient`, `chiptune_racing`) handles arrangement: instruments, accompaniment patterns, drums, vibrato, mix levels. You supply the music theory: a per-bar chord chart and a melody. ## Spec schema ```jsonc { "name": "file_name_no_extension", "style": "dos_adventure | n64_ambient | chiptune_racing", "bpm": 112, "key": ["A", "minor"], // "major" or "minor" "chords": [["A","m"], ["F",""], ...], // one per bar; qualities: // maj m dim aug 7 maj7 m7 m7b5 6 m6 9 add9 sus2 sus4 sus "melody_start_bar": 4, "melody": "A4-8 C5-8 E5-4 A5-2 | ...", // see score grammar below "loop": {"start_bar": 4, "end_bar": 20}, // optional; writes LOOP_START/END, // RPR LOOP START/END, LoopBegin/End markers "counter": "...", // optional 2nd line (dos_adventure only) "fills_at": [11, 19] // optional drum-fill bars (styles with drums) } ``` ## Score grammar (melody strings) - Tokens: `PITCH-dur-vel` — `A4-8-90` (A4 eighth, vel 90). Durations: `1` whole, `2` half, `4` quarter (default), `8` eighth, `16` sixteenth. - Rests: `r4`, `r8`, `r16`, `r2`, `r1`. - `|` separates bars. **If you use `|`, every bar must total exactly 4/4** — the parser raises `ValueError: bar N totals X ticks` and tells you which bar is wrong. Use it; it is the single most useful bug-catcher here. - Pitches: letter + optional `#`/`b` + octave, e.g. `Bb3`, `F#5`. ## Verification loop (do this, don't skip it) 1. `python3 tools/make_tune.py spec.json --render` — writes MIDI, re-parses it, renders WAV, checks for clipping / dead air / wrong duration. 2. If a check fails, fix the spec and re-run. Never declare success without a passing run. 3. Optional theory audit: parse the lead track with `midikit.read()`, flag notes outside the key scale and outside the current bar's chord tones, and treat each flag as either intentional colour or a bug. ## Looping for game background music `loop` in the spec (or `MidiFile.set_loop(start_tick, end_tick)` in Python) writes markers in three conventions so any engine/DAW finds them: `LOOP_START/LOOP_END`, `RPR LOOP START/RPR LOOP END` (Reaper), `LoopBegin/LoopEnd`. Convention: intro bars sit *before* the loop start so the engine plays the intro once, then jumps back to loop start and returns to loop end. ## Library use (Python) `lib/midikit.py` and `lib/songkit.py` are stdlib-only — drop them into any project. Key entry points: `MidiFile`, `Track`, `MPQN.from_bpm`, `set_loop`, `verify`; songkit: `bar`, `melody`, `arp`, `bass_drive`, `drums`, `vibrato`, `parse_score`. Times are ticks; `bar(n)` converts bars to ticks at 480 tpq. ## Rendering Two routes, both verified: - **Sample-based**: `timidity -Ow -s 44 in.mid -o out.wav` + a GM soundfont. FluidR3 is MIT-licensed and one command away: `apt install fluid-soundfont-gm` (148MB; lighter: `timgm6mb-soundfont`, ~6MB). Not bundled in-repo because of size, not licence. Debian's timidity: use `-Ow -s 44` (no `-O2` mode). - **OPL3 FM (authentic DOS sound, no soundfont)**: `./tools/opl/build.sh` once (needs cmake+g++, fetches libADLMIDI), then `./tools/render_opl.sh`. `OPL_MODEL=` env picks a game-driver volume model: 3 DMX/Doom (default), 4 Apogee/Duke Nukem 3D, 2 id-style, 8 AIL, 10 HMI. Output is normalised. ## Known gotchas - Pitch bends extend file duration (vibrato tails count). - No comments inside melody strings — `#` gets parsed as a note. - GM2 patches (ocarina etc.) are unreliable on GM1 synths; use pan flute. - Five hot tracks sum to clipping; lower CC7 volumes, not velocities.