1// BORROW CHECKER BLUES — robot-funk, 110bpm, 64 bars as written; Jev arranges up to 24 four-bar sections. Spec: SPEC.md 2// Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count). 3setcps(110/60/4) 4await samples('github:tidalcycles/dirt-samples') 5await samples('jev-samples/strudel.json') 6 7// ── Jev: two of them, in TypeSafe's own shape (SPEC.md, "Jev in the booth") ── 8// formJev picks the next section; djJev, asked once that is decided (questions 9// in one request cannot see each other), says how much of each part plays it, 10// how the song moves into it, and how hard. Every 4 bars. The form's rules (walk, at the bottom) narrow the 11// sections Jev may pick and end the song; without Jev it plays as written. 12const song = { 13 title: 'BORROW CHECKER BLUES', 14 what: 'Daft Punk-style robot funk about deterministic tools in front of Jev: the verses are real rustc errors, the chorus says lint before you spend, and when the build finally succeeds, Wow', 15 tempo: '110 bpm, 4/4, 4 bars a section', 16 key: 'E dorian', 17 chords: 'Em7 Em7 A7 A7 Dmaj7 Dmaj7 Bm7 Bm7, two bars each, under the bass and the stabs', 18 lyrics: { 19 verse1a: 'Cannot borrow as mutable / Lifetime may not live long enough', 20 verse1b: 'x does not live long enough / Use of moved value', 21 chorus1: 'Lint. Before you spend / Lint. Before you spend / Finished dev profile / Wow', 22 verse2a: 'Mismatched types / Which is behind a shared reference', 23 verse2b: 'Expected Foo, found Bar / Cannot borrow as mutable more than once at a time', 24 chorus2: 'Lint. Before you spend / Lint. Before you spend / Finished dev profile / Wow', 25 outro: 'Finished dev profile / Wow (the ending; it always plays)', 26 }, 27} 28 29const formJev = jev({ 30 state: song, 31 questions: { 32 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', { 33 intro1: 'the robot bass alone; plays: bass', 34 intro2: 'the bass and a four-on-the-floor kick; plays: bass, drums (kick only)', 35 verse1a: 'verse 1, first half: the band enters, dull stabs, vocoded rustc errors; plays: bass, drums, stabs, vocals', 36 verse1b: 'verse 1, second half; plays: bass, drums, stabs, vocals', 37 chorus1: 'the chorus: the hook sung twice, then the build succeeds and a Wow; plays: bass, drums, stabs, vocals', 38 groove1a: 'groove 1: instrumental, open hi-hats, a crash, the stab filter starting to open; plays: bass, drums, stabs', 39 groove1b: 'groove 1 continues, the stabs opening bar by bar; plays: bass, drums, stabs', 40 groove1c: 'groove 1, last four bars, the stabs at their brightest yet; plays: bass, drums, stabs', 41 verse2a: 'verse 2, first half: dull stabs again, more rustc errors; plays: bass, drums, stabs, vocals', 42 verse2b: 'verse 2, second half; plays: bass, drums, stabs, vocals', 43 chorus2: 'the chorus again, into the big groove; plays: bass, drums, stabs, vocals', 44 groove2a: 'groove 2, the peak: a crash, the stabs opening wider bar by bar; plays: bass, drums, stabs', 45 groove2b: 'groove 2 continues, opening; plays: bass, drums, stabs', 46 groove2c: 'groove 2 continues, opening; plays: bass, drums, stabs', 47 groove2d: 'groove 2, last four bars, wide open; plays: bass, drums, stabs', 48 outro: 'the bass alone, the build succeeds one last time, a last Wow, and the end; plays: bass, and the ending, which always plays', 49 }), 50 }, 51}) 52 53// how much of each part the section decidedThisSection has: out, back or full 54const 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', [ 55 'out: silent for the section', 56 'back: under the rest of the mix', 57 'full: as written', 58]) 59const djJev = jev({ 60 state: song, 61 questions: { 62 bass: level('the robot bass (the slap-style synth bass and its sub, the song rides on it from the first bar to the last)'), 63 drums: level('the drums (the four-on-the-floor kick from bar 5, claps and hi-hats with the band from the verse on, open hats and crashes in the grooves)'), 64 stabs: level('the disco stabs (short chord stabs from the verse on, the only harmony: dull in the verses, opening bar by bar through each groove)'), 65 vocals: level('the vocals (the vocoded rustc errors in the verses and the sung hook in the choruses; the grooves are instrumental as written)'), 66 into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', { 67 cut: 'straight in, no transition', 68 fill: 'a clap and kick fill into the first beat', 69 riser: 'the disco stabs repeating faster as their filter sweeps open over the last bar', 70 dropout: 'everything stops for the last beat, a gap before the section hits', 71 }), 72 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', [ 73 'held back: quieter', 74 'steady: a little under full', 75 'driving: full volume', 76 'all out: full volume, nothing held back', 77 ]), 78 }, 79}) 80 81// a phrase is a run of sections that belong together: intro1-2, a verse's 82// a and b, a groove's a to d. Within one, the song moves on with a cut or a 83// fill; a riser or a dropout only where one phrase gives way to another. 84const phrase = (s) => (s.startsWith('intro') ? 'intro' : s.replace(/[a-d]$/, '')) 85 86// a little chance in the form: the section is drawn from Jev's probabilities 87const { section } = formJev.every(4, { sample: 0.7 }) 88const dj = djJev.every(4, { 89 after: { section }, 90 forget: ['into'], 91 fallback: { bass: 2, drums: 2, stabs: 2, vocals: 2, into: 'cut', energy: 3 }, 92 allow: { 93 // the opening has no section before it: nothing plays into it 94 into: ({ section, playingNow }) => 95 !playingNow ? ['cut'] 96 : phrase(section) === phrase(playingNow) ? ['cut', 'fill'] : ['cut', 'fill', 'riser', 'dropout'], 97 }, 98}) 99 100// A part at Jev's level: silent when out, otherwise its velocity scaled by 101// the level. Each part has its own orbit, named for its question, so the 102// 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))) 105 106// bars: 1-8 intro | 9-16 verse 1 | 17-20 chorus | 21-32 GROOVE 1 | 33-40 verse 2 107// 41-44 chorus | 45-60 GROOVE 2 | 61-64 outro (each mask = 64 steps) 108const kick = "<0!4 1!56 0!4>" 109const band = "<0!8 1!52 0!4>" 110const verse1 = "<0!8 1!8 0!48>" 111const verse2 = "<0!32 1!8 0!24>" 112const chorus = "<0!16 1!4 0!20 1!4 0!20>" 113const grooves = "<0!20 1!12 0!12 1!16 0!4>" 114const crash = "<0!20 1 0!23 1 0!19>" 115// the hook's bars (17, 19, 41, 43): no clap on beat 2, over the "t" of "Lint" 116const hookClap = "<1!16 [1 0 1 1] 1 [1 0 1 1] 1!21 [1 0 1 1] 1 [1 0 1 1] 1!21>" 117// no transition over a chorus's Wow bar (20, 44) 118const noWow = "<1!19 0 1!23 0 1!20>" 119// stab filter per bar: dull in the verses, opening bar by bar through each groove 120const stabLpf = "<600!8 800!8 800!4 1000 1070 1150 1220 1290 1360 1440 1510 1580 1650 1730 1800 800!8 800!4 2000 2270 2530 2800 3070 3330 3600 3870 4130 4400 4670 4930 5200 5470 5730 6000 600!4>" 121const stabGain = .4 122 123// two bars per chord: Em7 Em7 A7 A7 Dmaj7 Dmaj7 Bm7 Bm7 124const roots = "<40!2 45!2 38!2 35!2>" // E2 A2 D2 B1, as MIDI numbers 125const sev = "<10!2 10!2 11!2 10!2>" // each chord's 7th over its root: the major 7th on Dmaj7 126const chords = "<[e4,g4,b4,d5]!2 [a3,c#4,e4,g4]!2 [d4,f#4,a4,c#5]!2 [b3,d4,f#4,a4]!2>" 127 128// ── parts ───────────────────────────────────────── 129// bass: the song rides on it, start to finish. The sub doubles the root at E2-B1. 130const bass = stack( 131 stack( 132 note("0 ~ 12 ~ 0 0 ~ ~ ~ 12 0 ~ 7 ~ ~ ~"), 133 note(sev).struct("~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ x ~"), 134 ).add(note(roots)) 135 .s("sawtooth").lpf(1100).lpq(10).decay(.12).sustain(.1).gain(.36), 136 note(roots).struct("x ~ ~ ~ x ~ ~ ~ x ~ ~ ~ x ~ ~ ~") 137 .s("sine").decay(.3).sustain(.4).gain(.4) 138) 139 140// drums: four on the floor, at levels that keep the mix under 0 dBFS (no master limiter) 141const drums = stack( 142 s("bd:3*4").gain(.5).mask(kick), 143 s("cp").struct("~ x ~ x").room(.2).gain(.42).mask(band).mask(hookClap), 144 s("hh*8").gain(".22 .11").mask(band), 145 s("808oh").struct("~ x ~ x ~ x ~ x").gain(.17).mask(grooves), 146 s("hh27:1").gain(1.9).mask(crash) 147) 148 149// disco stabs, short, filter by bar: the only harmony, near the bass 150const stabs = note(chords).struct("~ x ~ ~ ~ x ~ x").s("sawtooth") 151 .lpf(stabLpf).decay(.2).sustain(0).room(.3).gain(stabGain).mask(band) 152 153const vox = stack( 154 // verses: rustc errors, vocoded, one per 2 bars, each on its chord 155 s("<bcborrow ~ bclifetime ~ bcxlive ~ bcmoved ~>").gain(.8).room(.2).mask(verse1), 156 s("<bctypes ~ bcshared ~ bcfoobar ~ bctwice ~>").gain(.8).room(.2).mask(verse2), 157 // chorus: lint before you spend (Em7, then A7) ... it compiles (A7): wow, 158 // on the and of 3, after the 1.4 s success line 159 s("<bclintem ~ bclinta [bcfinished@5 bcwow@3]>").gain(.85).room(.25).mask(chorus) 160) 161 162// the end: the build succeeds (bar 62, Dmaj7) and the last "wow" (63), over 163// the bass. It always plays, at its written level. 164const ending = s("<~!61 bcfinishedd bcwow ~>").gain(.85).room(.5) 165 166const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 167 168// ── the form, as rules in code: which sections may follow which ── 169// `at` is where each starts as written; an empty `next` ends the song. The 170// written order is always allowed, and is what plays without Jev. `times`: 171// a groove goes round once more at most, cut short (SPEC.md, "Loops end"). 172const form = walk(section, { 173 max: 24, 174 sections: { 175 intro1: { at: 0, next: ['intro2'] }, 176 intro2: { at: 4, next: ['verse1a'] }, 177 verse1a: { at: 8, next: ['verse1b'] }, 178 verse1b: { at: 12, next: ['chorus1'] }, 179 chorus1: { at: 16, next: ['groove1a', 'verse2a'] }, 180 groove1a: { at: 20, next: ['groove1b'], times: 2 }, 181 groove1b: { at: 24, next: ['groove1c', 'verse2a'] }, 182 groove1c: { at: 28, next: ['verse2a', 'groove1a'], times: 1 }, 183 verse2a: { at: 32, next: ['verse2b'] }, 184 verse2b: { at: 36, next: ['chorus2'] }, 185 chorus2: { at: 40, next: ['groove2a'] }, 186 groove2a: { at: 44, next: ['groove2b'], times: 2 }, 187 groove2b: { at: 48, next: ['groove2c', 'outro'] }, 188 groove2c: { at: 52, next: ['groove2d', 'groove2a'], times: 1 }, 189 groove2d: { at: 56, next: ['outro', 'groove1a', 'chorus2', 'verse1a'] }, 190 outro: { at: 60, next: [] }, 191 }, 192}) 193 194// ── the song: Jev's section, parts and energy, composed ── 195// Each section plays its own bars, each part at the level Jev chose. 196const parts = form.arrange(once(stack( 197 part('bass', bass), 198 part('drums', drums), 199 part('stabs', stabs), 200 part('vocals', vox), 201))) 202 203// ── into the next section: over the last bar of each, the next one's answer ── 204// Past the last section there is no next one, so nothing plays there. Each 205// transition is a part (the fill is drums, the riser stabs), so it follows 206// the level of the section it plays in; none plays over a chorus's Wow bar. 207const into = dj.into.choice.early(1) 208// the riser's filter ends where the next section's stabs begin 209const nextLpf = form.arrange(once(stabLpf)).early(1) 210const transition = into.pick({ 211 cut: silence, 212 fill: part('drums', s("bd:3 ~ cp ~ [cp cp] cp [cp cp] [cp cp cp cp]").room(.2) 213 .gain(".5 .25 .28 .31 .35 .38 .41 .44")), 214 // on the chord of the section it leaves 215 riser: part('stabs', form.arrange(once(note(chords).struct("x*16").s("sawtooth") 216 .decay(.08).sustain(0))) 217 .lpf(saw.range(.15, 1).mul(nextLpf)).gain(saw.range(.1, stabGain))), 218 dropout: silence, 219}).mask("<0!3 1>").mask(form.arrange(once(noWow))) 220// dropout: the parts stop for the section's last beat. The outro has no 221// next section, so its last beat plays. 222const lastBeat = "<0!3 [0 0 0 1]>" 223const gap = stack( 224 "<1!3 [1 1 1 0]>", 225 into.fmap((c) => c !== 'dropout').mask(lastBeat), 226 section.choice.fmap((s) => s === 'outro').mask(lastBeat), 227) 228 229// Energy scales the parts and transitions together, so the mix keeps its 230// balance; the ending plays at its written level. 231stack( 232 stack(parts.mask(gap), transition).mul(velocity(dj.energy.score.div(3).range(.55, 1))), 233 form.arrange(once(ending)), 234).mul(postgain(.7)) // the master level: peaks under -1 dBFS (no limiter)