Live coding
By the end of this chapter, the piece plays while you edit its patterns, and every save is heard from the next bar without the music stopping. You can also type into the piece while it plays.
The build checks nothing in this chapter. Live coding needs a piece playing and a person saving a file, and a render has neither, so every block is marked.
Two files
Live coding keeps apart what runs once and what changes as the piece plays. The patterns move into a file of their own, patterns.py, which Subsequence runs again every time it is saved:
Not checked: the build does not run this.
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes
import subsequence.sequence_utils
swing = subsequence.Groove.swing(percent=57)
hook = subsequence.motif([5, 6, 5, 3, None, 1, 2, 3])
answered = subsequence.period(hook)
melody_state = composition.data["melody_state"]
temperature = composition.data["temperature"]
wind = composition.data["wind"]
@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.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)
@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.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)))
@composition.pattern(channel=2, beats=4, min_energy=0.5)
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)
@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)
- Subsequence gives
patterns.pytwo names when it runs it,compositionandsubsequence, so the file begins with the other imports its patterns use. - Anything else a pattern needs from
piece.pycomes throughcomposition.data.melody_state,temperatureandwindare made once, inpiece.py, so the melody keeps its memory and the lead its weather through every save.swing,hookandansweredare made again at each save, which changes nothing, because they remember nothing. - The patterns keep the order they had, so the drums still come before the bass, which reads what they write.
piece.py keeps everything else, and runs once:
Not checked: the build does not run this.
import itertools
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes
import subsequence.easing
composition = subsequence.Composition(bpm=120, key="E", scale="minor", seed=1)
composition.harmony(style="aeolian_minor")
melody_state = subsequence.MelodicState(low=notes.E4, high=notes.E5, rest_probability=0.2)
composition.data["melody_state"] = melody_state
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.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)
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.conductor.line("cutoff", start_val=20, end_val=127, duration_beats=32, start_beat=16)
# 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()
composition.data["temperature"] = temperature
composition.data["wind"] = wind
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)
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.watch("patterns.py")
composition.play()
piece.pyis the piece without its patterns,swing,hook,answeredand thesequence_utilsimport, which only the drums use, and with three new lines that putmelody_state,temperatureandwindintocomposition.data.composition.watchrunspatterns.pyonce, and again each time it is saved. Run the piece from the folder that holds both files.
Editing while it plays
Start the piece with python piece.py, as before, and open patterns.py in your editor. To play the hats on eighth notes, change the first line of hats to this and save:
Not checked: the build does not run this.
p.hit_steps("hi_hat_closed", range(0, 16, 2))
range(0, 16, 2) counts from 0 up to 15 in twos.
- A save is heard from the next bar. A save in the last beat or two of a bar is heard a bar later: Subsequence reads a save once the file has held still for a quarter of a second, and builds each bar's patterns a beat before it starts.
- A save that Python cannot read, such as a bracket left open, is skipped. Subsequence prints the error, and the piece plays on as it was until the file is saved again, mended.
- Delete a pattern and save, and it stops. Add one, and it comes in on the next bar line that fits its length: a pattern a bar long on the next bar, one four bars long on the next four-bar line.
- A save changes how a pattern is set up as well as what it plays. A change to
channel,beats,min_energyor anything else in the line abovedefis heard from the pattern's next cycle, exceptdevice, which waits until the piece is restarted, as a change topiece.pydoes.
Typing into the piece
composition.live lets you type Python into the piece while it plays. In piece.py, add this line before the watch line:
Not checked: the build does not run this.
composition.live()
Then, with the piece playing, open a second terminal in the same folder, with the same virtual environment active, and start the prompt:
Not checked: the build does not run this.
python -m subsequence.live_client
It says it has connected, shows where the piece has got to, and waits at a >>> prompt. A line is heard from the next bar, as a save is:
Not checked: the build does not run this.
>>> composition.mute("hats")
OK
>>> composition.unmute("hats")
OK
- Anything
compositioncan do can be typed here, such ascomposition.tweak("euclidean_snare", pulses=7). The moves inpiece.pyare not here by name, because the prompt knows onlycompositionandsubsequence. - Ctrl+D leaves the prompt, and the piece plays on.
composition.live()runs whatever it is sent. It listens only to programs on the same computer, so leave it out on a computer that other people use.