A melody that remembers
By the end of this chapter, a melody moves over the chords in eighth notes, leaping and then turning back, stepping and then carrying on, as a singer's line does, with a breath here and there.
The piece so far
Everything Chords that move built, without its render. This chapter adds a pattern and changes none.
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)
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)
A line that remembers
Every pattern so far has decided its notes knowing only the moment it runs in: the bar, the cycle, the chord. A melody needs more than that. Whether its next note should rise or fall depends on the notes before it, including the notes at the end of the last bar, and the function that builds each bar starts fresh every time.
So the memory is kept outside the function, in a MelodicState, made once:
melody_state = subsequence.MelodicState(low=notes.E4, high=notes.E5, rest_probability=0.2)
@composition.pattern(channel=4, beats=4)
def melody (p, chord):
p.melody(melody_state, spacing=0.5, chord_tones=chord.tones(notes.E4))
composition.render(bars=4, filename="a-melody-that-remembers.mid")
In piece.py, put melody_state above the patterns and add melody below them. Give a lead instrument MIDI channel 4.
melody_stateis made outside the pattern, so there is one of it for the whole piece, and it keeps the last notes the melody played from one bar to the next. Made inside the function, it would be made again every bar and remember nothing of the bar before.lowandhighkeep the melody between E4 and E5, above the pad. It takes its key and scale from the composition.rest_probability=0.2gives each note a one-in-five chance of being a rest instead, so the line breathes.p.melodyplaces a note or a rest everyspacingbeats, choosing each note with the state's help.chord.tones(notes.E4)is the bar's chord as notes near E4. The melody favours them, so it agrees with the pad.
How it chooses
The state scores every note it could play next, using a model of how listeners expect a melody to go: Eugene Narmour's implication-realisation model, set out in his 1990 book The Analysis and Cognition of Basic Melodic Structures. Two of its expectations do most of the work. After a large leap, a listener expects the line to turn back. After a small step, they expect it to carry on the same way. The state also avoids the same note too many times in a row, and favours the notes of the chord.
The first bar leaps and turns back:
MIDI channel 4
1 1.000 note E5 (76) velocity 90 length 0.167
1 2.000 note G4 (67) velocity 90 length 0.167
1 2.500 note E5 (76) velocity 90 length 0.167
1 3.500 note B4 (71) velocity 90 length 0.167
1 4.000 note C5 (72) velocity 90 length 0.167
E5 falls a major sixth to G4, and the line turns straight back up. The rests fall where no note is listed: after the first E5, and after the second. By the third bar the line has settled into steps, rising and falling a note or two at a time:
3 1.000 note G4 (67) velocity 90 length 0.167
3 1.500 note A4 (69) velocity 90 length 0.167
3 2.000 note G4 (67) velocity 90 length 0.167
3 2.500 note E4 (64) velocity 90 length 0.167
3 3.000 note F#4 (66) velocity 90 length 0.167
Other melodic generators
Each has its entry in the reference:
p.markovwalks a Markov chain, choosing each note by the chances you give for what follows the note before.p.lsystemgrows a sequence by rewriting a string of symbols by rules, as a plant branches.p.self_avoiding_walkwalks a list of pitches mostly a step at a time, never coming back to a pitch within three notes, and carries on from bar to bar where the last one ended.p.recamanplays Recamán's sequence, which steps back when it can and forward when it cannot, by a step one longer each time.p.de_bruijnworks through every short run of its pitches.p.lorenzfollows the Lorenz attractor, which never quite repeats its path.p.branchmakes variations by walking a tree of transformations.p.evolveloops a sequence of pitches that changes a little each cycle.