Files
midi-playlist/AGENTS.md
T
Hermes Agent 633eb92352 Public-ready: JSON tune specs, first-class loop API, licence, AGENTS.md
- MidiFile.set_loop(): one call writes LOOP_START/END, RPR LOOP
  START/END (Reaper) and LoopBegin/End markers - every engine finds
  its dialect; reader now parses markers and the smoke test asserts
  the round-trip
- tools/make_tune.py: declarative JSON spec -> fully arranged MIDI
  with style presets (dos_adventure, n64_ambient, chiptune_racing);
  the agent-friendly front door, no Python required
- tunes/specs/: demo tune 04 'Tea Break at the Terminal' (Am/112,
  loop bars 4-20) generated entirely from JSON
- AGENTS.md: spec schema, score grammar, verification loop, looping
  conventions for game BGM
- MIT LICENSE; history audited clean of credentials
2026-10-17 06:59:04 +00:00

3.5 KiB

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

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

{
  "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

Needs timidity + a GM soundfont (e.g. FluidR3_GM.sf2, not bundled — licensing). Debian's timidity: use -Ow -s 44 (no -O2 mode).

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.