song.jsannotatedsong.jssource342 lines · 21.6 KB · raw

SNEK HISS — house, 124bpm, 72 bars as written; Jev arranges up to 10 sections. Spec: SPEC.md Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count).

3setcps(124/60/4)
4await samples('github:tidalcycles/dirt-samples')
5await samples('jev-samples/strudel.json')

── Jev: two of them, in TypeSafe's own shape (SPEC.md, "Jev in the booth") ── formJev picks the next section; djJev, asked once that is decided (questions in one request cannot see each other), says how much of each part plays it, how the song moves into it, and how hard. Every 8 bars. The form's rules (walk, at the bottom) narrow the sections Jev may pick and end the song; without Jev it plays as written.

13const song = {
14  title: 'SNEK HISS',
15  what: 'house about the almost metallic hiss a listener hears between 3 and 8 kHz in AI-made music, like a snake. The song blames a codec picking tokens one at a time, hunts the snake band by band, finds it, and fixes it as TQCodec does (a 2026 music streaming codec that found its own quantizer noisy in that band); then it takes the other road, WanSong (a 2026 song generator on continuous tokens, with no codebook to pick from), until the last chorus is clean. That the band is why generated music hisses is the song\'s claim, not the papers\'',
16  tempo: '124 bpm, 8 bars a section',
17  key: 'A minor',
18  chords: 'Am Am F F C C G G, two bars each, a plucked sixteenth-note arpeggio; the song ends on Am',
19  sound: 'until the fix the band is bit-crushed (the codec): rough through chorus 1, half fixed in verse 2, clean from the flow section on. The snake never is: it is band-limited noise, 3 to 8 kHz, with ringing edges',
20  lyrics: {
21    verse1: 'One token at a time / Pick a code from the book / Every pick, a little off / And something starts to hiss',
22    hunt: 'Check it, band by band / Three to eight kilohertz / (the band drops out, the snake alone) That\'s a snake',
23    chorus1: 'Three to eight kilohertz / Almost metallic / Like a snake\'s hiss / (the snake answers)',
24    verse2: 'Freeze the codebook / A linear projection / Keep the mid band detail / Residual sim V Q',
25    flow: 'Continuous tokens / Flow matching / From noise, to a song / Five minutes long',
26    stems: 'Two stems, one run (sung alone) / (the backing alone) / The vocals, and the backing',
27    chorus2: 'Three to eight kilohertz / Nothing metallic / No snake',
28  },
29}
31const formJev = jev({
32  state: song,
33  questions: {
34    section: choice('Pick the section of the song that plays next, from those allowed. The story runs one way: the hiss, the hunt, the fix, the clean chorus. The outro ends the song: choose it once the clean chorus has made its point', {
35      intro: 'the crushed arpeggio alone, the snake creeping in, then the kick',
36      verse1: 'verse 1: the codec picks tokens one at a time over the crushed band, the snake under everything',
37      hunt: 'the hunt, in two halves. First: Check it, band by band / Three to eight kilohertz, while a band-pass steps up through the band, one band a bar. Then the band stops: only the kick and the snake, alone and loud, and That\'s a snake',
38      chorus1: 'chorus 1, still crushed: Three to eight kilohertz / Almost metallic / Like a snake\'s hiss, with the lead and sixteenth hats, and the snake answering with one long swell',
39      verse2: 'verse 2, the fix (TQCodec): Freeze the codebook / A linear projection. The crushing halves and the snake fades bar by bar',
40      flow: 'the other road (WanSong): Continuous tokens / Flow matching, with no codebook to pick from. Noise fades away under a pad that fades in; nothing is crushed from here on',
41      stems: 'two stems: the vocal sung alone for a bar, then the backing alone, then both, then the band',
42      chorus2: 'the clean chorus: Three to eight kilohertz / Nothing metallic / No snake, the lead up an octave and carrying the bars where the snake used to answer',
43      outro: 'the arpeggio and bass closing down to a last A minor chord, then one small hiss, alone: the end',
44    }),
45  },
46})

how much of each part the section decidedThisSection has: out, back or full

