Patterns that listen to each other
By the end of this chapter, the drums measure how syncopated each bar of theirs is, and the bass listens: when the drums are busy on the weak sixteenth notes, it plays one long root note a beat and leaves them room, and when they are plain, it plays its full arpeggio.
The piece so far
Everything Shaping over time built, without its render. The drums and bass patterns are left out, because this chapter rewrites 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)
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=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)
@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=3, beats=4, voice_leading=True)
def pad (p, chord):
p.chord(chord, root=notes.E3, sustain=True, velocity=int(p.signal("swell")))
@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)
Shared data
Patterns so far have known about the piece, its bar, its section and its chord, but not about each other. p.data is a place any pattern can write to and any pattern can read from: one dictionary, shared by the whole composition, which Subsequence also offers as composition.data. A dictionary holds values under names, written p.data["name"] = value and read p.data["name"].
When two patterns are the same length, the one defined first builds first in every bar, so the one defined after it reads what it wrote in that bar. Here the drums write, so they are defined first.
Measuring a rhythm
subsequence.sequence_utils holds measures of rhythm. syncopation scores a list of steps by how far they sit from the strong beats: a note on the downbeat adds nothing, and a note on one of the weakest sixteenth notes adds the most, and the score is the average over the notes. The drums, rewritten to measure themselves and share the score:
import subsequence.sequence_utils
@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)
p.placedlists every note the pattern has placed so far this bar. Each note'spositionis counted in Subsequence's 24 divisions of a beat, six to a sixteenth note.[note.position // 6 for note in p.placed()]makes a list with one value for each placed note: its position divided by six, and//drops any remainder, so the value is the note's step on the grid.p.gridis how many steps the pattern has, 16.- It measures before the groove moves any notes.
With the kick and snare alone, the score is about a third. Each ghost note on a weak sixteenth note pulls it up, so a bar full of ghost notes scores more than half.
A bass that leaves room
The bass reads the score and decides how to play:
@composition.pattern(channel=2, beats=4, min_energy=0.5)
def bass (p, chord):
if p.data.get("drum_syncopation", 0.0) > 0.5:
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)
composition.render(bars=24, filename="patterns-that-listen.mid")
In piece.py, put the sequence_utils import with the others, and replace drums and bass with these, drums before bass.
p.data.get("drum_syncopation", 0.0)reads the score, or 0.0 if nothing has written one yet.elseruns its lines when theifabove it was false.- In a busy bar,
count=1andspacing=1make the arpeggio one note, the chord's root, played once a beat. In a plain bar, it is the four-note arpeggio as before.
What the bass did
The drums come in with the build in bar 5, and their first bar has ghost notes on the last sixteenth note before beat 2, before beat 4 and before the next bar, three of the weakest places in a bar, which scores more than half:
MIDI channel 10
5 1.792 note D2 (38) velocity 34 length 0.083
5 2.000 note D2 (38) velocity 100 length 0.083
5 3.792 note D2 (38) velocity 26 length 0.083
5 4.000 note D2 (38) velocity 100 length 0.083
5 4.792 note D2 (38) velocity 26 length 0.083
So in bar 5 the bass plays a root a beat. Bar 6 is plainer, and the arpeggio comes back:
MIDI channel 2
5 1.000 note E1 (28) velocity 100 length 1.000
5 2.000 note E1 (28) velocity 100 length 1.000
5 3.000 note E1 (28) velocity 100 length 1.000
5 4.000 note E1 (28) velocity 100 length 1.000
6 1.000 note G1 (31) velocity 100 length 0.250
6 1.292 note B1 (35) velocity 100 length 0.250
6 1.500 note D2 (38) velocity 100 length 0.250
6 1.792 note G2 (43) velocity 100 length 0.250
Other measures
subsequence.sequence_utils has other measures of a line, described in its own docstrings rather than in the API reference: rhythmic_evenness scores how evenly a rhythm's notes are spread around the bar, offbeatness counts the notes on off-beat pulses, and vl_distance measures how far the voices move between two chords.