1// MINIMAX WAS RIGHT THERE — chiptune jungle, 170bpm, 64 bars as written; Jev arranges up to 24 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(170/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 which parts play it and how 10// 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: 'MINIMAX WAS RIGHT THERE', 14 what: 'chiptune jungle roasting people who use Jev as a game bot, where plain code wins: Pong, tic-tac-toe, a Mario Kart racing line; Jev is for judgment, not for joysticks', 15 tempo: '170 bpm, 4 bars a section', 16 key: 'A minor', 17 chords: 'Am Am F F C C G G, two bars each; the verses start on Am or C, and the drops are phased to land on Am', 18 lyrics: { 19 verse1: 'You paid by the billion to press right jump / Tic tac toe was solved before you were born', 20 verse1b: 'A PID loop takes the racing line / Minimax was right there', 21 build1: 'Oh no no no', 22 verse2: 'Two hundred twenty seven milliseconds a move / Jev is for judgment, not for joysticks', 23 verse2b: 'Route the ticket. Gate the sudo. / Minimax was right there', 24 build2: 'Oh no no no', 25 }, 26} 27 28const formJev = jev({ 29 state: song, 30 questions: { 31 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', { 32 intro: 'the square-wave arpeggio alone', 33 introB: 'the arpeggio with Pong blips answering it', 34 verse1: 'verse 1: half-speed beat, bass, vocoded lines', 35 verse1b: 'verse 1, second half', 36 build1: 'the build: the Mario Kart blue shell locks on, Pong slows to one blip per decision, oh no no no, and the fall off the cliff', 37 drop1: 'drop 1 lands on A minor and a crash: crushed amen break, jungle kick, reese and sub bass, the chip lead with Pong answering it', 38 drop1b: 'drop 1, middle', 39 drop1c: 'drop 1, last four bars', 40 verse2: 'verse 2: half-speed beat, bass, vocoded lines', 41 verse2b: 'verse 2, second half', 42 build2: 'the build again: blue shell, slowing Pong, oh no no no, the fall', 43 drop2: 'drop 2 lands on A minor and a crash: the lead an octave up in harmony, sixteenth hats, a double-time arpeggio, the reese opening further', 44 drop2b: 'drop 2, continued', 45 drop2c: 'drop 2, the peak', 46 drop2d: 'drop 2, last four bars', 47 outro: 'a last kick and crash left to ring on A, the arpeggio fading on A minor, then the game-over blip: the end', 48 }), 49 }, 50}) 51 52// how much of each part the section decidedThisSection has: out, back or full 53const 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', [ 54 'out: silent for the section', 55 'back: under the rest of the mix', 56 'full: as written', 57]) 58const djJev = jev({ 59 state: song, 60 questions: { 61 arps: level('the arpeggio (the square-wave chord arpeggio, under every section as written except the builds, double-time in drop 2)'), 62 lead: level('the chip lead (the square-wave melody, in the drops only, an octave up in harmony in drop 2)'), 63 pong: level('Pong (blips answering the arpeggio in the intro, answering the chip lead in the drops, and in the builds slowing down to one blip per 227 ms decision)'), 64 drums: level('the drums (the half-speed beat in the verses; in the drops the jungle kick and 808, the crushed amen break, hats and a crash where each drop lands)'), 65 bass: level('the bass (a triangle bass in the verses, the reese and sine sub bass in the drops)'), 66 vocals: level('the vocoded verse lines'), 67 into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', { 68 cut: 'straight in, no transition', 69 fill: 'a crushed amen fill in place of the written break, chopped into rolls down into the first beat', 70 riser: 'a square-wave sweep climbing A minor three octaves over the last bar, like a power-up', 71 dropout: 'everything stops for the last beat, a gap the last word rings into', 72 }), 73 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', [ 74 'held back: quieter', 75 'steady: a little under full', 76 'driving: full volume', 77 'all out: full volume, nothing held back', 78 ]), 79 }, 80}) 81 82// The transitions' rules, as code: a build always ends as written (so only a 83// cut or a dropout out of one), a fill goes into a verse or a build, a riser 84// into a build or a drop, a dropout into a drop or the outro. 85const intoAllowed = ({ section: next, playingNow: now = '' }) => [ 86 'cut', 87 ...((next.startsWith('verse') || next.startsWith('build')) && !now.startsWith('build') ? ['fill'] : []), 88 ...((next.startsWith('build') || next.startsWith('drop')) && !now.startsWith('build') ? ['riser'] : []), 89 ...(next.startsWith('drop') || next === 'outro' ? ['dropout'] : []), 90] 91 92// a little chance in the form: the section is drawn from Jev's probabilities 93const { section } = formJev.every(4, { sample: 0.7 }) 94const dj = djJev.every(4, { 95 after: { section }, 96 forget: ['into'], 97 allow: { into: intoAllowed }, 98 sample: { into: 1 }, 99 fallback: { arps: 2, lead: 2, pong: 2, drums: 2, bass: 2, vocals: 2, into: 'cut', energy: 3 }, 100}) 101// A part at Jev's level: silent when out, otherwise its velocity scaled by 102// the level. Each part has its own orbit, named for its question, so the 103// meter hears it alone (measuredSound). 104const part = (name, pat) => 105 pat.orbit(name).mask(dj[name].score.round().gt(0)).mul(velocity(dj[name].score.round().div(2))) 106 107// bars: 1-8 intro | 9-16 verse 1 | 17-20 build | 21-32 DROP 1 | 33-40 verse 2 108// 41-44 build | 45-60 DROP 2 | 61-64 outro (each mask = 64 steps) 109const pongIn = "<0!4 1!4 0!56>" 110const verses = "<0!8 1!8 0!16 1!8 0!24>" 111const verse1 = "<0!8 1!8 0!48>" 112const verse2 = "<0!32 1!8 0!24>" 113const builds = "<0!16 1!4 0!20 1!4 0!20>" 114const drops = "<0!20 1!12 0!12 1!16 0!4>" 115const drop1 = "<0!20 1!12 0!32>" 116const drop2 = "<0!44 1!16 0!4>" 117const unsung = "<1!16 0!16 1!8 0!24>" // the intro and the verses 118const outro = "<0!60 1!4>" 119const crash = "<0!20 1 0!23 1 0!19>" 120const final = "<0!60 1 0!3>" 121const gameOver = "<0!63 1>" 122// the line each verse bar starts (verse 1 bars 9-16, verse 2 bars 33-40) 123const titleLines = "<0!14 1 0!23 1 0!25>" 124 125// two bars per chord: Am Am F F C C G G. The drops play it four bars on 126// (`.early(4)`), so each lands on Am. 127const roots = "<a1!2 f1!2 c2!2 g1!2>" 128const subs = "<a1!2 f2!2 c2!2 g2!2>" // the sine sub: F and G an octave up, over 45 Hz 129const arp = "<[a4 c5 e5 a5 e5 c5 a4 e4]!2 [f4 a4 c5 f5 c5 a4 f4 c4]!2 [c5 e5 g5 c6 g5 e5 c5 g4]!2 [g4 b4 d5 g5 d5 b4 g4 d4]!2>" 130// the amen at its own groove: 8 slices a bar (each an eighth of the break), 131// choked; a new chop order, rolling in its fourth bar 132const amen = "<[0 1 2 3 4 5 6 7] [8 9 10 11 12 13 14 15] [0 1 2 3 16 17 18 19] [24 25 [26 26] 27 [28 28] 29 [30 31] [31 31]]>" 133 134// ── parts ───────────────────────────────────────── 135// square arpeggio: the whole song except the builds, double-time in drop 2, 136// on A minor alone in the outro, fading 137const arpSq = (p) => p.s("square").decay(.12).sustain(0).lpf(3200) 138const arps = stack( 139 arpSq(note(arp)).gain("<.24!8 .14!56>").mask(unsung), 140 arpSq(note(arp).early(4)).gain(.2).mask(drops), 141 note(arp).early(4).ply(2).s("square").decay(.08).sustain(0).lpf(3600).pan(.7).gain(.2).mask(drop2), 142 arpSq(note("[a4 c5 e5 a5 e5 c5 a4 e4]")).gain("<.2 .14 .08 .04>").mask(outro) 143) 144 145// the drops' riff: the chip lead calls in the first half of each bar, and 146// Pong answers it in the second, ping to pong across the stereo field 147const call = "<[4 ~ 7 4 ~ ~ ~ ~] [2 4 ~ 0 ~ ~ ~ ~] [5 ~ 7 5 ~ ~ ~ ~] [2 3 ~ 5 ~ ~ ~ ~] [4 ~ 6 4 ~ ~ ~ ~] [2 4 ~ 7 ~ ~ ~ ~] [6 ~ 8 6 ~ ~ ~ ~] [1 3 ~ 6 ~ ~ ~ ~]>" 148const answer = "<[~ ~ ~ ~ 4 ~ 0 ~] [~ ~ ~ ~ 2 ~ 0 ~] [~ ~ ~ ~ 5 ~ 2 ~] [~ ~ ~ ~ 5 ~ 0 ~] [~ ~ ~ ~ 4 ~ 2 ~] [~ ~ ~ ~ 7 ~ 4 ~] [~ ~ ~ ~ 6 ~ 1 ~] [~ ~ ~ ~ 1 ~ 3 4]>" 149const chip = (p) => p.s("square").decay(.25).sustain(.15).lpf(4000) 150 .delay(.25).delaytime(.1765).delayfeedback(.3) 151const lead = stack( 152 chip(n(call).early(4).scale("A4:minor")).gain(.6).mask(drop1), 153 // drop 2: an octave up, with a third above 154 chip(n(call).early(4).scale("A5:minor")).gain(.5).mask(drop2), 155 chip(n(call).early(4).add(n(2)).scale("A5:minor")).gain(.35).mask(drop2) 156) 157 158const pong = stack( 159 // blips answering the arp in the intro 160 note("~ ~ ~ a5 ~ ~ e5 ~").s("square").decay(.05).sustain(0) 161 .pan("<0 1>").gain(.3).mask(pongIn), 162 // in the builds: sixteenths slowing to one blip per 227 ms decision 163 // (6.22 a bar) across bars 1-2, then nothing under the hit and "oh no" 164 note("a5").s("square").decay(.08).sustain(0) 165 .struct("<[x*8 x*6] [x*4 x*3.11] ~ ~>").pan(sine.fast(3)).gain(.7).mask(builds), 166 // in the drops: answering the chip lead 167 n(answer).early(4).scale("A5:minor").s("square").decay(.06).sustain(0) 168 .pan("[.15 .15 .85 .85]*2").gain(.45).mask(drops) 169) 170 171// the written amen, except where a fill takes a section's last bar 172const amenOut = stack("<1!3 0>", dj.into.choice.early(1).fmap((c) => c !== 'fill').mask("<0!3 1>")) 173const drums = stack( 174 // verses: half-speed beat 175 s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.6).mask(verses), 176 s("sn:3").struct("~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~").gain(.35).mask(verses), 177 s("hh*8").gain(".2 .1").mask(verses), 178 179 // drops: a jungle kick with 808 underneath, varied every fourth bar; 180 // snare on 2 and 4; the crushed amen at its own groove 181 s("[bd:3,808bd:1]").struct("<[x ~ ~ ~ ~ ~ x ~ ~ ~ x ~ ~ ~ ~ ~]!3 [x ~ x ~ ~ ~ ~ ~ ~ x ~ ~ x ~ ~ ~]>").gain(.45).mask(drops), 182 s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.15).gain(.4).mask(drops), 183 n(amen).s("amencutup").cut(1).crush(6).hpf(150).gain(.35).mask(drops).mask(amenOut), 184 s("hh:0*8").gain(".7 .35").mask(drop1), 185 s("hh:0*16").gain(".7 .3 .5 .3").mask(drop2), 186 s("hh27:1").gain(2).mask(crash) 187) 188 189const bass = stack( 190 note(roots).add(note(12)).s("triangle").struct("x ~ ~ x ~ ~ x ~") 191 .decay(.3).sustain(.4).gain(.5).mask(verses), 192 // the reese, its filter opening further in drop 2 193 note(roots).struct("x@2 ~ x@3 ~@2 x@4 x@2 x@2").s("supersaw").detune(.6) 194 .lpf(sine.range(220, 900).slow(8)).lpq(3).shape(.3).gain(.45).early(4).mask(drop1), 195 note(roots).struct("x@2 ~ x@3 ~@2 x@4 x@2 x@2").s("supersaw").detune(.6) 196 .lpf(sine.range(300, 1600).slow(8)).lpq(3).shape(.3).gain(.45).early(4).mask(drop2), 197 note(subs).struct("x@2 ~ x@3 ~@2 x@4 x@2 x@2").s("sine").gain(.6).early(4).mask(drops) 198) 199 200// the vocoded verse lines Jev levels: one per 2 bars, each on its own chord 201const vox = stack( 202 s("<rightjump ~ tictactoe ~ pidloop ~ ~ ~>").gain(1).room(.2).mask(verse1), 203 s("<twotwoseven ~ joysticks ~ sudo ~ ~ ~>").gain(1).room(.2).mask(verse2) 204) 205 206// the hook: the title lines and the builds' memes. It always plays, like the 207// ending: the energy and a dropout's gap reach it, no part level does. 208const hook = stack( 209 s("minimax").gain(1).room(.2).mask(titleLines), 210 // the blue shell locks on under the slowing Pong, then hits on bar 3 at 211 // full; its tail stops before "oh no" 212 s("<blueshell ~ ~ ~>").end(.65).gain(.6).mask(builds), 213 s("<~ ~ blueshell ~>").begin(.65).end(.7).gain(.9).mask(builds), 214 s("<~ ~ [~ ohno@7] ~>").gain(1).mask(builds), 215 // the fall, after "oh no", from bar 4 216 s("<~ ~ ~ kartfall>").gain(.7).room(.4).mask(builds) 217).orbit('vocals') 218 219// the end: kick, 808, crash and a sine A left to ring, then the game-over 220// blip on A. It always plays. 221const ending = stack( 222 s("[bd:3,808bd:1]").room(.6).size(.9).gain(.55).mask(final), 223 s("hh27:1").room(.6).size(.9).gain(2.5).mask(final), 224 note("a1").s("sine").decay(1.4).sustain(0).gain(.5).mask(final), 225 note("a3").s("square").decay(.6).sustain(0).gain(.35).mask(gameOver) 226) 227 228const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 229 230// ── the form, as rules in code: which sections may follow which ── 231// `at` is where each starts as written; an empty `next` ends the song. The 232// written order is always allowed, and is what plays without Jev. Verse 2 233// always plays before drop 2: build2 follows only verse2b. 234// `times`: drop 2's loops go 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'] }, 245 drop1c: { at: 28, next: ['verse2', 'verse1'] }, 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}) 256 257// ── the song: Jev's section, parts and energy, composed ── 258// Each section plays its own bars, each part at the level Jev chose; the 259// energy scales every part's velocity together, so the mix keeps its 260// balance. The hook and the ending always play. 261const parts = form.arrange(once(stack( 262 part('arps', arps), 263 part('lead', lead), 264 part('pong', pong), 265 part('drums', drums), 266 part('bass', bass), 267 part('vocals', vox), 268 hook, 269))) 270 271// ── into the next section: over the last bar of each, the next one's answer ── 272// Past the last section there is no next one, so nothing plays there. Under 273// a verse's sung lines a fill or riser waits for the last half-bar. 274const into = dj.into.choice.early(1) 275const clearOfLines = (p) => stack( 276 p.mask(section.choice.fmap((s) => !s.startsWith('verse'))), 277 p.mask(section.choice.fmap((s) => s.startsWith('verse'))).mask("[0 1]"), 278) 279const transition = into.pick({ 280 cut: silence, 281 fill: clearOfLines(n("[12 12] 13 14 15 [28 28] 29 [30 30 30 30] [31 31 31 31]").s("amencutup").crush(6) 282 .hpf(150).gain(.45)).orbit('drums').mask(dj.drums.score.round().gt(0)), 283 riser: clearOfLines(n(saw.range(0, 22).segment(16).floor()).scale("A2:minor").s("square").decay(.05).sustain(0).lpf(4000) 284 .gain(saw.range(.05, .2))).orbit('arps').mask(dj.arps.score.round().gt(0)), 285 dropout: silence, 286}).mask("<0!3 1>") 287// dropout: the parts stop for the section's last beat. The outro has no 288// next section, so its last beat plays. 289const lastBeat = "<0!3 [0 0 0 1]>" 290const gap = stack( 291 "<1!3 [1 1 1 0]>", 292 into.fmap((c) => c !== 'dropout').mask(lastBeat), 293 section.choice.fmap((s) => s === 'outro').mask(lastBeat), 294) 295 296stack(parts.mask(gap), transition, form.arrange(once(ending))) 297 .mul(velocity(dj.energy.score.div(3).range(.55, 1))) 298 .mul(postgain(.5)) // the master level: peaks under -1 dBFS (superdough has no limiter)