Subsystem Search

Chords that move

By the end of this chapter, a pad plays a chord progression that Subsequence chooses as the piece goes, a chord a bar, and the bass line plays the notes of whichever chord is sounding.

The piece so far

Everything An arpeggiated bass line built, without its render. The bass pattern is left out, because this chapter rewrites it.

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.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.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)
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)
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)
def fill (p):
	if p.bar_cycle(4).last:
		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)
def pedal_hats (p):
	p.hit_steps("hi_hat_pedal", [0], velocity=80)

A harmony engine

composition.harmony starts Subsequence choosing chords. It walks a chord graph: a map, for one style of music, of which chords tend to follow which, and how strongly. At each change it picks the next chord from the ones that can follow the current one, and by default it changes once a bar.

To hear what it chooses before giving it to the piece, here is a composition of its own, called listening, whose one pattern only prints the chord of each bar:

listening = subsequence.Composition(bpm=120, key="E", scale="minor", seed=1)
listening.harmony(style="aeolian_minor")

@listening.pattern(channel=3, beats=4)
def chord_names (p, chord):
	print("bar", p.bar + 1, chord.name())

listening.render(bars=8, filename="listening.mid")

A pattern whose function takes a second parameter called chord is given the chord sounding in its cycle, and chord.name() is that chord's name. p.bar + 1 counts bars from 1, as a musician does.

bar 1 Em
bar 2 D
bar 3 C
bar 4 D
bar 5 C
bar 6 B
bar 7 B7
bar 8 Em

"aeolian_minor" is a style built on the natural minor scale. B and B7, in bars 6 and 7, are the major chord on the scale's fifth note, whose D sharp the natural minor does not have, and they pull back to E minor, which bar 8 goes to. Other styles include "dorian_minor", "phrygian_minor" and "functional_major", and the reference lists them all. The seed makes the choice, so the same seed chooses the same chords every time.

A pad, and a bass that follows

Now the piece gets the harmony, a pad on MIDI channel 3, and the bass rewritten to follow the chords:

composition.harmony(style="aeolian_minor")

@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)
def bass (p, chord):
	p.arpeggio(chord, root=notes.E1, count=4, spacing=0.25)
	p.groove(swing)

composition.render(bars=4, filename="chords-that-move.mid")

In piece.py, put the harmony line below the composition, replace the bass with this one, and add the pad. Give a pad instrument MIDI channel 3.

The first two bars, the bass's first beat and the pad's chord, as the engine chose them above, E minor and then D:

MIDI channel 2
    1  1.000  note E1 (28)  velocity 100  length 0.250
    1  1.292  note G1 (31)  velocity 100  length 0.250
    1  1.500  note B1 (35)  velocity 100  length 0.250
    1  1.792  note E2 (40)  velocity 100  length 0.250
    2  1.000  note D1 (26)  velocity 100  length 0.250
    2  1.292  note F#1 (30)  velocity 100  length 0.250
MIDI channel 3
    1  1.000  note E3 (52)  velocity 70  length 4.000
    1  1.000  note G3 (55)  velocity 70  length 4.000
    1  1.000  note B3 (59)  velocity 70  length 4.000
    2  1.000  note D3 (50)  velocity 70  length 4.000
    2  1.000  note F#3 (54)  velocity 70  length 4.000
    2  1.000  note A3 (57)  velocity 70  length 4.000

A progression you write

The engine is one way to have chords. The other is to write them. subsequence.progression takes a list, and a number in it is a chord built on that note of the scale. Given to harmony as progression=, it plays in order, a bar a chord, and starts again when it runs out. In a composition of its own, called as_written:

as_written = subsequence.Composition(bpm=120, key="E", scale="minor", seed=1)
as_written.harmony(progression=subsequence.progression([1, 6, 3, 7]))

@as_written.pattern(channel=3, beats=4)
def chord_names (p, chord):
	print("bar", p.bar + 1, chord.name())

as_written.render(bars=4, filename="as-written.mid")
bar 1 Em
bar 2 C
bar 3 G
bar 4 D

In E minor, the first note of the scale is E, the sixth C, the third G and the seventh D. A pattern that takes chord works the same with either, so the pad and the bass above would follow a written progression as they follow the engine.

Also for harmony: