Files
midi-playlist/tests/smoke_test.py
T
Hermes Agent 95480abdab Rebuild as a code-first MIDI composition toolkit
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.
2026-10-17 00:00:20 +00:00

45 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""Smoke test: does midikit+songkit produce a valid, parseable, playable file?
Run from repo root: python3 tests/smoke_test.py"""
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from midikit import MidiFile, Track, MPQN, note, note_name, read, verify
from songkit import bar, melody, arp, bass_drive, drums, vibrato, humanize, parse_score
assert note("A4") == 69 and note("C#5") == 73 and note("Bb3") == 58
assert note_name(69) == "A4" and note_name(60) == "C4"
mf = MidiFile(tpq=480)
mf.time_sig = (4, 2)
mf.key_sig = (-1, True) # D minor
lead = Track("Lead", channel=0)
lead.program(0, "square lead")
melody(lead, 0, "D4 A4-8 D5-8 F5-8 E5-4 D5-2")
vibrato(lead, bar(0), bar(4), channel=0, tempo_bpm=120)
bass = Track("Bass", channel=1)
bass.program(0, "synth bass 1")
bass_drive(bass, 0, [("D", "m"), ("Bb", "")])
acomp = Track("Arp", channel=2)
acomp.program(0, "electric piano 1")
arp(acomp, 0, [("D", "m"), ("Bb", "")])
d = Track("Drums", channel=9)
drums(d, 0, 2, "rock")
mf.add_track(lead); mf.add_track(bass); mf.add_track(acomp); mf.add_track(d)
mf.tempos = [MPQN.from_bpm(0, 120)]
path = "/tmp/midikit_smoke.mid"
mf.write(path)
d = verify(path, min_sec=1, max_sec=30)
print("OK:", path, d["duration_sec"], "sec,",
sum(len(t["notes"]) for t in d["tracks"]), "notes,",
len(d["tracks"]), "tracks")
# cross-check with mido if present (third-party parser as second opinion)
try:
import mido
m = mido.MidiFile(path)
print("mido agrees:", m.length, "sec,", len(m.tracks), "tracks")
assert abs(m.length - d["duration_sec"]) < 0.5
except ImportError:
print("(mido not installed; skipping cross-check)")