High-level, theory-powered MIDI generation library
Project description
MidiGen
A Python library for creating and manipulating MIDI files with a music-theory-aware API. Build anything from simple melodies to multi-track compositions with chord progressions, scales, and rhythmic patterns.
Built on mido with a custom Roman numeral analysis engine for chord progression parsing. No heavy dependencies.
The package is published on PyPI as
midigen-libbut you import it asmidigen.
Installation
Requires Python 3.10+:
pip install midigen-lib
For development:
git clone https://github.com/cainky/midigen.git
cd midigen
uv sync
Quick Start
from midigen import Song, Section, Key, MidiCompiler
song = Song(key=Key("C", "major"), tempo=120)
song.add_section(Section("Verse", 8, "I-V-vi-IV"))
song.add_section(Section("Chorus", 4, "IV-I-V-vi"))
song.add_instrument("Acoustic Grand Piano")
MidiCompiler(song).compile().save("my_song.mid")
Song holds your musical intent. MidiCompiler handles MIDI protocol details (channels, tracks, timing).
Features
- Song structure - Compose with
SongandSectionobjects using familiar concepts like verses and choruses - Chord progressions - Roman numeral notation (e.g., "I-IV-V-I") with automatic voice leading
- 14 scale types - Major, minor (natural/harmonic/melodic), pentatonic, blues, whole tone, chromatic, and all seven modes
- 22 chord types - Triads, suspended, seventh, ninth, eleventh, thirteenth, augmented, and diminished voicings
- Melody generation - From note names, scale degrees, or random walk algorithm
- Arpeggio patterns - Ascending, descending, and alternating
- Drum programming -
DrumKitclass with standard General MIDI drum names - Multi-track support - Layer instruments across separate MIDI tracks with General MIDI instrument names
- Time utilities - Convert between musical time (measures, beats) and MIDI ticks
Examples
Melodies
from midigen import Melody, Scale
# From note names
melody = Melody.from_note_names("C4 E4 G4 E4 C4", durations=480)
# From scale degrees with rhythm
scale = Scale.major(60) # C major
melody = Melody.from_degrees(
scale,
degrees=[1, 3, 5, 8, 5, 3, 1],
rhythms="quarter quarter quarter half quarter quarter half"
)
# Random walk (reproducible with seed)
melody = Melody.random_walk(
start_pitch=60, length=16, scale=scale, max_interval=3, seed=42
)
# Transform
transposed = melody.transpose(5)
retrograde = melody.reverse()
Scales and Modes
from midigen import Scale
# Basic
c_major = Scale.major(60)
a_minor = Scale.minor(57)
a_harmonic_minor = Scale.harmonic_minor(57)
# Pentatonic and blues
c_pent = Scale.major_pentatonic(60)
c_blues = Scale.blues(60)
# Modes
d_dorian = Scale.dorian(62)
e_phrygian = Scale.phrygian(64)
f_lydian = Scale.lydian(65)
g_mixolydian = Scale.mixolydian(67)
b_locrian = Scale.locrian(71)
# Other
c_whole_tone = Scale.whole_tone(60)
c_chromatic = Scale.chromatic(60)
Multi-Track Compositions
from midigen import Song, Section, Key, MidiCompiler
song = Song(key=Key("Am", "minor"), tempo=90)
song.add_section(Section(name="Intro", length=4, chord_progression="i-VI-III-VII"))
song.add_section(Section(name="Verse", length=8, chord_progression="i-VI-III-VII-i-VI-iv-V"))
song.add_instrument("Synth Bass 1")
song.add_instrument("String Ensemble 1")
song.add_instrument("Lead 1 (square)")
compiler = MidiCompiler(song)
compiler.compile_instrument("Synth Bass 1", octave=3)
compiler.compile_instrument("String Ensemble 1", octave=4)
compiler.compile_instrument("Lead 1 (square)", octave=5)
compiler.save("multi_track.mid", output_dir="./output")
Drum Patterns
from midigen import MidiGen, DrumKit, Key
midi = MidiGen(key=Key("C"))
drum_kit = DrumKit()
# Simple 4/4 rock beat
for i in range(4):
t = i * 480
drum_kit.add_drum("Bass Drum 1", time=t)
drum_kit.add_drum("Acoustic Snare", time=t + 240)
drum_kit.add_drum("Closed Hi Hat", time=t)
drum_kit.add_drum("Closed Hi Hat", time=t + 120)
drum_kit.add_drum("Closed Hi Hat", time=t + 240)
drum_kit.add_drum("Closed Hi Hat", time=t + 360)
track = midi.get_active_track()
track.add_drum_kit(drum_kit)
midi.save("drum_beat.mid")
Arpeggios
from midigen import MidiGen, Note, Arpeggio, ArpeggioPattern, Key, KEY_MAP
midi = MidiGen(tempo=140, key=Key("C"))
track = midi.get_active_track()
notes = [
Note(pitch=KEY_MAP["C4"], velocity=70, duration=120, time=0),
Note(pitch=KEY_MAP["E4"], velocity=70, duration=120, time=0),
Note(pitch=KEY_MAP["G4"], velocity=70, duration=120, time=0),
Note(pitch=KEY_MAP["B4"], velocity=70, duration=120, time=0),
]
arpeggio = Arpeggio(
notes=notes,
pattern=ArpeggioPattern.ASCENDING, # or DESCENDING, ALTERNATING
delay=120,
loops=4,
)
track.add_arpeggio(arpeggio)
midi.save("arpeggio.mid")
Time Conversions
from midigen import TimeConverter
tc = TimeConverter(ticks_per_quarter_note=480)
one_measure = tc.measures_to_ticks(1) # 1920 ticks in 4/4
half_note = tc.note_duration("half") # 960 ticks
dotted_quarter = tc.note_duration("dotted_quarter") # 720
triplet_eighth = tc.note_duration("triplet_eighth") # 160
# Different time signatures
waltz_measure = tc.measures_to_ticks(
1, time_signature_numerator=3, time_signature_denominator=4
)
Low-Level API
For full control, bypass Song/MidiCompiler and work directly with tracks and notes:
from midigen import MidiGen, Note, Chord, Key, KEY_MAP
midi = MidiGen(tempo=120, time_signature=(4, 4), key_signature=Key("C"))
track = midi.get_active_track()
track.add_note(Note(pitch=KEY_MAP["C4"], velocity=64, duration=480, time=0))
chord = Chord([
Note(pitch=KEY_MAP["E4"], velocity=64, duration=480, time=480),
Note(pitch=KEY_MAP["G4"], velocity=64, duration=480, time=480),
])
track.add_chord(chord)
midi.save("low_level.mid")
Architecture
The library is organized into four layers:
midigen.theory— Pure music theory (Note, Key, Scale, roman numeral parsing, time conversion)midigen.composition— Musical structures (Chord, ChordProgression, Arpeggio, Melody, DrumKit, Section)midigen.protocol— MIDI protocol (Track, ChannelPool, instruments)midigen.api— High-level API (Song, MidiGen, MidiCompiler)
All public names are available via from midigen import ....
Contributing
Contributions are welcome. See CONTRIBUTING.md for details.
License
GPL-3.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file midigen_lib-1.1.0.tar.gz.
File metadata
- Download URL: midigen_lib-1.1.0.tar.gz
- Upload date:
- Size: 89.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dacd33c8bd925a1915017d5baca991b1a9e88118835f720225036ceb21977ebb
|
|
| MD5 |
1dc0ed8ac3830e38c22d641912078fd7
|
|
| BLAKE2b-256 |
ff38a1507364d449941088b46c1d08d0f92ff0593b1dc271f0ee8c82238eaaca
|
Provenance
The following attestation bundles were made for midigen_lib-1.1.0.tar.gz:
Publisher:
release.yml on cainky/midigen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midigen_lib-1.1.0.tar.gz -
Subject digest:
dacd33c8bd925a1915017d5baca991b1a9e88118835f720225036ceb21977ebb - Sigstore transparency entry: 1282825276
- Sigstore integration time:
-
Permalink:
cainky/midigen@db17110d25bac9bf646e7ee56b96a128d914b122 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/cainky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@db17110d25bac9bf646e7ee56b96a128d914b122 -
Trigger Event:
push
-
Statement type:
File details
Details for the file midigen_lib-1.1.0-py3-none-any.whl.
File metadata
- Download URL: midigen_lib-1.1.0-py3-none-any.whl
- Upload date:
- Size: 73.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bbf30ed47eed884fe37bf932a9205f315703aaed2cca77b9916f6d4c3d571b7
|
|
| MD5 |
39e3e31a0ac9e24ff1474f0e77d80ab9
|
|
| BLAKE2b-256 |
418a8e89bb095e99fa0a4ba0fd0551065974536da01767a2eb58e9857caa33d3
|
Provenance
The following attestation bundles were made for midigen_lib-1.1.0-py3-none-any.whl:
Publisher:
release.yml on cainky/midigen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midigen_lib-1.1.0-py3-none-any.whl -
Subject digest:
1bbf30ed47eed884fe37bf932a9205f315703aaed2cca77b9916f6d4c3d571b7 - Sigstore transparency entry: 1282825278
- Sigstore integration time:
-
Permalink:
cainky/midigen@db17110d25bac9bf646e7ee56b96a128d914b122 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/cainky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@db17110d25bac9bf646e7ee56b96a128d914b122 -
Trigger Event:
push
-
Statement type: