# 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. ```{testsetup} ch3 # Hidden per-chapter setup: the imports the first example shows in full, so # later blocks in this chapter can use `notes` and `dur` without repeating them. import subsequence import subsequence.constants.instruments.gm_drums as gm_drums import subsequence.constants.midi_notes as notes import subsequence.constants.durations as dur ``` (sec-pnote)= ## 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: ```python 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](#sec-constants). - **`beat`** — *where in the bar it starts*, counted in beats from `0.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 to `0.25` (a sixteenth note). `1.0` is a full quarter note; `0.5` an eighth; `2.0` a 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. ```{testcode} ch3 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=1`** sends 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=4`** makes the pattern four beats long — one bar of 4/4. - **`notes.C2`** is the named constant for the MIDI note C2; we'll unpack the naming in [§3.5](#sec-constants). For now read it as "the C two octaves below middle C," a comfortable bass register. - Each `p.note(...)` places exactly one note. The `beat=` 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. ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.pattern_builder.PatternBuilder.note` ``` (sec-beats-vs-steps)= ## 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.0` is the downbeat; beat `1.5` is the "and" of beat 2; beat `2.25` is the first sixteenth after beat 3. Beats are what `p.note`, `p.hit`, and `p.repeat` speak. - 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 what `p.hit_steps` and `p.sequence` speak. 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) ```{list-table} The same eight moments, named two ways :header-rows: 1 :widths: 20 20 60 * - Step (0–15) - Beat (0.0–) - In words * - `0` - `0.0` - downbeat (beat 1) * - `2` - `0.5` - the "and" of beat 1 * - `4` - `1.0` - beat 2 * - `6` - `1.5` - the "and" of beat 2 * - `8` - `2.0` - beat 3 * - `12` - `3.0` - beat 4 * - `14` - `3.5` - the "and" of beat 4 * - `15` - `3.75` - 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. ``` (sec-bassline)= ## 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. ```python 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: ```{testcode} ch3 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)`). ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.pattern_builder.PatternBuilder.sequence`, {py:func}`~subsequence.sequence_utils.tile` ``` (sec-durations)= ## 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: ```{testcode} ch3 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: ```{testcode} ch3 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: ```{testcode} ch3 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: ```{testcode} ch3 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") ``` ```{list-table} The three articulation verbs :header-rows: 1 :widths: 22 78 * - Verb - What it does to every placed note * - `p.duration(beats)` - Sets each note to one fixed length, ignoring what comes next. * - `p.detached(gap)` - Shrinks each note to leave at least `gap` beats of silence before the next. * - `p.legato(ratio)` - Resizes each note to fill the gap to the next (`ratio=1.0` = fully connected; less = a fraction of the gap). ``` ```{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. ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.pattern_builder.PatternBuilder.repeat`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.hit`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.duration`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.detached`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.legato` ``` (sec-constants)= ## 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 `` for naturals and `S` for sharps, with **C4 = 60 (middle C)**, the convention Ableton, Logic, and Reaper use: ```{testcode} ch3 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) ``` ```{testoutput} ch3 60 36 46 43 ``` So the walking line from [§3.3](#sec-bassline) — `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`: ```{testcode} ch3 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 ``` ```{testoutput} ch3 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: ```{testcode} ch3 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. ``` (sec-mini-notation)= ## 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: ```{testcode} ch3 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: ```{testcode} ch3 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.