Capturing a take
By the end of this chapter, the piece is a MIDI file to open in a DAW: a take rendered with a new melody and steady drums, and a way to record a take as you play it live.
The piece so far
Everything Playing with other gear built, without its render.
import itertools
import pathlib
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes
import subsequence.sequence_utils
import subsequence.easing
composition = subsequence.Composition(bpm=120, key="E", scale="minor", seed=1)
swing = subsequence.Groove.swing(percent=57)
composition.harmony(style="aeolian_minor")
melody_state = subsequence.MelodicState(low=notes.E4, high=notes.E5, rest_probability=0.2)
hook = subsequence.motif([5, 6, 5, 3, None, 1, 2, 3])
answered = subsequence.period(hook)
composition.form(subsequence.Form([
subsequence.Section("intro", 4, energy=0.2),
subsequence.Section("build", 8, energy=0.5),
subsequence.Section("drop", 8, energy=1.0),
subsequence.Section("breakdown", 4, energy=0.3),
]), loop=True)
composition.transition(before="drop", mute=["drums", "hats"])
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP, min_energy=0.5)
def fill (p):
if p.section.last_bar:
p.sequence(steps=[12, 13, 14, 15], pitches=["high_tom", "high_mid_tom", "low_tom", "low_floor_tom"], velocities=[100, 90, 90, 110])
p.groove(swing)
@composition.pattern(channel=10, beats=0.75, reschedule_lookahead=0.75, drum_note_map=gm_drums.GM_DRUM_MAP, min_energy=0.5)
def pedal_hats (p):
p.hit_steps("hi_hat_pedal", [0], velocity=80)
@composition.pattern(channel=4, beats=4, min_energy=0.3)
def melody (p, chord):
p.melody(melody_state, spacing=0.5, chord_tones=chord.tones(notes.E4))
@composition.pattern(channel=5, bars=4, min_energy=1.0)
def hook_line (p):
p.phrase(answered, root=notes.E5)
composition.conductor.line("swell", start_val=40, end_val=90, duration_beats=32, start_beat=16)
composition.conductor.lfo("breath", shape="sine", cycle_beats=16, min_val=60, max_val=100)
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP, min_energy=0.2)
def hats (p):
p.hit_steps("hi_hat_closed", range(16))
p.velocity_shape(low=40, high=int(p.signal("breath")))
p.groove(swing)
def tempo (section):
if section is not None and section.name == "build":
composition.target_bpm(126, bars=8)
elif section is not None and section.name == "breakdown":
composition.target_bpm(120, bars=4)
composition.on_section(tempo)
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP, min_energy=0.5)
def drums (p):
p.hit_steps("kick", [0, 4, 8, 12], velocity=110)
p.hit_steps("snare", [4, 12], velocity=100)
p.ghost_fill("snare", density=0.5, velocity=(20, 40), bias="before")
steps = [note.position // 6 for note in p.placed()]
p.data["drum_syncopation"] = subsequence.sequence_utils.syncopation(steps, p.grid)
p.groove(swing)
composition.conductor.line("cutoff", start_val=20, end_val=127, duration_beats=32, start_beat=16)
@composition.pattern(channel=3, beats=4, voice_leading=True)
def pad (p, chord):
p.chord(chord, root=notes.E3, sustain=True, velocity=int(p.signal("swell")))
p.cc_ramp(74, int(p.signal("cutoff")), int(p.conductor.get("cutoff", (p.bar + 1) * p.bar_beats)))
# Weather data by Open-Meteo.com, CC BY 4.0: central London, 15 September 2026.
recorded = {
"time": ["2026-09-15T03:00", "2026-09-15T06:00", "2026-09-15T09:00", "2026-09-15T12:00", "2026-09-15T15:00", "2026-09-15T18:00", "2026-09-15T21:00"],
"temperature_2m": [18.1, 17.8, 17.7, 19.8, 22.3, 21.7, 18.4],
"wind_speed_10m": [13.0, 14.4, 15.5, 16.2, 13.7, 13.0, 15.8],
}
readings = itertools.cycle(zip(recorded["temperature_2m"], recorded["wind_speed_10m"]))
temperature = subsequence.easing.EasedValue()
wind = subsequence.easing.EasedValue()
async def read_weather ():
temperature_now, wind_now = next(readings)
temperature.update(temperature_now)
wind.update(wind_now)
composition.schedule(read_weather, cycle_beats=16, reschedule_lookahead=2, wait_for_initial=True)
@composition.pattern(channel=6, beats=4, min_energy=1.0)
def lead (p, chord):
progress = (p.bar % 4) / 4
p.arpeggio(chord, root=int(notes.E4 + 2 * (temperature.get(progress) - 18)), count=3, spacing=0.5)
p.cc(74, int(wind.get(progress) * 6))
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP, min_energy=1.0)
def euclidean_snare (p):
p.euclidean("snare_2", pulses=p.param("pulses", 5), velocity=60).rotate(2).groove(swing)
def hats_out ():
composition.mute("hats")
def hats_in ():
composition.unmute("hats")
def tom_fill (p):
p.sequence(steps=[8, 10, 12, 14], pitches=["high_tom", "high_mid_tom", "low_tom", "low_floor_tom"], velocities=[90, 90, 100, 110])
def fire_fill ():
composition.trigger(tom_fill, channel=10, bars=1, drum_note_map=gm_drums.GM_DRUM_MAP, quantize=4)
def drop_now ():
composition.form_jump("drop")
def more_snare ():
composition.tweak("euclidean_snare", pulses=7)
def drop_again ():
composition.form_next("drop")
composition.display(grid=True)
composition.hotkeys()
composition.hotkey("m", hats_out)
composition.hotkey("u", hats_in)
composition.hotkey("f", fire_fill)
composition.hotkey("d", drop_now)
composition.hotkey("s", more_snare)
composition.hotkey("r", drop_again)
@composition.pattern(channel=7, beats=4)
def keys (p):
p.arpeggio(p.held_notes(), spacing=0.25, velocity=int(p.data.get("keys_velocity", 90)))
@composition.pattern(channel=2, beats=4, min_energy=0.5, mirrors=[(0, 9)])
def bass (p, chord):
busy = p.data.get("drum_syncopation", 0.0) > 0.5
if busy:
p.arpeggio(chord, root=notes.E1, count=1, spacing=1)
else:
p.arpeggio(chord, root=notes.E1, count=4, spacing=0.25)
p.groove(swing)
if not busy:
p.slide(notes=[3, 7, 11, 15], time=0.5, bend_range=12)
pathlib.Path("project.yaml").write_text("""notes:
rain: 36
thunder: 49
channels:
field_kit: 11
cc:
release: 72
""")
definitions = subsequence.load_definitions("project.yaml")
@composition.pattern(channel=definitions.channels["field_kit"], beats=4, drum_note_map=definitions.notes, cc_name_map=definitions.cc, min_energy=1.0)
def field_kit (p):
p.hit_steps("rain", [0, 6, 10], velocity=90)
p.hit_steps("thunder", [12], velocity=110)
p.cc("release", 100)
A new take of one part
The composition's seed=1 makes every render the same take. Each pattern draws its random choices from a seed of its own, made from the composition's seed and the pattern's name, so one part can change while every other part keeps what it plays. A new melody:
composition.reroll("melody")
reroll('melody') -> effective seed 2994986849 (nonce 1)
composition.rerolldeals a pattern a new seed, and says what the seed is. The number afternoncecounts the rerolls.- The new seed comes from the number of rerolls, not from chance, so while the line is in the script, every run plays the same new melody. Call it twice for the melody after that one.
A steady part
A part can be locked, so that it plays the same in every bar:
composition.lock("drums")
composition.reroll("drums")
reroll('drums') refused: 'drums' is locked - call unlock('drums') first
composition.lockgives a pattern the same random choices in every bar, so the drums' ghost notes fall in the same places bar after bar.- A reroll leaves a locked part alone, and says so.
composition.unlockfrees it again.
Rendering the take
composition.render(bars=24, filename="capturing-a-take.mid")
The drums play the same three ghost notes in every bar:
MIDI channel 10
5 1.792 note D2 (38) velocity 34 length 0.083
5 3.792 note D2 (38) velocity 26 length 0.083
5 4.792 note D2 (38) velocity 26 length 0.083
6 1.792 note D2 (38) velocity 34 length 0.083
6 3.792 note D2 (38) velocity 26 length 0.083
6 4.792 note D2 (38) velocity 26 length 0.083
7 1.792 note D2 (38) velocity 34 length 0.083
7 3.792 note D2 (38) velocity 26 length 0.083
7 4.792 note D2 (38) velocity 26 length 0.083
The bass listens to the drums, as it has since Patterns that listen to each other. The locked drums are as busy in every bar, so the bass plays its roots all the way through, where it used to arpeggio and slide in the quieter bars:
MIDI channel 2
6 1.000 note G1 (31) velocity 100 length 1.000
6 2.000 note G1 (31) velocity 100 length 1.000
6 3.000 note G1 (31) velocity 100 length 1.000
6 4.000 note G1 (31) velocity 100 length 1.000
Recording a take as you play it
A render makes a take as fast as the computer can, with no instrument connected. To capture a take as you play it, with your keys, your fills and your knob, record it. In piece.py, give Composition two more arguments:
Example not checked: it is not run when this site is built.
composition = subsequence.Composition(bpm=120, key="E", scale="minor", seed=1, record=True, record_filename="take.mid")
record=Truewrites everything the piece sends while it plays into a MIDI file, andrecord_filenamenames it.- Play the piece with
python piece.py. When Ctrl+C stops it, the file is written.
Into a DAW
Open the file in a DAW, or drag it into an empty project. The file starts with the piece's tempo, its rise into the drop and its fall into the breakdown, and its time signature:
tempo 120 bpm from 1 1.000
tempo 120.01 bpm from 4 4.042, ramping to 126 bpm at 12 4.000 in 768 changes
tempo 125.98 bpm from 20 4.042, ramping to 120 bpm at 24 4.000 in 384 changes
time signature 4/4 from 1 1.000
- A DAW that reads a MIDI file's tempo and time signature plays the take at the piece's own tempo, rise and fall included.
- Subsequence writes a track for each MIDI output, named after it when there is more than one, with each part on its own MIDI channel. A DAW that splits a MIDI file by MIDI channel gives each part a track of its own.
Going further
- The API reference documents every name this guide used, and every one it did not. The cheat sheet gathers the most used on one page.
- Subsequence's own examples are whole pieces to read and run, and
examples/demo.pyis the one to start with. - For a reader who writes Python fluently, Subsequence has a second way to write a piece, the Direct Pattern API: patterns written as classes, with an event loop run by hand instead of
composition.play(). It is for a piece that keeps state across cycles, changes its patterns in place rather than rebuilding them, or runs several sequencers side by side.examples/demo_advanced.pyplays the same music asexamples/demo.py, written that way.