49const level = (part) => score('How much of ' + part + ' should the next section (decidedThisSection) have? Drop parts to build tension or make space, pull them back to let others speak, and bring them in full for the payoff; keep what defines the section. Where the song as written already stops a part for some bars, that happens by itself: the level is for the bars where the part plays', [
50  'out: silent for the section',
51  'back: under the rest of the mix',
52  'full: as written',
53])
54const djJev = jev({
55  state: song,
56  questions: {
57    keys: level('the keys (the plucked sixteenth-note arpeggio on the chords, the song\'s bed, in every section; in the flow section also the noise fading out and the pad fading in, and the pad stays under the stems and the clean chorus. The intro\'s first four bars, the arpeggio alone, are not part of this: they always play)'),
58    lead: level('the lead (the square-wave melody, only in the choruses, answering the sung lines; an octave up in the clean chorus)'),
59    bass: level('the bass (offbeat saw bass on the chord roots with a sine sub, from verse 1 on; silent with the rest of the band in the hunt\'s second half; out for the first half of the flow section and while the vocal is sung alone)'),
60    drums: level('the drums (four-on-the-floor kick from bar 5; clap on 2 and 4 and offbeat hats in the verses and choruses, and in the stems once the backing is in; sixteenth hats and a crash added in the choruses; out for the first half of the flow section and while the vocal is sung alone; in the hunt the kick keeps going alone under the snake)'),
61    // the sung lines are never out, so their question has no such level (SPEC.md, "The parts")
62    vocals: score('How loud should the vocoded lines be in the next section (decidedThisSection)? They are in every section but the intro and the outro and they carry the story, so they are never out: pull them back to let the band speak, or bring them in full. Two lines are not part of this and always play in full: That\'s a snake, in the hunt, and Two stems, one run, sung alone at the top of the stems section', [
63      'back: under the rest of the mix',
64      'full: as written',
65    ]),
66    snake: level('the snake under the band (the hiss itself: noise between 3 and 8 kHz with ringing, metallic edges, breathing in swells. It creeps in over the intro, sits under verse 1, the first half of the hunt and chorus 1\'s sung lines, and fades out across verse 2; it is gone after that. Where the snake is all a passage has, it is not part of this and always plays: alone in the hunt\'s second half, and its answer in chorus 1\'s last two bars)'),
67    into: choice('How should the song move into the section being entered (decidedThisSection)? It plays over the last bar before it. Pick what suits that section on its own merits; the moves before it are no reason to repeat or avoid one', {
68      asWritten: 'as written: a noise riser into chorus 1, a pad swelling up into the clean chorus, straight in to any other section',
69      fill: '808 toms rolling down across the second half of the bar',
70      riser: 'white noise swelling up across the whole bar, its high-pass rising',
71      dropout: 'everything stops for the last beat, held sounds too: a gap before the section hits',
72    }),
73    energy: score('How hard should the band play the section being entered (decidedThisSection)? It scales the whole band\'s velocity for that section. Judge that section by its place in the story: the choruses are the payoffs, and the clean chorus is the peak of the song. Then shape it by how the song has measured so far and how the listener reacted', [
74      'held back, 55% velocity: for a section that waits, like the intro, or closes down, like the outro',
75      'steady, 70%: for a verse, telling the story under the sung lines',
76      'driving, 85%: for a section that builds towards a payoff, like the hunt, the flow section and the stems',
77      'all out, full velocity as written: for a payoff, the choruses',
78    ]),
79  },
80})

a little chance in the form: the section is drawn from Jev's probabilities

83const { section } = formJev.every(8, { sample: 0.7 })

The transitions Jev may pick (SPEC.md, "The transitions"): none at the opening, where nothing is playing yet; no noise riser from the flow section on, nor into chorus 1, where as written already is one.

87const noRiserInto = ['chorus1', 'flow', 'stems', 'chorus2', 'outro']
88const dj = djJev.every(8, {
89  after: { section },
90  forget: ['into'],
91  allow: {
92    into: ({ section: entering, playingNow }) =>
93      !playingNow ? ['asWritten']
94        : noRiserInto.includes(entering) ? ['asWritten', 'fill', 'dropout']
95        : ['asWritten', 'fill', 'riser', 'dropout'],
96  },
97  fallback: { keys: 2, lead: 2, bass: 2, drums: 2, vocals: 1, snake: 2, into: 'asWritten', energy: 3 },
98})

