Shaping over time
By the end of this chapter, the pad swells from quiet to full across the build, the closed hi-hats breathe in and out every four bars, and the tempo rises through the build into the drop and settles back in the breakdown.
The piece so far
Everything Sections and form built, without its render. The pad and hats 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=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=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=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)
Signals
A section switches parts on and off. Some things should change gradually instead, over many bars. The composition's conductor holds signals for that: named values that change over time, which any pattern can read.
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)
linemakes a signal that moves from one value to another:"swell"stays at 40 until beat 16, the start of the build in bar 5, rises to 90 over the 32 beats of the build, and stays at 90 from then on. A line runs once, counted from the start of the piece, so when the form comes round again the swell stays at 90.lfomakes a signal that goes round and round, as a low-frequency oscillator does on a synth:"breath"follows a sine wave between 60 and 100, once every 16 beats, which is four bars.
Reading a signal
p.signal reads a signal at the start of the bar a pattern is building. The pad and the hi-hats, rewritten to read them:
@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)
int turns a signal's value, which can have a fraction, into the whole number a velocity has to be. The pad now plays at the swell's value, and the hi-hats' loudest notes reach the breath's.
Because a signal is read once a bar, an LFO needs a cycle of several bars to be heard moving. Read at the start of each bar, a sine wave four bars long gives 80, 100, 80 and 60 in turn. One a bar long would give the same value every bar.
The tempo, section by section
composition.target_bpm moves the tempo gradually to a new one over a number of bars. To start that at the right moment, give composition.on_section a function of your own, which Subsequence calls each time a section begins:
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)
composition.render(bars=24, filename="shaping-over-time.mid")
In piece.py, put the two signals and tempo with its on_section line below the transition, and replace pad and hats with these.
def tempo (section):defines a function, as a pattern is, but one that is not a pattern:on_sectioncalls it with the section that is starting, and withNonewhen a form that does not loop has ended.if ... elif ...checks the first condition, and only if that is false, the second.- The build ramps the tempo up to 126 over its eight bars, and the breakdown brings it back to 120 over its four. Subsequence calls the function a beat before the section, so each ramp starts on the last beat of the section before.
What changed over time
The tempo, as the file states it: 120 at the start, a ramp up to 126 across the build, and a ramp back down in the breakdown.
tempo 120 bpm from 1 1.000
tempo 120.01 bpm from 4 4.042, ramping to 126 bpm at 12 4.000 in 768 changes
tempo 125.98 bpm from 20 4.042, ramping to 120 bpm at 24 4.000 in 384 changes
The pad's chords get louder bar by bar through the build, and stay at full strength in the drop:
MIDI channel 3
5 1.000 note E3 (52) velocity 40 length 4.000
7 1.000 note E3 (52) velocity 52 length 4.000
9 1.000 note E3 (52) velocity 65 length 4.000
11 1.000 note E3 (52) velocity 77 length 4.000
13 1.000 note C3 (48) velocity 90 length 4.000
More signals
lfoshapes include"triangle","saw"and"square"as well as"sine", andlinetakes ashape, such as"ease_in_out", to curve its rise.p.bar_cycle(n).progressandp.section.progressrise from 0 towards 1 across a cycle of bars or a section, for shaping that follows the form rather than the clock.