songcode

How to read a track

Five stems of techno, taken apart one at a time. No Strudel experience needed.

under the hood4 min read

Here is a complete loop. It is the kind of thing songcode writes for “driving techno. rolling bass, metallic hats, a single dark stab.”

// title: iron oxide
setcpm(130/4)
form('loop:8')

kick: s("bd*4").bank("RolandTR909").gain(.9)
  .duckorbit(2).duckdepth(.7).duckattack(.12)

hats: s("hh*16").bank("RolandTR909")
  .gain("[.34 .13 .2 .15]*4").hpf(420).degradeBy(.15)

clap: s("~ cp ~ cp").bank("RolandTR909").late(.008).room(.3).gain(.42)

bass: note("c1*16").s("sawtooth")
  .lpf(sine.range(320, 1500).slow(8)).lpq(7)
  .decay(.08).sustain(0).orbit(2).gain(.6)

stab: note("<[c3,eb3,g3] ~ ~ [bb2,d3,f3]>").s("square").hpf(330)
  .room(.45).delay(.3).delaytime(.375).gain(.4)

You can read all of it in about five minutes. Three ideas carry most of the weight.

The three ideas

A cycle is a bar. The pattern language underneath, Strudel, counts time in cycles. setcpm(130/4) sets 130 beats per minute with four beats to a cycle, so one cycle is one bar of 4/4. form('loop:8') says the song is one section, eight bars long, that repeats.

A string is a bar of rhythm. Whatever sits inside double quotes is spread evenly across one cycle. "bd sd" is two hits, half a bar each. A few symbols bend that:

  • * repeats: "bd*4" is four kicks in the bar.
  • ~ is a rest.
  • [ ] groups steps into the space of one.
  • < > plays one item per bar, in turn.
  • , inside a group stacks notes into a chord.

Each stem is one instrument, and the dots are a signal chain. A stem starts with its name and a colon. Read it left to right the way you would follow a cable: what plays, then everything done to it. A chain can wrap onto the next line.

The kick

kick: s("bd*4").bank("RolandTR909").gain(.9)
  .duckorbit(2).duckdepth(.7).duckattack(.12)

s("bd*4") plays a bass drum four times a bar. .bank("RolandTR909") picks the 909’s kick. .gain(.9) sets the level.

The second line is the sidechain. .duckorbit(2) says: every time this kick hits, turn down everything in group 2. .duckdepth(.7) is how far down, and .duckattack(.12) is how long the level takes to come back. Hold that thought until the bass.

The hats

hats: s("hh*16").bank("RolandTR909")
  .gain("[.34 .13 .2 .15]*4").hpf(420).degradeBy(.15)

Sixteen closed hats per bar. The gain is a pattern too: four levels, repeated four times, so every beat goes loud, quiet, medium, quiet. That accent shape is most of what separates a groove from a grid.

.hpf(420) is a highpass filter at 420 Hz, which keeps the hats out of the low end. .degradeBy(.15) drops about 15 percent of the hits at random. The randomness is a function of time, so the same hits drop on every playback.

The clap

clap: s("~ cp ~ cp").bank("RolandTR909").late(.008).room(.3).gain(.42)

Rest, clap, rest, clap: beats two and four. .late(.008) pushes the clap back by eight thousandths of a cycle, which at this tempo is about 15 milliseconds. You would not notice it as late. You would notice the track feeling stiffer without it. .room(.3) adds reverb.

The bass

bass: note("c1*16").s("sawtooth")
  .lpf(sine.range(320, 1500).slow(8)).lpq(7)
  .decay(.08).sustain(0).orbit(2).gain(.6)

Sixteen C1s per bar on a sawtooth oscillator. .decay(.08).sustain(0) makes each note a short pluck.

The interesting part is the filter. .lpf() usually takes a number. Here it takes sine.range(320, 1500).slow(8): a sine wave that travels between 320 and 1,500 Hz and takes eight bars to complete, exactly the length of the loop. The bass opens up and closes down once per pass while the notes never change. .lpq(7) adds resonance so you can hear the filter move.

.orbit(2) puts the bass in group 2, which is what the kick is ducking. Four times a bar the bass dips out of the kick’s way and swells back. That is the pump.

The stab

stab: note("<[c3,eb3,g3] ~ ~ [bb2,d3,f3]>").s("square").hpf(330)
  .room(.45).delay(.3).delaytime(.375).gain(.4)

The angle brackets hold four items, so this pattern takes four bars. Bar one: C, E flat and G together, a C minor chord. Bars two and three: nothing. Bar four: a B flat major chord.

It plays on a square wave, highpassed, with reverb and delay. Every stem has a reverb and a delay to itself, so this tail does not wash over the drums.

Four edits to try

Generate a loop in songcode, paste this track over it in the editor, press play, and change one thing at a time.

  1. Break the kick. Replace "bd*4" with "bd(3,8)". That asks for three hits spread as evenly as possible over eight steps, and they land on steps one, four and seven.
  2. Speed up the filter. Change .slow(8) to .slow(4). The sweep now completes every four bars.
  3. Add a fill. Append .into('loop', 1, x => x.ply(2)) to the hats. In the last bar before the loop comes round, each hat plays twice.
  4. Fill in the chords. Change the stab to "<[c3,eb3,g3] [bb2,d3,f3]>". The two chords now alternate every bar.

Every one of these is a few characters. None of them needs a model.

The same track without the code

Above the editor is a switch: code, stems. The stems view shows this same text as a multitrack session. Each stem is a row with mute, solo and a level. Select one and its notes open in a piano roll or a drum grid, where you can draw and drag them.

Nothing is converted. The notes you draw are written back into the text, which stays the single source of truth. Levels, mutes and solos are a mix on top of it.

Reading what the model wrote

When songcode generates a track, this is the material you get back. A longer one has more stems and more variation, and a full track declares its sections up front and tells each stem where to play:

form('intro:16 build:8 main:32 break:16 rise:8 peak:32 outro:16')

kick: s("bd*4").bank("RolandTR909").gain(.9).except('break')
lead: n("0 3 7 10 7 3 5 ~").scale("c4:minor").s("supersaw").only('peak')

The kick plays everywhere but the break. The lead is held back until the peak.

Once you can read a track you can ask better questions of it. Why does the clap feel late? Find the stem, look for .late. Why is the mix muddy? Look for a pad with no .hpf. The answer is always somewhere on the screen.

For the full pattern language, Strudel’s own workshop is the place to go.

Describe a song.
Get it in code.

open songcode