A part at Jev's level: silent when out, otherwise its velocity scaled by the level. Each part has its own orbit, named for its question, so the meter hears it alone (measuredSound).

103const part = (name, pat) =>
104  pat.orbit(name).mask(dj[name].score.round().gt(0)).mul(velocity(dj[name].score.round().div(2)))

the sung lines: back (half velocity) or full, never out

106const sung = (pat) => pat.orbit('vocals').mul(velocity(dj.vocals.score.round().add(1).div(2)))

A dropout stops held sounds too: on a section's last bar, when the song drops out into the next section, a held note plays its cut form, which ends at the last beat. Only parts whose sections always have a next one use it (the snake, the pad): past the last section there is no answer to read.

112const dropsOut = dj.into.choice.early(1).fmap((c) => c === 'dropout')
113const stopping = (held, cut) => stack(
114  held.mask("<1!7 0>"),
115  held.mask("<0!7 1>").mask(dropsOut.fmap((d) => !d)),
116  cut.mask("<0!7 1>").mask(dropsOut),
117)

bars: 1-8 intro | 9-16 verse 1 | 17-24 hunt | 25-32 chorus 1 | 33-40 verse 2 41-48 flow | 49-56 stems | 57-64 chorus 2 | 65-72 outro (each mask = 72 steps)

121const inVerse1  = "<0!8 1!8 0!56>"
122const inHunt    = "<0!16 1!8 0!48>"
123const inChorus1 = "<0!24 1!8 0!40>"
124const inVerse2  = "<0!32 1!8 0!32>"
125const inFlow    = "<0!40 1!8 0!24>"
126const inStems   = "<0!48 1!8 0!16>"
127const inChorus2 = "<0!56 1!8 0!8>"
128const choruses  = "<0!24 1!8 0!24 1!8 0!8>"

the choruses are played harder than everything else: each measures at least 2 dB over the verse before it (SPEC.md, "Mix")

131const push      = "<.78!24 1!8 .78!24 1!8 .78!8>"

── the codec: what the band goes through until the fix ── rough (crushed, sample rate reduced) through chorus 1; the hunt's first four bars also step a band-pass up through the band, one band a bar, and its last four are silent (the snake alone); half fixed in verse 2; clean from the flow section on.

138const rough = "<1!16 0!8 1!8 0!40>"
139const scan  = "<0!16 1!4 0!52>"
140const half  = "<0!32 1!8 0!32>"
141const clean = "<0!40 1!32>"
142const codec = (p) => stack(
143  p.crush(5).coarse(4).mask(rough),
144  p.crush(5).coarse(4).bpf("<250 700 1600 3200>").bpq(1.5).mask(scan),
145  p.crush(8).coarse(2).mask(half),
146  p.mask(clean),
147)

two bars per chord: Am Am F F C C G G, as MIDI numbers

150const arp = "<[57 60 64 69 64 60 57 60]*2!2 [53 57 60 65 60 57 53 57]*2!2 [48 52 55 60 55 52 48 52]*2!2 [55 59 62 67 62 59 55 59]*2!2>"
151const pad = "<[57,60,64]!2 [53,57,60]!2 [55,60,64]!2 [55,59,62]!2>"
152const roots = "<33!2 29!2 36!2 31!2>"   // A1 F1 C2 G1
153const subs  = "<33!2 41!2 36!2 31!2>"   // the sine under them: its F an octave up (F1 is 43.7 Hz, under what small speakers play)

the lead answers each sung line: a pickup at the end of the line's bar, the phrase in the next a melody, not the chord again: stepwise, each phrase ending somewhere new. The clean chorus has a last phrase of its own, a held A (an octave up there).

157const lead1 = "<[~!6 72 74] [76 ~ 74 72 ~ 74 76 ~] [~!6 74 76] [77 ~ 76 74 ~ 72 ~ 76] [~!6 76 77] [79 ~ 77 76 ~ 74 72 ~] [~!6 71 72] [74@3 72 71 ~ 69 ~]>"
158const lead2 = "<[~!6 72 74] [76 ~ 74 72 ~ 74 76 ~] [~!6 74 76] [77 ~ 76 74 ~ 72 ~ 76] [~!6 76 77] [79 ~ 77 76 ~ 74 72 ~] [~!6 64 67] [69@4 67 64 ~ ~]>"
159const lead = stack(lead1.mask("<1!56 0!16>"), lead2.mask("<0!56 1!16>"))

