2026-10-17 00:00:20 +00:00
|
|
|
#!/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)]
|
2026-10-17 06:59:04 +00:00
|
|
|
mf.set_loop(bar(0), bar(2)) # loop the whole 2-bar demo
|
2026-10-17 00:00:20 +00:00
|
|
|
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")
|
|
|
|
|
|
2026-10-17 06:59:04 +00:00
|
|
|
# loop markers must survive the round-trip in all three conventions
|
|
|
|
|
markers = sorted(set(m[1] for t in d["tracks"] for m in t["markers"]))
|
|
|
|
|
expected = sorted(["LOOP_START", "LOOP_END", "RPR LOOP START", "RPR LOOP END",
|
|
|
|
|
"LoopBegin", "LoopEnd"])
|
|
|
|
|
assert markers == expected, "loop markers wrong: %s" % markers
|
|
|
|
|
print("loop markers OK:", ", ".join(markers))
|
|
|
|
|
|
2026-10-17 00:00:20 +00:00
|
|
|
# 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)")
|