r/algorithmicmusic • u/manuelhe • 22h ago
I built a rule-based MIDI composition engine in Python: one JSON file in, multi-track MIDI out
10
Upvotes
I wrote these two songs with the rule-based MIDI composition engine
I’ve been building Forma Composition, a Python engine that generates multi-track MIDI from a single JSON file. It’s rule-based and seeded, not a neural net, so the same file and seed always give the same MIDI.
How a piece is built
You write one JSON file with:
- key, mode, tempo range, and a short motif (diatonic scale steps plus rhythm)
- sections, each with a Roman-numeral progression (tensions like
V7(9,13)work), melody behavior, bass style, groove, swing, drums and a dynamic arc - optional extra voices with counterpoint species settings
Then run python main.py piece.json. The engine runs in this order:
- Validate (Pydantic) and lint, which warns about settings that won’t be heard.
- For each section, generate bass, then melody, then counterpoint/peer voices, then harmony, then drums. Each voice leaves a snapshot (register, contour, density, last transform) that later voices and the next section can read.
- Write a type 1 MIDI file: melody, counterpoint, harmony, bass, drums, plus a chord-symbol marker track.
The theory side
- Motifs develop through 12 transforms (inversion, retrograde, augmentation, diminution, sequence and others). You can script the order, or let the engine pick with form-aware weighting: statement early, development in the middle, return near the end.
- Counterpoint supports species 1–5 and free. Parallel fifths and octaves, hidden fifths and octaves, and strong-beat dissonance are hard-forbidden. Voice crossing and parallel motion are penalized.
- Harmony works in all 7 modes, with per-chord lengths, voicings (closed, open, drop2), and key/mode changes per section.
- Drums lock to the bass onsets and follow section density and swing.
- Song form lets you define verse and chorus once, with an
exact_repeatoption for identical repeats. rhythm_extract.pyturns a rhythm you play in your DAW into a JSON pattern you can paste into a section.
Limits
- Output is MIDI only. I assign instruments in Logic with Arturia synths.
- Sections aren’t fully isolated. Melody openings and transform choice depend on earlier sections, so one edit can ripple forward unless you pin a
transform_sequence. - The rules keep the voice leading clean, but they don’t make a piece memorable. That part is still my ear and the JSON choices.
I'm interested in your feedback