Subsystem

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:

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.