In the stems section only bar 49 is the vocal alone: the backing comes in on bar 50.

162const keysOn  = "<0!4 1!44 0 1!21 0!2>"                 // from bar 5 (bars 1-4 always play, below); ends before the last chord
163const arpGain = "<.4!8 .35!8 .4!8 .45!8 .35!8 .08 .12 .18 .25 .32 .4 .45 .5 .45!8 .5!8 .45 .4 .35 .3 .25 .2 0 0>"
164const arpLpf  = "<600 800 1000 1300 1600 1900 2200 2500 2200!16 3000!8 2500!8 4000!16 5000!8 4000 3200 2400 1800 1200 800 500 500>"
165const padOn   = "<0!40 1!8 0 1!15 0!8>"                 // the flow, the stems once the backing is in, chorus 2
166const padGain = "<0!40 .04 .08 .12 .16 .2 .24 .26 .28 .22!24>"
167const leadOn  = "<0!24 1!6 0!26 1!8 0!8>"               // chorus 1 leaves its last two bars to the snake
168const leadUp  = "<0!56 12!16>"                          // the clean chorus is an octave up
169const bassOn  = "<0!8 1!32 0!4 1!4 0 1!21 0!2>"
170const drumsOn = "<0!4 1!36 0!4 1!4 0 1!21 0!2>"
171const clapOn  = "<0!8 1!8 0!8 1!16 0!9 1!15 0!8>"       // verses, choruses, the stems once the backing is in
172const crash   = "<0!24 1 0!31 1 0!15>"                  // where each chorus lands

the snake pulls back in the hunt's last bar (.8), so chorus 1 lands over it

174const snakeGain = "<0 0 .2 .3 .4 .5 .6 .7 .7!8 .8!4 1.6!3 .8 .7!6 1.5!2 .6 .5 .4 .3 .2 .15 .1 .05 0!32>"
175const snakeUnder = "<0!2 1!18 0!4 1!6 0!2 1!8 0!32>"    // under the band: bars 3-20, 25-30, 33-40
176const snakeAlone = "<0!20 1!4 0!48>"                    // the hunt's second half
177const snakeAnswers = "<0!30 1!2 0!40>"                  // chorus 1's last two bars
178const noiseGain = "<0!40 .5 .42 .34 .26 .18 .12 .07 .03 0!24>"
179const noiseLpf  = "<12000!40 12000 9000 7000 5000 3500 2500 1800 1200 12000!24>"

── parts ───────────────────────────────────────── The echoes are a dotted eighth at the song's tempo: superdough's default (delaysync 3/16). A delaytime is seconds, which fits one tempo only.

184const arpLine = codec(note(arp).s("sawtooth").decay(.12).sustain(0).lpf(arpLpf)
185  .delay(.25).delayfeedback(.3).room(.25).gain(arpGain).mul(gain(1.4)))
186const padNote = note(pad).s("supersaw").detune(.2).attack(.4).lpf(2400).room(.4).gain(padGain)
187const noise = s("white").attack(.05).sustain(1).lpf(noiseLpf).gain(noiseGain)

the last two bars play nothing here: the ending has the last chord

189const keys = stack(
190  arpLine.mask(keysOn),
191  // the flow: noise to song. Full-band noise closing down and fading, a pad fading in
192  stopping(noise.release(.1), noise.clip(.75).release(.05)).mask(inFlow),
193  stopping(padNote.release(.4), padNote.clip(.75).release(.05)).mask(padOn),
194)

bars 1-4, the arpeggio alone: all the song has there, so it always plays, on the keys' orbit

196const opening = arpLine.mask("<1!4 0!68>").orbit('keys')
198const leadPart = codec(note(lead.add(leadUp)).s("square").lpf(3200).attack(.005).decay(.2).sustain(.3)
199  .delay(.3).delayfeedback(.35).room(.4).gain(1).mask(leadOn))

