Sections and form
By the end of this chapter, the piece has a shape: a four-bar intro of pad and hi-hats, a build where the kit, bass and melody come in, a drop with everything, and a breakdown that pulls back, with the drums falling out for the bar before the drop and a tom fill at the end of each section.
The piece so far
The setup from Motifs and phrases, without its render. Its patterns are left out, because this chapter rewrites every one of them.
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
import subsequence.constants.midi_notes as notes
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)
A form
So far every pattern has played in every bar. composition.form gives the piece sections, in order:
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)
- Each
Sectionhas a name, a length in bars, and an energy from 0 to 1: how much is going on in it. - The four sections run in order, 24 bars in all, and
loop=Truestarts them again when they end, for as long as the piece plays. - A
Formholds the sections as a value, as a motif holds notes.
Out for a bar before the drop
A drop hits harder after a moment without the beat. composition.transition mutes patterns, by name, for the bar before a section starts, and brings them back when it does:
composition.transition(before="drop", mute=["drums", "hats"])
Patterns that know where they are
Every pattern now takes min_energy=: it plays only in sections whose energy is at least that much, and in the rest Subsequence does not run it at all. This is the kit, rewritten with it:
@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")
p.groove(swing)
@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=90)
p.groove(swing)
@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=5, velocity=60).rotate(2).groove(swing)
@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)
The fill has changed too. It used to play on the last of every four bars. Now it asks p.section, which describes the section the bar is in: its name, its bars, which bar of them this is, counted from 0, and its energy. p.section.last_bar is True on a section's last bar, so the fill ends each section, whatever its length.
The pitched parts, and a render of the whole form:
@composition.pattern(channel=3, beats=4, voice_leading=True)
def pad (p, chord):
p.chord(chord, root=notes.E3, sustain=True, velocity=70)
@composition.pattern(channel=2, beats=4, min_energy=0.5)
def bass (p, chord):
p.arpeggio(chord, root=notes.E1, count=4, spacing=0.25)
p.groove(swing)
@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.render(bars=24, filename="sections-and-form.mid")
In piece.py, put the form and the transition below answered, and replace every pattern with these.
The pad has no min_energy, so it plays everywhere. Together, the thresholds and the energies decide the arrangement:
| Pattern | min_energy |
Intro, 0.2 | Build, 0.5 | Drop, 1.0 | Breakdown, 0.3 |
|---|---|---|---|---|---|
pad |
none | plays | plays | plays | plays |
hats |
0.2 | plays | plays | plays | plays |
melody |
0.3 | plays | plays | plays | |
drums, fill, pedal_hats, bass |
0.5 | plays | plays | ||
euclidean_snare, hook_line |
1.0 | plays |
What the form made
The first bar of the intro has closed hi-hats and no kick, and the kick arrives with the build, in bar 5:
MIDI channel 10
1 1.000 note F#2 (42) velocity 40 length 0.083
5 1.000 note C2 (36) velocity 110 length 0.083
In bar 12, the last of the build, the drums and closed hi-hats are muted, and the kit is down to the pedal hi-hat and the tom fill:
12 2.000 note G#2 (44) velocity 80 length 0.083
12 4.000 note D3 (50) velocity 100 length 0.083
12 4.292 note C3 (48) velocity 90 length 0.083
The drop starts in bar 13 with everything, and the hook comes in on MIDI channel 5:
MIDI channel 5
13 1.000 note B5 (83) velocity 100 length 1.000
More on form
- A form can also be a graph, where each section says which can follow it and how likely each is.
composition.form_freezeturns one walk of such a graph into a fixedFormyou can edit. composition.section_chordsgives a section a chord progression of its own.p.energyreads the current section's energy inside a pattern, to play more or less rather than on or off.