HEY! LISTEN! — chiptune jungle, 170bpm, 64 bars as written; Jev arranges up to 24 sections. Spec: SPEC.md Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count).
── 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 which parts play it and how hard. Every 4 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.
Which parts play in each section as written (SPEC.md, "The parts"). Jev
is told this, and "back" is relative to the loudest of these (loudest).
15const verseParts = ['arps', 'drums', 'bass', 'vocals'] 16const dropParts = ['arps', 'lead', 'drums', 'bass'] 17const partsBySection = { 18 intro: ['arps'], 19 introB: ['arps', 'vocals'], 20 verse1: verseParts, 21 verse1b: verseParts, 22 build1: verseParts, 23 drop1: dropParts, 24 drop1b: dropParts, 25 drop1c: dropParts, 26 verse2: verseParts, 27 verse2b: verseParts, 28 build2: verseParts, 29 drop2: dropParts, 30 drop2b: dropParts, 31 drop2c: dropParts, 32 drop2d: dropParts, 33 outro: ['arps'], 34}
36const song = { 37 title: 'HEY! LISTEN!', 38 what: 'chiptune jungle about Jev playing A Link to the Past, where a C bot walks Link and Jev picks the goal: Navi shouting Hey! Listen!, the secret-found jingle on every drop, an amen break under SNES-style square waves', 39 tempo: '170 bpm, 4 bars a section', 40 key: 'G minor', 41 chords: 'Gm Gm Eb Eb F F D D, two bars each; a section starts on either Gm or F', 42 lyrics: { 43 introB: 'Hey! Listen!', 44 verse1: 'Jev plays Link to the Past / The core\'s a library', 45 verse1b: 'Three questions a minute / The bot walks, Jev picks', 46 build1: 'It\'s dangerous to go alone! Take this.', 47 verse2: 'Native now, WASM some day / Same program, both', 48 verse2b: 'Hey, listen / Jev plays Link to the Past', 49 build2: 'Hey! Listen! Hey! Listen!', 50 outro: 'Hey!', 51 }, 52 partsBySection, 53} 54const parts = (s) => '. Parts: ' + partsBySection[s].join(', ') 55 56const formJev = jev({ 57 state: song, 58 questions: { 59 section: choice('Pick the section of the song that plays next, from those allowed. The outro ends the song: choose it once the song has built to its peak and made its point, before it drags', { 60 intro: 'the square-wave arpeggio alone, with a wide reverb' + parts('intro'), 61 introB: 'the arpeggio, and Navi shouts Hey! Listen!' + parts('introB'), 62 verse1: 'verse 1, first half: half-speed beat, bass, and the vocoder sings ' + song.lyrics.verse1 + parts('verse1'), 63 verse1b: 'verse 1, second half: the vocoder sings ' + song.lyrics.verse1b + parts('verse1b'), 64 build1: 'the build: the old man\'s line, the arpeggio opening up over a bass pedal, then a snare roll into the drop' + parts('build1'), 65 drop1: 'drop 1 lands on the secret-found jingle alone, then amen break, 808, sub bass and the lead melody' + parts('drop1'), 66 drop1b: 'drop 1, middle' + parts('drop1b'), 67 drop1c: 'drop 1, last four bars' + parts('drop1c'), 68 verse2: 'verse 2, first half: half-speed beat, bass, and the vocoder sings ' + song.lyrics.verse2 + parts('verse2'), 69 verse2b: 'verse 2, second half: the vocoder sings ' + song.lyrics.verse2b + parts('verse2b'), 70 build2: 'the build: Hey! Listen! twice, the arpeggio opening up over a bass pedal, and a snare roll into the drop' + parts('build2'), 71 drop2: 'drop 2 lands on the secret-found jingle: sixteenth hats and a doubled arpeggio over the drop-1 groove' + parts('drop2'), 72 drop2b: 'drop 2, continued' + parts('drop2b'), 73 drop2c: 'drop 2, third four bars' + parts('drop2c'), 74 drop2d: 'drop 2, last four bars' + parts('drop2d'), 75 outro: 'the arpeggio fades out; Navi\'s last Hey!, then a kick, 808 and crash: the end' + parts('outro'), 76 }), 77 }, 78})
how much of each part the section decidedThisSection has: out, back or full
81const 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. A part that does not play in that section (partsBySection) stays silent whatever you answer', [ 82 'out: silent for the section', 83 'back: under the loudest part in the section, at half its level', 84 'full: as loud as written', 85]) 86const djJev = jev({ 87 state: song, 88 questions: { 89 arps: level('the arpeggios (the square-wave chord arpeggio with its wide reverb, in every section, doubled in drop 2)'), 90 lead: level('the lead (only in the drops: the square-wave lead melody, and the secret-found jingle that lands each drop alone)'), 91 drums: level('the drums (the half-speed beat in the verses, the snare rolls in the builds, the amen break, 808 and hats in the drops)'), 92 bass: level('the bass (a triangle bass in the verses, a triangle pedal in the builds, the supersaw and sine sub bass in the drops)'), 93 vocals: level('the voices (the vocoded verse lines, Navi\'s Hey! Listen! in the intro and build 2, the old man\'s line in build 1; the outro\'s last Hey! always plays)'), 94 into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', { 95 cut: 'straight in, no transition', 96 fill: 'an amen fill over the last half bar, replacing the break, at the drums\' level', 97 riser: 'a square-wave sweep climbing three octaves over the last bar', 98 dropout: 'the parts stop for the last beat and their reverb rings on into the gap before the section hits', 99 }), 100 energy: score('How intense should the next section be? Shape the energy of the whole song, following its history, how it measured and how the listener reacted', [ 101 'held back: quieter, a little distance', 102 'steady: under full', 103 'driving: a touch under full', 104 'all out: full, as written', 105 ]), 106 }, 107})
a little chance in the form: the section is drawn from Jev's probabilities
110const { section } = formJev.every(4, { sample: 0.7 }) 111const dj = djJev.every(4, { 112 after: { section }, 113 forget: ['into'], 114 // a fill never plays into a drop: the build's snare roll already is that fill 115 allow: { into: ({ section: next }) => (next.startsWith('drop') ? ['cut', 'riser', 'dropout'] : ['cut', 'fill', 'riser', 'dropout']) }, 116 fallback: { arps: 2, lead: 2, drums: 2, bass: 2, vocals: 2, into: 'cut', energy: 3 }, 117})
"back" is relative: each part's level is divided by the loudest level Jev chose among the parts that play in the section and are not out, so the loudest part plays as written and energy alone sets the overall level.
122const partNames = ['arps', 'lead', 'drums', 'bass', 'vocals'] 123const zip = (pats) => 124 pats.slice(1).reduce((acc, p) => acc.fmap((xs) => (x) => [...xs, x]).appLeft(p), pats[0].fmap((x) => [x])) 125const loudest = zip([section.choice, ...partNames.map((p) => dj[p].score)]).fmap(([s, ...scores]) => { 126 const heard = scores.filter((x, i) => Math.round(x) > 0 && (partsBySection[s] ?? []).includes(partNames[i])) 127 return heard.length ? Math.max(...heard) : 2 128})
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).
bars: 1-8 intro | 9-16 verse 1 | 17-20 build | 21-32 DROP 1 | 33-40 verse 2 41-44 build | 45-60 DROP 2 | 61-64 outro (each mask = 64 steps)
137const heyIntro = "<0!4 1 0!59>" 138const verses = "<0!8 1!8 0!16 1!8 0!24>" 139const verse1 = "<0!8 1!8 0!48>" 140const verse2 = "<0!32 1!8 0!24>" 141const danger = "<0!16 1 0!47>" 142const heyBuild = "<0!40 1 0 1 0!21>" 143const builds = "<0!16 1!4 0!20 1!4 0!20>" 144const notBuilds = "<1!16 0!4 1!20 0!4 1!20>" 145const roll = "<0!18 1!2 0!22 1!2 0!20>" 146const drops = "<0!20 1!12 0!12 1!16 0!4>" 147const drop1 = "<0!20 1!12 0!32>" 148const drop2 = "<0!44 1!16 0!4>" 149const secret = "<0!20 1 0!23 1 0!19>" 150const leadRests = "<1!20 0 1!23 0 1!19>" // the jingle lands alone 151const theEnd = "<0!62 1 0>" 152const arpGain = "<.38!8 .22!8 .32!16 .22!8 .32!20 .32 .26 .16 .08>"
two bars per chord: Gm Gm Eb Eb F F D D
155const arp = "<[g4 bb4 d5 g5 d5 bb4 g4 d4]!2 [eb4 g4 bb4 eb5 bb4 g4 eb4 bb3]!2 [f4 a4 c5 f5 c5 a4 f4 c4]!2 [d4 f#4 a4 d5 a4 f#4 d4 a3]!2>" 156const lead = "<[g5 ~ g5 ~ f5 g5 ~ d5] [~ d5 ~ bb4 ~ g4 ~ ~] [eb5 ~ eb5 ~ d5 eb5 ~ bb4] [~ bb4 ~ g4 ~ eb4 ~ ~] [f5 ~ f5 ~ eb5 f5 ~ c5] [~ c5 ~ a4 ~ f4 ~ ~] [f#5 ~ f#5 ~ e5 f#5 ~ a5] [a5 ~ f#5 ~ d5 ~ a4 ~]>" 157const bassRoots = "<43!2 39!2 41!2 38!2>" // G2 Eb2 F2 D2, as MIDI numbers 158const subRoots = "<31!2 27!2 29!2 26!2>" // G1 Eb1 F1 D1 159const amen = "<[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] [16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31] [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] [0 1 2 3 4 5 6 7 [12 12] [12 12] 14 14 [30 30] [30 30] 31 31]>"
── parts ───────────────────────────────────────── chiptune, with a wide SNES-style reverb: the arpeggios, then the lead and jingle
163const arpLine = note(arp).s("square").decay(.12).sustain(0).room(.5).size(.8).gain(arpGain) 164const arps = stack( 165 arpLine.lpf(3200).mask(notBuilds), 166 // the builds rise: the filter opens from 400 Hz to 3.2 kHz over their four bars 167 arpLine.lpf(saw.slow(4).range(400, 3200)).mask(builds), 168 note(arp).ply(2).s("square").decay(.08).sustain(0).lpf(3600).pan(.7).gain(.2).mask(drop2) 169) 170const melody = stack( 171 note(lead).s("square").decay(.25).sustain(.15).lpf(4000).room(.5).size(.8) 172 .delay(.25).delaytime(.1765).delayfeedback(.3).gain(.6).mask(drops).mask(leadRests), 173 // the Zelda secret-found jingle, landing each drop: seven 16ths and a held last note 174 note("g5 f#5 d#5 a4 g#4 e5 g#5 c6@9").s("square").decay(.4).sustain(.3).release(.4) 175 .room(.6).size(.8).gain(.8).mask(secret) 176)
178const drums = stack( 179 // verses: half-speed beat 180 s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.42).mask(verses), 181 s("sn:3").struct("~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~").gain(.56).mask(verses), 182 s("hh*8").gain(".18 .08").mask(verses), 183 // drops: jungle two-step, 808 underneath 184 s("[bd:3,808bd:1]").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.4).mask(drops), 185 s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.15).gain(.42).mask(drops), 186 s("hh*8").gain(".25 .12").mask(drop1), 187 s("hh*16").gain(".25 .1 .18 .1").mask(drop2), 188 // the crash on each drop: hh27:1 is a crash (cr is a ride), about 15 dB quieter than cr 189 s("hh27:1").gain(1.75).mask(secret) 190)
the builds' snare roll (rising across its two bars) and the drops' rolling
amen: a fill replaces these for its half-bar (noFill, below)
198const bass = stack( 199 note(bassRoots).s("triangle").struct("x ~ ~ x ~ ~ x ~") 200 .decay(.3).sustain(.4).gain(.5).mask(verses), 201 // the builds: a triangle pedal on G under the opening filter 202 note(43).s("triangle").struct("x*4").decay(.2).sustain(.3).gain(.4).mask(builds), 203 note(subRoots).struct("x@3 x@3 x@4 x@2 x@4").s("supersaw").detune(.6) 204 .lpf(sine.range(220, 900).slow(8)).lpq(3).shape(.3).gain(.45).mask(drops), 205 note(subRoots).struct("x@3 x@3 x@4 x@2 x@4").s("sine").gain(.6).mask(drops) 206) 207 208const vox = stack( 209 // vocoder verses, one line per 2 bars, each sung on its chord 210 s("<hljevplays ~ hlcore ~ hlframe ~ hlpress ~>").gain(1.4).room(.25).mask(verse1), 211 s("<hlnative ~ hlsame ~ hlheyvoc ~ hljevplaysd ~>").gain(1.4).room(.25).mask(verse2), 212 // Navi and the old man 213 s("hlheylisten").gain(1.5).room(.4).mask(heyIntro), 214 s("hldanger").gain(1.5).room(.3).mask(danger), 215 s("hlheylisten").gain(1.5).room(.4).mask(heyBuild) 216)
the end: Navi's last "Hey!" on beat 1 of bar 63, then kick, 808 and crash on beat 3. It always plays, at full: outside the part levels and the energy.
226const once = (p) => p.mask("<1!64 0!936>") // 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.
The chords turn over every 8 bars, so each section may only be followed
by one that starts on the other half of the turn (Gm Eb | F D). Build 2
comes only after verse 2, so every vocoded line is sung at least once.
times: each drop's loop goes round once more at most (SPEC.md, "Loops end").
235const form = walk(section, { 236 max: 24, 237 sections: { 238 intro: { at: 0, next: ['introB'] }, 239 introB: { at: 4, next: ['verse1'] }, 240 verse1: { at: 8, next: ['verse1b'] }, 241 verse1b: { at: 12, next: ['build1'] }, 242 build1: { at: 16, next: ['drop1'] }, 243 drop1: { at: 20, next: ['drop1b'] }, 244 drop1b: { at: 24, next: ['drop1c'], times: 2 }, 245 drop1c: { at: 28, next: ['verse2', 'verse1', 'drop1b'] }, 246 verse2: { at: 32, next: ['verse2b'] }, 247 verse2b: { at: 36, next: ['build2', 'build1'] }, 248 build2: { at: 40, next: ['drop2'] }, 249 drop2: { at: 44, next: ['drop2b'] }, 250 drop2b: { at: 48, next: ['drop2c'], times: 2 }, 251 drop2c: { at: 52, next: ['drop2d', 'drop2b'], times: 2 }, 252 drop2d: { at: 56, next: ['outro', 'drop2c'] }, 253 outro: { at: 60, next: [] }, 254 }, 255})
── the song: Jev's section, parts and energy, composed ── Each section plays its own bars, each part at the level Jev chose.
── into the next section: over the last bar of each, the next one's answer ── Past the last section there is no next one, so nothing plays there.
270const into = dj.into.choice.early(1) 271const transition = into.pick({ 272 cut: silence, 273 // the fill: the second half of the bar, at the drums' level 274 fill: part('drums', n("~ [[12 12] 13 14 15 [28 28] 29 [30 30 30 30] [31 31 31 31]]") 275 .s("amencutup").hpf(150).cut(1).gain(.28)), 276 riser: note(saw.range(43, 79).segment(16)).s("square").decay(.05).sustain(0).lpf(4000) 277 .room(.5).size(.8).gain(saw.range(.05, .2)).orbit('arps'), 278 dropout: silence, 279}).mask("<0!3 1>")
the fill replaces the break and the roll for that half-bar, so no hit doubles
dropout: the parts stop for the section's last beat. The outro has no next section, so its last beat plays.