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.
This commit is contained in:
1 parent
7ddabaff2a
commit
95480abdab
31 files changed
+1222
-31
No files matched your search
@@ -0,0 +1,44 @@
|
||||
#!/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)")
|
||||
Reference in new issue
Block a user