subsequence¶
Subsequence - an algorithmic composition framework for Python.
Subsequence gives you a palette of mathematical building blocks - Euclidean rhythms, cellular automata, L-systems, Markov chains, cognitive melody generation - and a stateful engine that lets them interact and evolve over time. Unlike tools that loop a fixed pattern forever, Subsequence rebuilds every pattern fresh before each cycle with full context, so algorithms feed into each other and compositions emerge that no single technique could produce alone. It generates pure MIDI (no audio engine) to control hardware synths, modular systems, drum machines, or software VSTs/DAWs.
What makes it different:
A rich algorithmic palette. Euclidean and Bresenham rhythm generators, cellular automata (1D and 2D), L-system string rewriting, Markov chains, cognitive melody via the Narmour model, probability- weighted ghost notes, position-aware thinning, drones and continuous notes, Perlin and pink noise, logistic chaos maps - plus groove templates, velocity shaping, and pitch-bend automation to shape how they sound.
Stateful patterns that evolve. Each pattern is a Python function rebuilt fresh every cycle with full context - current chord, section, cycle count, shared data from other patterns. A Euclidean rhythm can thin itself as tension builds, a cellular automaton can seed from the harmony, and a Markov chain can shift behaviour between sections.
Optional chord graph. Define weighted chord and key transitions via probability graphs, with gravity and automatic voice leading. A dozen built-in palettes and frozen progressions to lock some sections while others evolve freely. Layer on cognitive harmony for Narmour-based melodic inertia.
Sub-microsecond clock. Hybrid sleep+spin timing achieves typical pulse jitter of < 5 us on Linux, with zero long-term drift.
Turn anything into music.
composition.schedule()runs any Python function on a beat cycle - APIs, sensors, files. Anything Python can reach becomes a musical parameter.Pure MIDI, zero sound engine. No audio synthesis, no heavyweight dependencies. Route to hardware synths, drum machines, Eurorack, or software instruments.
Composition tools:
Rhythm and feel. Euclidean and Bresenham generators, multi-voice weighted Bresenham distribution (
bresenham_poly()), ghost note layers (ghost_fill()), position-aware note removal (thin()- the musical inverse ofghost_fill), evolving cellular-automaton rhythms (cellular_1d(),cellular_2d()), smooth Perlin noise (perlin_1d(),perlin_2d(),perlin_1d_sequence(),perlin_2d_grid()), deterministic chaos sequences (logistic_map()), pink 1/f noise (pink_noise()), L-system string rewriting (p.lsystem()), Markov-chain generation (p.markov()), aperiodic binary rhythms (p.thue_morse()), golden-ratio beat placement (p.fibonacci()), Gray-Scott reaction-diffusion patterns (p.reaction_diffusion()), Lorenz strange-attractor generation (p.lorenz()), exhaustive pitch-subsequence melodies (p.de_bruijn()), step-wise melodies with guaranteed pitch diversity (p.self_avoiding_walk()), drones and explicit note on/off events (p.drone(),p.drone_off(),p.silence()), groove templates (Groove.swing(),Groove.from_agr()), swing viap.swing()(a shortcut forGroove.swing()), randomize, velocity shaping and ramps (p.build_velocity_ramp()), dropout, per-step probability, and polyrhythms via independent pattern lengths.Melody generation.
p.melody()withMelodicStateapplies the Narmour Implication-Realization model to single-note lines: continuation after small steps, reversal after large leaps, chord-tone weighting, range gravity, and pitch-diversity penalty. History persists across bar rebuilds for natural phrase continuity.Chord parts.
comp.chords()andp.progression()play a chord progression — generated from a chord-graph style or given explicitly — at a declared harmonic rhythm: a fixed length, a shaped[WHOLE, HALF, HALF]sequence, orbetween(WHOLE, 3 * WHOLE, step=WHOLE)for chords of varying, quantized length. Voicing density,detachedarticulation, and a seed for a fixed phrase are all declarative.Expression. CC messages/ramps, pitch bend, note-correlated bend/portamento/slide, program changes, SysEx, and OSC output - all from within patterns.
Form and structure. Musical form as a weighted graph, ordered list, or generator. Patterns read
p.sectionto adapt. Conductor signals (LFOs, ramps) shape intensity over time.Mini-notation.
p.seq("x x [x x] x", pitch="kick")- concise string syntax for rhythms, subdivisions, and per-step probability.Scales.
p.snap_to_scale()snaps notes to any scale.scale_notes()generates a list of MIDI note numbers from a key, mode, and range or note count - useful for arpeggios, Markov chains, and melodic walks. Built-in western and non-western modes, plusregister_scale()for your own.Microtonal tuning.
composition.tuning()applies a tuning system globally;p.apply_tuning()overrides per-pattern. Supports Scala.sclfiles, explicit cent lists, frequency ratios, and N-TET equal temperaments. Polyphonic parts use explicit channel rotation so simultaneous notes can carry independent pitch bends without MPE. Compatible with any standard MIDI synthesiser.Randomness tools. Weighted choice, no-repeat shuffle, random walk, probability gates. Deterministic seeding makes every decision repeatable: set it composition-wide (
seed=42) or per generator (seed=on any generator, withrng=for an explicit instance — precedencerng>seed> the pattern’sp.rng). See the README “Conventions” section for the API’s shared vocabulary.Pattern transforms. Legato, detached, fixed gate (
p.duration()), reverse, time-stretch, rotate, transpose, invert, randomize, and conditionalp.every().
Integration:
MIDI clock. Master (
clock_output()) or follower (clock_follow=True). When multiple inputs are connected, only one may be designated as the master clock source; messages from other inputs are filtered to prevent sync interference. Sync to a DAW or drive hardware.Latency compensation. Declare each output device’s physical latency (
latency_ms=); Subsequence delays the faster devices so a mix of hardware and slower software instruments sound together.MIDI mirroring with per-device drum maps. Fan a pattern out to extra
(device, channel)destinations; an entry can carry its owndrum_note_mapso one named drum hit re-resolves to the right voice on each device — a DRM1 and a General MIDI sampler alike.Hardware control. CC input mapping from knobs/faders to
composition.data; patterns read and write the same dict viap.datafor both external data access and cross-pattern communication. OSC for bidirectional communication with mixers, lighting, visuals.Live held-note arpeggiator.
composition.note_input()tracks the notes you hold on a keyboard; a pattern reads them withp.held_notes()and arpeggiates them (p.arpeggio(p.held_notes())), withrelease_msdebounce andlatch. A performance layer over the deterministic composition - empty when rendering headlessly.Live coding. Hot-swap patterns, change tempo, mute/unmute, and tweak parameters during playback via a built-in TCP eval server.
Hotkeys. Single keystrokes to jump sections, toggle mutes, or fire any action - with optional bar-boundary quantization.
Real-time pattern triggering.
composition.trigger()generates one-shot patterns in response to sensors, OSC, or any event.Terminal display. Live status line (BPM, bar, section, chord). Add
grid=Truefor an ASCII pattern grid showing velocity and sustain - makes legato, detached, and staccato articulations visually distinct at a glance. Addgrid_scale=2to zoom in horizontally, revealing swing and groove micro-timing.Web UI Dashboard (Beta). Enable with
composition.web_ui()to broadcast live composition metadata and visualize piano-roll pattern grids in a reactive HTTP/WebSocket browser dashboard.Ableton Link. Industry-standard wireless tempo/phase sync (
comp.link(); requirespip install subsequence[link]). Any Link-enabled app on the same LAN — Ableton Live, iOS synths, other Subsequence instances — stays in time automatically.Recording. Record to standard MIDI file. Render to file without waiting for real-time playback.
Minimal example:
import subsequence import subsequence.constants.instruments.gm_drums as gm_drums comp = subsequence.Composition(bpm=120) @comp.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP) def drums (p): (p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100) .hit_steps("snare_1", [4, 12], velocity=90) .hit_steps("hi_hat_closed", range(16), velocity=70)) comp.play()
Community and Feedback:
Discussions: Chat and ask questions at https://github.com/simonholliday/subsequence/discussions
Issues: Report bugs and request features at https://github.com/simonholliday/subsequence/issues
Package-level exports: Composition, Chord, Groove, MelodicState, Tuning, Motif, Phrase, motif, sentence, period, Degree, ChordTone, Approach, MotifEvent, ControlEvent, Progression, ChordSpan, PitchSet, progression, Cadence, Section, Form, roles, sieve, residual_class, between, parse_chord, register_chord_quality, register_scale, scale_notes, bank_select.
Submodules¶
- subsequence.cadences
- subsequence.chord_graphs
- subsequence.chords
- subsequence.composition
- subsequence.conductor
- subsequence.constants
- subsequence.display
- subsequence.easing
- subsequence.form_state
- subsequence.forms
- subsequence.groove
- subsequence.harmony
- subsequence.held_notes
- subsequence.intervals
- subsequence.keystroke
- subsequence.link_clock
- subsequence.live_client
- subsequence.live_reloader
- subsequence.live_server
- subsequence.melodic_state
- subsequence.midi
- subsequence.midi_utils
- subsequence.motifs
- subsequence.osc
- subsequence.pattern
- subsequence.pattern_algorithmic
- subsequence.pattern_builder
- subsequence.pattern_midi
- subsequence.progressions
- subsequence.roles
- subsequence.sequence_utils
- subsequence.sequencer
- subsequence.tuning
- subsequence.voicings
- subsequence.web_ui