offbeat saw bass with a sine sub under it

202const bass = codec(stack(
203  note(roots).struct("~ x ~ x ~ x ~ x").s("sawtooth").lpf(500).decay(.18).sustain(.2).gain(.5),
204  note(subs).struct("~ x ~ x ~ x ~ x").s("sine").decay(.2).sustain(.4).gain(.6),
205).mask(bassOn))
207const kit = stack(
208  codec(stack(
209    s("bd:3*4").gain(.6).mask(drumsOn),
210    s("~ cp ~ cp").room(.2).gain(.55).mask(clapOn),
211    // one hat on each offbeat eighth; the choruses add sixteenths around them
212    s("[~ linnhats]*4").gain(.3).mask(drumsOn),
213    s("linnhats*16").gain("[.2 .1 .14 .1]*4").mask(choruses),
214    s("hh27:1").gain(1.8).mask(crash),
215  )),
216  // the hunt's last four bars: the kick alone under the snake, outside the codec's silence
217  s("bd:3*4").gain(.6).mask(snakeAlone),
218)

the vocoded lines, one per 2 bars, each rendered on its chord and levelled to the same loudness. Until the fix they are crushed too, lightly, so every word still reads.

223const line = (names, where) => s(names).gain(1.95).room(.2).mask(where)
224const vox = stack(
225  line("<shtoken ~ shcode ~ shoff ~ shhiss ~>", inVerse1).crush(7),
226  line("<shband ~ shkhzf ~ ~ ~ ~ ~>", inHunt).crush(7),
227  line("<shkhza ~ shmetal ~ shlike ~ ~ ~>", inChorus1).crush(7),
228  line("<shfreeze ~ shlinear ~ shmid ~ shsimvq ~>", inVerse2).crush(9),
229  line("<shcont ~ shflow ~ shnoise ~ shfive ~>", inFlow),
230  line("<~ ~ ~ ~ shvb ~ ~ ~>", inStems),
231  line("<shkhza ~ shnometal ~ shnosnake ~ ~ ~>", inChorus2),
232)

Two lines always play in full, on the vocals' orbit: the hunt's punchline, over the snake alone, and the stem sung alone on bar 49.

236const found = line("<~ ~ ~ ~ shsnake ~ ~ ~>", inHunt).crush(7).orbit('vocals')
237const alone = line("<shstems ~ ~ ~ ~ ~ ~ ~>", inStems).orbit('vocals')

