Ghost fills
By the end of this chapter, the beat sounds played rather than programmed: quiet snare notes fall around the backbeat, in different places from bar to bar, and in the same places every time the piece is rendered.
The piece so far
The setup from A first beat. The drum pattern is left out, because this chapter rewrites it.
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(bpm=120, seed=1)
Ghost notes
A drummer fills the space around the backbeat with ghost notes: soft snare strokes, felt more than heard. p.ghost_fill adds them. Here is the drum pattern with one line more, rendered for four bars:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums (p):
p.hit_steps("kick", [0, 4, 8, 12], velocity=110)
p.hit_steps("snare", [4, 12], velocity=100)
p.hit_steps("hi_hat_closed", range(16), velocity=70)
p.ghost_fill("snare", density=0.5, velocity=(20, 40), bias="before")
composition.render(bars=4, filename="ghost-fills.mid")
In piece.py, replace the drum pattern with this one.
p.ghost_fill chooses at random which steps of the grid get a quiet snare:
bias="before"says where the ghost notes like to fall: most often on the last sixteenth note before a beat, less often on the other sixteenth notes, and hardly ever on a beat itself.density=0.5says how many: about half of the steps where the bias is strongest, and fewer elsewhere.velocity=(20, 40)is a range rather than one velocity. Two numbers in round brackets, which Python calls a tuple, stand for everything from the first to the second, and each ghost note draws its own velocity from that range.- A ghost note never lands on a step where the snare already plays.
Different every bar, the same every run
The pattern runs again each time it comes round, and each time p.ghost_fill chooses again, so every bar has ghost notes of its own. Here are the snares of the first two bars:
1 1.750 note D2 (38) velocity 34 length 0.083
1 2.000 note D2 (38) velocity 100 length 0.083
1 3.750 note D2 (38) velocity 26 length 0.083
1 4.000 note D2 (38) velocity 100 length 0.083
1 4.750 note D2 (38) velocity 26 length 0.083
2 2.000 note D2 (38) velocity 100 length 0.083
2 2.500 note D2 (38) velocity 33 length 0.083
2 3.750 note D2 (38) velocity 24 length 0.083
2 4.000 note D2 (38) velocity 100 length 0.083
The choices are random, and the composition's seed makes them. Render the piece again and every ghost note lands where it did before, at the same velocity. That is what makes a generative beat something you can keep: the build that checks this page renders each example twice, and fails if a single note moves.
Another take
A different seed is a different take. Here is the same pattern in a composition of its own, called another_take, with seed=3:
another_take = subsequence.Composition(bpm=120, seed=3)
@another_take.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums (p):
p.hit_steps("kick", [0, 4, 8, 12], velocity=110)
p.hit_steps("snare", [4, 12], velocity=100)
p.hit_steps("hi_hat_closed", range(16), velocity=70)
p.ghost_fill("snare", density=0.5, velocity=(20, 40), bias="before")
another_take.render(bars=4, filename="another-take.mid")
Its first bar has ghost notes of its own:
1 2.000 note D2 (38) velocity 100 length 0.083
1 2.250 note D2 (38) velocity 32 length 0.083
1 3.500 note D2 (38) velocity 31 length 0.083
1 3.750 note D2 (38) velocity 21 length 0.083
1 4.000 note D2 (38) velocity 100 length 0.083
1 4.500 note D2 (38) velocity 38 length 0.083
Try a few seeds, and keep the number of the take you like. The piece carries on with seed=1.
The other way round
p.thin does the opposite of p.ghost_fill: it removes notes a pattern has already placed, choosing by where they fall in the beat, and it takes the same names for where. p.thin("hi_hat_closed", "sixteenths", amount=0.5) thins out the closed hi-hats between the eighth notes.