Skip to main content

High-level, theory-powered MIDI generation library

Project description

MidiGen

PyPI version Tests License: GPL v3

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-lib but you import it as midigen.

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 Song and Section objects 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 - DrumKit class 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

midigen_lib-1.1.0.tar.gz (89.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

midigen_lib-1.1.0-py3-none-any.whl (73.5 kB view details)

Uploaded Python 3

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

Hashes for midigen_lib-1.1.0.tar.gz
Algorithm Hash digest
SHA256 dacd33c8bd925a1915017d5baca991b1a9e88118835f720225036ceb21977ebb
MD5 1dc0ed8ac3830e38c22d641912078fd7
BLAKE2b-256 ff38a1507364d449941088b46c1d08d0f92ff0593b1dc271f0ee8c82238eaaca

See more details on using hashes here.

Provenance

The following attestation bundles were made for midigen_lib-1.1.0.tar.gz:

Publisher: release.yml on cainky/midigen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for midigen_lib-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1bbf30ed47eed884fe37bf932a9205f315703aaed2cca77b9916f6d4c3d571b7
MD5 39e3e31a0ac9e24ff1474f0e77d80ab9
BLAKE2b-256 418a8e89bb095e99fa0a4ba0fd0551065974536da01767a2eb58e9857caa33d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for midigen_lib-1.1.0-py3-none-any.whl:

Publisher: release.yml on cainky/midigen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page