the snake: noise held between 3 and 8 kHz, both edges resonant so they ring (the metal), swelling once a bar; and a run of inharmonic tones inside the band (a codec's warble). It never goes through the codec: it is what the codec leaves behind.

243const hiss = (p) => p.hpf(3000).hpq(8).lpf(8000).lpq(8)
244const breath = (shape, release) => hiss(s("white").struct(shape)).sustain(1).release(release).gain(.5)
245const swell = stopping(breath("x@7 ~", .25).attack(.9), breath("x@6 ~@2", .05).attack(.9))
246const warble = freq("[3170 5870 4410 7350 3730 6610 4990 7910]*4").s("sine").decay(.03).sustain(0).pan(sine.fast(3)).gain(.1)

chorus 1's answer: one swell, a single note across both bars. It starts a bar before the section's last, so it reads the next section's transition two bars ahead; on a dropout it ends at the last beat.

250const dropsOutAfter = dj.into.choice.early(2).fmap((c) => c === 'dropout')
251const answer = stack(
252  breath("x@15 ~", .25).slow(2).attack(2.2).mask(dropsOutAfter.fmap((d) => !d)),
253  breath("x@14 ~@2", .05).slow(2).attack(2.2).mask(dropsOutAfter),
254)

under the band the snake is a part, at Jev's level

256const snake = stack(swell, warble).mul(gain(snakeGain)).mask(snakeUnder)

where the snake is all a passage has, it always plays, on the snake's orbit

258const snakeSolo = stack(
259  stack(swell, warble).mask(snakeAlone),
260  stack(answer, warble).mask(snakeAnswers),
261).mul(gain(snakeGain)).orbit('snake')

the end: the last chord on bar 71, gone by the end of its bar, then one small hiss alone on bar 72. It always lands, at its own level.

265const ending = stack(
266  note("[33,45,57,60,64,69]").s("sawtooth").lpf(2200).attack(.005).decay(1.9).sustain(0).room(.5).gain(.3).mask("<0!70 1 0>"),
267  s("bd:3").gain(.8).mask("<0!70 1 0>"),
268  hiss(s("white").struct("~ x@2 ~")).attack(.25).sustain(1).release(.2).gain(.45).mask("<0!71 1>"),
269).orbit('ending')
271const once = (p) => p.mask("<1!72 0!928>")   // play once, then silence

── the form, as rules in code: which sections may follow which ── at is where each starts as written; an empty next ends the song. The written order is always allowed, and is what plays without Jev. Nothing after the fix leads back before it (SPEC.md, "The form").

277const form = walk(section, {
278  max: 10,
279  sections: {
280    intro: { at: 0, next: ['verse1'] },
281    verse1: { at: 8, next: ['hunt'] },
282    hunt: { at: 16, next: ['chorus1'] },
283    chorus1: { at: 24, next: ['verse2'] },
284    verse2: { at: 32, next: ['flow'] },
285    flow: { at: 40, next: ['stems'] },
286    stems: { at: 48, next: ['chorus2'] },
287    chorus2: { at: 56, next: ['outro', 'chorus2'], times: 2 },
288    outro: { at: 64, next: [] },
289  },
290})

── the song: Jev's section, parts and energy, composed ── Each section plays its own bars, each part at the level Jev chose; what a passage is made of plays outside the levels (SPEC.md, "The parts").

295const parts = form.arrange(once(stack(
296  part('keys', keys),
297  opening,
298  part('lead', leadPart),
299  part('bass', bass),
300  part('drums', kit),
301  sung(vox),
302  found,
303  alone,
304  part('snake', snake),
305  snakeSolo,
306).mul(velocity(push))))

── into the next section: over the last bar of each, the answer for the section being entered ── Past the last section there is no next one, so nothing plays there.

310const into = dj.into.choice.early(1)
311const next = section.choice.early(1)
312const enters = (name) => next.fmap((s) => s === name)
313const toms = s("~ ~ ~ ~ ~ ~ ~ ~ 808ht 808ht 808mt 808mt 808lt 808lt 808lt 808lt").room(.3).gain(.6).orbit('drums')

one note across the bar, its level and high-pass both rising: a swell. As written it leads into chorus 1 only, louder and sooner than Jev's own.

316const riser = s("white").attack(1.7).sustain(1).release(.05).hpf(400).hpenv(4.3).hpattack(1.9).hpsustain(1).gain(.5)
317const rise  = s("white").attack(1.2).sustain(1).release(.05).hpf(400).hpenv(4.3).hpattack(1.9).hpsustain(1).gain(.9)

into the clean chorus nothing is noise: the pad's G chord, an octave doubled, swelling up as its filter opens

320const lift = note("[55,59,62,67,71,74]").s("supersaw").detune(.2).attack(1.7).sustain(1).release(.05)
321  .lpf(700).lpenv(3).lpattack(1.8).room(.4).gain(.3).orbit('keys')
322const transition = into.pick({
323  asWritten: stack(rise.mask(enters('chorus1')), lift.mask(enters('chorus2'))),
324  fill: toms,
325  riser,
326  dropout: silence,
327}).mask("<0!7 1>")

dropout: the parts stop for the section's last beat. The outro has no next section, so its last beat plays.

330const lastBeat = "<0!7 [0 0 0 1]>"
331const gap = stack(
332  "<1!7 [1 1 1 0]>",
333  into.fmap((c) => c !== 'dropout').mask(lastBeat),
334  section.choice.fmap((s) => s === 'outro').mask(lastBeat),
335)

Energy scales the band; the ending always lands at its own level. One postgain for the whole mix keeps the master's peak under -1 dBFS (SPEC.md, "Mix").

339stack(
340  stack(parts.mask(gap), transition).mul(velocity(dj.energy.score.div(3).range(.55, 1))),
341  form.arrange(once(ending)),
342).mul(postgain(.52))