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.
45 lines
1.7 KiB
Python
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)")
|