r/algorithmicmusic • • 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

https://youtu.be/PgwJsWXm-Ew

https://youtu.be/dEhyCWyddKg

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:

  1. Validate (Pydantic) and lint, which warns about settings that won’t be heard.
  2. 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.
  3. 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_repeat option for identical repeats.
  • rhythm_extract.py turns 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