Chapter 3 · Notes, Beats, and Durations¶
So far every hit has been a drum on a grid step. This chapter is where the sequencer learns to sing: we’ll place individual pitched notes at exact beat positions, give each one a length, and build a bassline by hand that locks to the Chapter 0 drum loop.
3.1 p.note and the required beat¶
A drum hit is a moment — it has no length worth speaking of. A bass note is
different: it starts somewhere, sounds for a while, and then stops. The verb for
placing one pitched note is p.note, and its full shape is:
p.note(pitch, beat, velocity, duration)
pitch— which note, as a MIDI number (0–127, with 60 = middle C) or a named constant we’ll meet in §3.5.beat— where in the bar it starts, counted in beats from0.0. This argument is required. A note with no position is meaningless, so Subsequence insists you say when it sounds.velocity— how hard it plays (1–127), defaulting to 100.duration— how long it rings, in beats, defaulting to0.25(a sixteenth note).1.0is a full quarter note;0.5an eighth;2.0a half note.
Here is a complete, runnable example — one bass note per beat, each a quarter note long. It shows the imports in full so you can copy it as a standalone script.
import subsequence
import subsequence.constants.midi_notes as notes
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def bass(p):
p.note(notes.C2, beat=0, duration=1.0) # downbeat
p.note(notes.C2, beat=1, duration=1.0)
p.note(notes.G2, beat=2, duration=1.0)
p.note(notes.C2, beat=3, duration=1.0)
composition.render(bars=4, filename="bass.mid")
Reading the new parts:
channel=1sends to MIDI channel 1 — a melodic instrument, not the drum channel. (Remember from Chapter 0: channels are 1-indexed, so 1 is the first channel and 10 is the General MIDI drum channel.) Point a bass synth or a DAW track at channel 1 and you’ll hear these notes.beats=4makes the pattern four beats long — one bar of 4/4.notes.C2is the named constant for the MIDI note C2; we’ll unpack the naming in §3.5. For now read it as “the C two octaves below middle C,” a comfortable bass register.Each
p.note(...)places exactly one note. Thebeat=is spelled out every time because it’s required and because being explicit reads clearly.
Warning
beat is not optional. Writing p.note(notes.C2) with no position raises
TypeError: ... missing 1 required positional argument: 'beat'. Older drafts of
Subsequence let you drop it; current versions do not. Always give a beat:
p.note(pitch, beat=...).
Tip
A negative beat counts back from the end of the pattern. In a 4-beat bar,
beat=-0.5 lands on the “and” of beat 4 (i.e. beat 3.5) — handy for placing a
note near the end of the bar without doing the subtraction yourself.
Reference
3.2 Beats vs grid steps¶
Chapters 0 and 1 placed drums on grid steps — whole numbers 0..15. This
chapter places notes on beats — 0.0, 1.0, 2.0 ..., fractions allowed. These
are two ways of naming the same timeline, and keeping them straight is the one
idea this chapter really turns on.
A beat is musical time measured in quarter notes. Beat
0.0is the downbeat; beat1.5is the “and” of beat 2; beat2.25is the first sixteenth after beat 3. Beats are whatp.note,p.hit, andp.repeatspeak.A step is a slot on a fixed grid. A 4-beat pattern is divided into 16 sixteenth-note steps numbered
0..15. Steps are whatp.hit_stepsandp.sequencespeak.
The bridge between them is simple. In a 4-beat bar the grid is 16 steps, so each
step is 4 / 16 = 0.25 beats wide, and:
beat = step × 0.25 (in a standard 4-beat, sixteenth-grid pattern)
Step (0–15) |
Beat (0.0–) |
In words |
|---|---|---|
|
|
downbeat (beat 1) |
|
|
the “and” of beat 1 |
|
|
beat 2 |
|
|
the “and” of beat 2 |
|
|
beat 3 |
|
|
beat 4 |
|
|
the “and” of beat 4 |
|
|
the last sixteenth |
So the Chapter 0 kick on steps [0, 4, 8, 12] is exactly a note on beats
[0, 1, 2, 3] — four-on-the-floor either way. Use steps when you’re thinking
in a fixed grid of equal slots (most drum work); use beats when you want a
note at an exact musical position, including off-grid ones like a triplet or a
hair-early push.
Note
Why two systems at all? Grid steps make even, repetitive rhythms effortless —
range(16) is sixteen hi-hats with no arithmetic. Beats make expressive
placement effortless — beat=2.66 lands a note a third of the way through beat 3,
which no whole-number step can name. You’ll reach for whichever fits the moment,
and they always refer to the same underlying time.
3.3 A hand-written bassline (p.sequence)¶
Writing one p.note per note is clear but verbose. When you want several notes on
a grid — different pitches, different velocities, different lengths — p.sequence
lets you lay them out as parallel lists, the same way a hardware step sequencer
shows a row of knobs per step.
p.sequence(steps, pitches, velocities, durations)
You give the steps that fire, then a pitch for each, and optionally a
velocity and duration for each. Any of pitches, velocities,
durations can be a single value (used for every step) or a list read in step
order. Here’s a walking bassline under the Chapter 0 drums — root on the
downbeat, a passing tone, the fifth, and a chromatic approach:
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes
composition = subsequence.Composition(bpm=120)
# The Chapter 0 drum loop, unchanged, on channel 10.
@composition.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)
p.hit_steps("snare_1", [4, 12], velocity=90)
p.hit_steps("hi_hat_closed", range(16), velocity=70)
# A bassline that locks to it, on channel 1.
@composition.pattern(channel=1, beats=4)
def bass(p):
p.sequence(
steps = [0, 4, 8, 10, 12],
pitches = [notes.C2, notes.C2, notes.G2, notes.AS2, notes.A2],
velocities = [110, 85, 100, 80, 95],
durations = [1.0, 0.5, 0.75, 0.25, 1.0],
)
composition.render(bars=4, filename="bass-and-drums.mid")
What each list does, read down the columns:
steps— fires on steps 0, 4, 8, 10, 12 → beats 0, 1, 2, 2.5, 3.pitches— C2, C2, G2, A♯2, A2. The root twice, the fifth, then a chromatic A♯ leading into the A.velocities— the downbeat hits hardest (110); the off-beat passing note is softer (80), so the line breathes.durations— the downbeat rings a full beat; the A♯ is a quick sixteenth (0.25) that gets out of the way of the note it leads to.
Because the bass shares the beats=4 length with the drums, step 0 of the bass
and step 0 of the kick are the same instant — the line is glued to the
four-on-the-floor without any extra effort.
Note
The bass uses its own channel (1), so it goes to a melodic synth while the
drums stay on channel 10. Two patterns on two channels play together; render
writes both into the one MIDI file, each tagged with its MIDI channel — so your
DAW can split them onto separate instruments by channel on import.
Tip
A single value broadcasts to every step. p.sequence([0, 4, 8, 12], notes.C2, durations=1.0) is four quarter-note C2s — one pitch and one duration applied to
all four steps. Reach for lists only where the steps actually differ.
Tip
Tile a repeating cell with *. When a per-step list is just a short cell
repeated a whole number of times, plain Python list multiplication reads best: a
two-step accent across sixteen steps is [100, 70] * 8, and [1, 0, 0] * 4 is
[1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]. It works on any per-step list —
velocities, durations, a 0/1 rhythm cell — and there’s deliberately no helper for
it; it’s just the * operator. When the target length isn’t an exact multiple
of the cell, reach for subsequence.sequence_utils.tile(cell, length), which
cycles and truncates to exactly length (e.g. tile([1, 0, 0], 16)).
Reference
3.4 Durations, repeats, detached and legato¶
A note’s duration is its gate time — how long it sounds before it’s released.
You can set it per-note (the duration= argument we’ve used), or shape every
note in a pattern at once with three articulation verbs.
p.repeat fires one pitch at a steady spacing for the whole pattern — the
“note repeat” of an MPC or Push. Give it a spacing in beats and it fills the bar:
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def driving_bass(p):
p.repeat(notes.C2, spacing=0.5, velocity=90, duration=0.4) # eighth notes
composition.render(bars=2, filename="repeat-bass.mid")
spacing=0.5 places a note every half-beat — straight eighth notes — from beat 0
to the end of the bar. (spacing=0.25 would give sixteenths; spacing=1.0,
quarters.) The duration=0.4 leaves a sliver of silence before each next onset,
so the notes don’t slur together.
p.duration overrides the length of every note already placed, to one fixed
value. Lay the notes down first, then stamp a global gate over them:
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def stabby_bass(p):
p.hit(notes.C2, [0, 1, 2, 3], velocity=95) # four notes, default short length
p.duration(0.25) # now make them all sixteenth-long stabs
composition.render(bars=2, filename="stabs.mid")
Note
p.hit is the pitched cousin of p.hit_steps. Where hit_steps takes grid
steps, p.hit(pitch, beats, ...) takes a list of beats — p.hit(notes.C2, [0, 1, 2, 3]) is one C2 on each beat. It’s the quickest way to drop the same
pitch at several beat positions.
p.detached trims every note so a guaranteed gap of silence precedes the next
onset — clean, separated articulation, and a safety margin on a monophonic synth
that would otherwise choke when one note’s tail overlaps the next:
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def mono_safe_bass(p):
p.sequence([0, 4, 8, 12], notes.C2, durations=1.0) # long quarter notes
p.detached(0.1) # each ends 0.1 beats before the next begins
composition.render(bars=2, filename="detached.mid")
Its opposite, p.legato, stretches each note to fill the gap up to the next
onset, so the line plays smoothly with no holes — the bound, connected feel:
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def smooth_bass(p):
p.hit(notes.C2, [0, 2], velocity=90) # two short notes
p.legato() # each grows to meet the next
composition.render(bars=2, filename="legato.mid")
Verb |
What it does to every placed note |
|---|---|
|
Sets each note to one fixed length, ignoring what comes next. |
|
Shrinks each note to leave at least |
|
Resizes each note to fill the gap to the next ( |
Important
These three verbs act on notes already placed, so call them after you’ve
written the line. They reshape durations only — they never move a note’s start.
Note length is about articulation; pitch is set with snap_to_scale, and
timing feel with swing — we’ll meet both in later chapters. Don’t confuse
length with either.
Reference
3.5 Note and duration constants¶
Bass notes written as bare MIDI numbers (36, 43, 45) work, but they read
like a phone number. Subsequence ships named constants so you can write the music,
not the arithmetic.
Pitches live in subsequence.constants.midi_notes. Each constant is named
<Letter><Octave> for naturals and <Letter>S<Octave> for sharps, with
C4 = 60 (middle C), the convention Ableton, Logic, and Reaper use:
import subsequence.constants.midi_notes as notes
print(notes.C4) # middle C
print(notes.C2) # two octaves down — bass register
print(notes.AS2) # A-sharp 2 (the 'S' is sharp)
print(notes.G2)
60
36
46
43
So the walking line from §3.3 — C2, C2, G2, AS2, A2 — is just
36, 36, 43, 46, 45 spelled in a way you can read at a glance.
Note
Sharps use the S spelling: C♯4 is notes.CS4. There are no flat names — a flat
is the enharmonic sharp a step lower (D♭4 is notes.CS4). When in doubt, reach
for the sharp.
Durations live in subsequence.constants.durations, all measured in beats
where QUARTER is 1.0:
import subsequence.constants.durations as dur
print(dur.SIXTEENTH, dur.EIGHTH, dur.QUARTER, dur.HALF)
print(dur.DOTTED_EIGHTH) # 0.75 — an eighth and a half
print(4 * dur.SIXTEENTH) # four sixteenths = one beat
0.25 0.5 1.0 2.0
0.75
1.0
Now the bassline reads in plain musical language — register in note names, lengths in note values:
import subsequence
import subsequence.constants.midi_notes as notes
import subsequence.constants.durations as dur
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=1, beats=4)
def named_bass(p):
p.note(notes.C2, beat=0, duration=dur.QUARTER)
p.note(notes.G2, beat=2, duration=dur.EIGHTH)
p.note(notes.A2, beat=3, duration=dur.DOTTED_EIGHTH)
composition.render(bars=4, filename="named-bass.mid")
Tip
Multiplying a constant by a count expresses odd lengths clearly:
9 * dur.SIXTEENTH is “nine sixteenths” (2.25 beats) without you ever computing
the decimal. The constants are ordinary numbers, so you can do arithmetic on them
freely.
3.6 Legacy aside: mini-notation¶
Note
This section documents an older shorthand you may meet in other people’s code. It is not the path this guide teaches. Skip it on a first read — nothing later depends on it.
Some sequencers describe rhythm with a compact mini-notation string, and
Subsequence keeps one for familiarity: p.seq. A space-separated string is spread
evenly across the bar, ~ or . is a rest, [a b] subdivides a slot, and _
extends the previous note:
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def mini_drums(p):
p.seq("kick_1 . snare_1 .") # kick on 1, snare on 3, rests between
composition.render(bars=2, filename="mini.mid")
The string "kick_1 . snare_1 ." reads as four equal slots: kick, rest, snare,
rest. It’s terse, and for a quick rhythmic sketch it can feel fast.
Warning
Why we don’t build on it. A string is opaque to Python — you can’t loop over
it, vary one value, pull a pitch from the current chord, or feed it a generated
list. The standard form in this guide is plain Python lists (p.sequence,
p.hit, p.note), because the API is the instrument: lists are values you can
compute, transform, and reuse. Everything from here on uses them, and so should
you. p.seq is shown once, here, and never again.
The same two-bar idea as a list — the form the rest of the guide uses — is simply:
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def list_drums(p):
p.hit_steps("kick_1", [0], velocity=100)
p.hit_steps("snare_1", [8], velocity=90)
composition.render(bars=2, filename="list.mid")
You can now place pitched notes at any beat, shape their length and articulation, and write a bassline in plain musical names alongside the drums. Next we hand the rhythm-making over to generators — single numbers that bloom into whole Euclidean and spread patterns — so you describe the feel and let Subsequence fill in the grid.