1// TYPESAFE JUNGLE — 170bpm, 64 bars as written; Jev arranges up to 28 four-bar sections. Spec: SPEC.md 2// vox: the repo's samples/ folder, served with the site (see README) 3// Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count). 4// (The MCP server rejects any code containing the substring e-v-a-l, comments included.) 5setcps(170/60/4) 6await samples('github:tidalcycles/dirt-samples') 7await samples('jev-samples/strudel.json') 8 9// ── Jev: two of them, in TypeSafe's own shape (SPEC.md, "Jev in the booth") ── 10// formJev picks the next section; djJev, asked once that is decided (questions 11// in one request cannot see each other), says how much of each part plays 12// it, how the song moves into it, and how hard. djJev is asked every section; 13// formJev only where the form branches. The form's rules (walk, at the bottom) narrow the 14// sections Jev may pick and end the song; without Jev it plays as written. 15const song = { 16 title: 'TYPESAFE JUNGLE', 17 what: 'a jungle track about Jev, the typed-decision model from TypeSafe AI, built on the My Name Is Jeff meme: the hook swaps Jeff for Jev', 18 tempo: '170 bpm, 4/4, 4 bars a section', 19 key: 'C minor', 20 chords: 'Cm Ab Fm G, one chord per bar, under a music-box lead and a reese bassline', 21 lyrics: { 22 build1: 'My name is Jeff / My name is Jev. J, E, V.', 23 build1b: 'My name is Jeff / Type safe Jev / Type safe... Jev', 24 build2: 'My name is Jeff / My name is Jev. J, E, V. / Type safe Jev / Type safe... Jev', 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. Choosing outro ends the song (outro, then the final hit): choose it once the song has built to its peak and made its point, before it drags', { 32 intro: 'the music-box lead alone', 33 tease: 'the lead with a thin, high-passed amen break coming in', 34 build1: 'the build: the hook vocals, the amen filter opening, a low sub', 35 build1b: 'the build peaks: the hook, a snare roll and a noise riser into the drop', 36 drop1: 'drop 1: full jungle drums, the reese and sine bassline, eighth hats, a crash on the downbeat', 37 drop1b: 'drop 1 continues', 38 drop1c: 'drop 1 with the rave stabs', 39 drop1d: 'the end of drop 1, stabs and all', 40 breakdown: 'the breakdown: drums out, just the lead and the low sub', 41 build2: 'the hook returns, the whole Jeff-to-Jev swap in 4 bars, with a noise riser into the second drop', 42 drop2: 'drop 2: everything, sixteenth hats, rave stabs, a crash on the downbeat', 43 drop2b: 'drop 2 continues', 44 drop2c: 'drop 2, the peak of the song', 45 drop2d: 'the last bars of drop 2', 46 outro: 'the outro: kick and bass out, the amen thins, the lead starts to fade: the way to the end', 47 final: 'one last kick and crash left to ring while the lead fades out: the end', 48 }), 49 }, 50}) 51 52// how much of each part the section decidedThisSection has: out, back or full 53const level = (part) => choice('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, at half velocity', 56 full: 'as written', 57}) 58const djJev = jev({ 59 state: song, 60 questions: { 61 lead: level('the music-box lead (the tune, on its own in the intro, over everything from the intro until it fades in the outro)'), 62 drums: level('the jungle drums (the amen break with kick and snare in the drops, a thin filtered amen in the tease and builds, hats, crash, and the snare roll and riser into each drop, and the amen alone, thinning, in the outro)'), 63 stabs: level('the rave stabs (in the second half of drop 1 and all of drop 2)'), 64 bass: level('the bass (the reese and sine bassline in the drops, a low sub in the builds: the floor under them)'), 65 vocals: level('the vocals (the My name is Jeff / Type safe Jev hook, only in the builds)'), 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 snare roll rising in pitch into the first beat', 69 riser: 'white noise sweeping up 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: about 5 dB under full', 74 'steady: about 3 dB under full', 75 'driving: about 1.5 dB under full', 76 'all out: full, as written', 77 ]), 78 }, 79}) 80 81// a little chance in the form: the section is drawn from Jev's probabilities 82const { section } = formJev.every(4, { sample: 0.7 }) 83 84// Where each part plays as written. Jev is asked about a part only in these 85// sections; anywhere else it cannot sound, so it is not asked (full). The 86// lead plays in every section. 87const plays = { 88 drums: ['tease', 'build1', 'build1b', 'drop1', 'drop1b', 'drop1c', 'drop1d', 'build2', 89 'drop2', 'drop2b', 'drop2c', 'drop2d', 'outro'], 90 stabs: ['drop1c', 'drop1d', 'drop2', 'drop2b', 'drop2c', 'drop2d'], 91 bass: ['build1', 'build1b', 'drop1', 'drop1b', 'drop1c', 'drop1d', 'breakdown', 'build2', 92 'drop2', 'drop2b', 'drop2c', 'drop2d'], 93 vocals: ['build1', 'build1b', 'build2'], 94} 95const levels = (name) => ({ section: next }) => 96 plays[name] && !plays[name].includes(next) ? ['full'] 97 : name === 'drums' && next === 'outro' ? ['back', 'full'] // the outro is the amen alone 98 : ['out', 'back', 'full'] 99const dj = djJev.every(4, { 100 after: { section }, 101 forget: ['into'], 102 allow: { 103 lead: levels('lead'), drums: levels('drums'), stabs: levels('stabs'), 104 bass: levels('bass'), vocals: levels('vocals'), 105 // out of a build the roll and riser are already written: a cut or a dropout 106 into: ({ playingNow }) => (['build1b', 'build2'].includes(playingNow) 107 ? ['cut', 'dropout'] : ['cut', 'fill', 'riser', 'dropout']), 108 }, 109 fallback: { lead: 'full', drums: 'full', stabs: 'full', bass: 'full', vocals: 'full', into: 'cut', energy: 3 }, 110}) 111 112// A part at Jev's level: silent when out, half velocity when back, as 113// written when full. Each part has its own orbit, named for its question, 114// so the meter hears it alone (measuredSound); the lead and the vocals each 115// get their own delay line there. 116const part = (name, pat) => 117 pat.orbit(name) 118 .mask(dj[name].choice.fmap((c) => c !== 'out')) 119 .mul(velocity(dj[name].choice.fmap((c) => (c === 'back' ? .5 : 1)))) 120 121// bars: 1-8 intro | 9-16 build+hook | 17-32 DROP 1 | 33-36 breakdown 122// 37-40 build+hook | 41-56 DROP 2 | 57-64 outro (each mask = 64 steps) 123const tease1 = "<0!4 1!12 0!48>" // filtered amen, opening from the tease into drop 1 124const tease2 = "<0!36 1!4 0!24>" // and again over build 2 125const hook1 = "<0!8 1!8 0!48>" // vocals ONLY here, in the builds 126const hook2 = "<0!36 1!4 0!24>" 127const drops = "<0!16 1!16 0!8 1!16 0!8>" 128const drop1 = "<0!16 1!16 0!32>" 129const drop2 = "<0!40 1!16 0!8>" 130const stabs = "<0!24 1!8 0!8 1!16 0!8>" 131const lowSub = "<0!8 1!8 0!16 1!8 0!24>" 132const riser = "<0!14 1!2 0!22 1!2 0!24>" 133// the snare roll ducks out under every spoken word: in bar 15 it plays beat 134// 4 only, in bar 16 beat 1 only; in build 2 the words fill bars 39-40, so 135// the noise riser carries that build alone 136const roll = "<0!14 [0 0 0 1] [1 0 0 0] 0!48>" 137const crash = "<0!16 1 0!23 1 0!23>" 138const outroB = "<0!56 1!4 0!4>" 139const final = "<0!60 1 0!3>" 140const leadGain = "<.3!16 .2!16 .3!8 .2!16 .28!4 .2 .14 .08 0>" 141 142const roots = "<c2 ab1 f1 g1>" // F1-C2, 44-65 Hz: a sub that small speakers still carry 143const bassline = "<[c2@3 c2@3 c2@4 c3@2 bb1@4] [ab1@3 ab1@3 ab1@4 ab2@2 g1@4] [f1@3 f1@3 f1@4 f2@2 ab1@4] [g1@3 g1@3 g1@4 b1@2 d2@4]>" 144const 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]>" 145 146// ── parts ───────────────────────────────────────── 147// music-box lead: the tune, fading out at the end 148// each bar outlines its chord; the G bar takes B natural (harmonic minor) 149const lead = n("<[0 ~ 2 ~ 4 ~ 7 ~] [~ 0 ~ 3 ~ 5 ~ 3] [5 ~ 3 ~ 0 ~ -2 ~] [1 ~ 4 ~ 6 ~ 4 ~]>") 150 .scale("<C4:minor!3 C4:harmonic:minor>").s("triangle").decay(.3).sustain(0) 151 .delay(.3).delaytime(.375).delayfeedback(.35).room(.4).gain(leadGain) 152 153// each amen slice cuts the one before (cut group 1); the roll is group 2 154const drums = stack( 155 s("[bd:3,808bd:1]").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.75).mask(drops), 156 s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.15).gain(.9).mask(drops), 157 n(amen).s("amencutup").hpf(150).gain(.75).cut(1).mask(drops), 158 // the filter opens in one sweep from the tease into the drop, and again over build 2 159 n("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15").s("amencutup") 160 .hpf(saw.range(2500,300).slow(12).late(4)).gain(.55).cut(1).mask(tease1), 161 n("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15").s("amencutup") 162 .hpf(saw.range(2500,300).slow(4)).gain(.55).cut(1).mask(tease2), 163 n(amen).s("amencutup").hpf(saw.range(300,3000).slow(4)).gain(.6).cut(1).mask(outroB), 164 s("hh*8").gain(".35 .18").mask(drop1), 165 s("hh*16").gain(".35 .15 .25 .15").mask(drop2), 166 s("hh27:1").gain(2.5).mask(crash), // the crash: Dirt-Samples' cr is a ride 167 // the roll rises across both bars of the build 168 s("sn:3*16").gain(saw.range(.25,.6).slow(2)).speed(saw.range(1,1.4).slow(2)).cut(2).mask(roll), 169 s("white*16").decay(.06).sustain(0) 170 .hpf(saw.range(400,9000).slow(2)).gain(saw.range(.05,.35).slow(2)).mask(riser) 171) 172 173// rave stabs, second half of drop 1 and all of drop 2 174// stab:3 is pitched at C#; these speeds put it on C, Ab, F and G 175const rave = s("stab:3").struct("x ~ ~ x ~ ~ x ~ ~ ~ x ~ ~ ~ ~ ~") 176 .speed("<.944 .749 .63 .707>").room(.3).gain(.45).mask(stabs) 177 178const bass = stack( 179 note(bassline).s("supersaw").detune(.6) 180 .lpf(sine.range(220,900).slow(8)).lpq(3).shape(.3).gain(.5).mask(drops), 181 note(bassline).s("sine").gain(.5).mask(drops), 182 // a little shape, so its second harmonic carries on small speakers 183 note(roots).s("sine").attack(.3).release(.5).shape(.25).gain(.45).mask(lowSub) 184) 185 186// the hook — builds only. Build 1: "my name is jeff … my name is jev, J E V … 187// my name is jeff … type safe jev", then the pickup "type safe … jev" into the 188// drop. Build 2 packs the swap into 4 bars; mnjev runs 2.3 s, so tsjev waits 189// for beat 3 of bar 39. 190const vox = stack( 191 s("<jeff ~ mnjev ~ jeff ~ tsjev [~ ts ~ jev]>").mask(hook1), 192 s("<jeff mnjev [~ ~ tsjev ~] [~ ts ~ jev]>").mask(hook2), 193).gain(1.3).room(.25).delay(.25).delaytime(.375).delayfeedback(.3) 194 195// the end: one last hit, then let it ring. It always plays. 196const ending = stack(s("[bd:3,808bd:1]").gain(.75), s("hh27:1").gain(2.5)) 197 .room(.6).size(.9).mask(final) 198 199const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 200 201// ── the form, as rules in code: which sections may follow which ── 202// `at` is where each starts as written; an empty `next` ends the song. The 203// written order is always allowed, and is what plays without Jev. 204// `times`: each drop's loop goes round once more at most (SPEC.md, "Loops end"). 205const form = walk(section, { 206 max: 28, 207 sections: { 208 intro: { at: 0, next: ['tease'] }, 209 tease: { at: 4, next: ['build1'] }, 210 build1: { at: 8, next: ['build1b'] }, 211 build1b: { at: 12, next: ['drop1'] }, 212 drop1: { at: 16, next: ['drop1b'] }, 213 drop1b: { at: 20, next: ['drop1c'] }, 214 drop1c: { at: 24, next: ['drop1d'], times: 2 }, 215 drop1d: { at: 28, next: ['breakdown', 'drop1c'] }, 216 breakdown: { at: 32, next: ['build2'] }, 217 build2: { at: 36, next: ['drop2'] }, 218 drop2: { at: 40, next: ['drop2b'], times: 2 }, 219 drop2b: { at: 44, next: ['drop2c'] }, 220 drop2c: { at: 48, next: ['drop2d', 'drop2'] }, 221 drop2d: { at: 52, next: ['outro', 'breakdown', 'drop2'] }, 222 outro: { at: 56, next: ['final'] }, 223 final: { at: 60, next: [] }, 224 }, 225}) 226 227// ── the song: Jev's section, parts and energy, composed ── 228// Each section plays its own bars, each part at the level Jev chose. The 229// vocals stand apart so a dropout never cuts them: out of a build, the 230// last "Jev" lands alone in the gap. 231const parts = form.arrange(once(stack( 232 part('lead', lead), 233 part('drums', drums), 234 part('stabs', rave), 235 part('bass', bass), 236))) 237const vocals = form.arrange(once(part('vocals', vox))) 238 239// ── into the next section: over the last bar of each, the next one's answer ── 240// Past the last section there is no next one, so nothing plays there. 241const into = dj.into.choice.early(1) 242const transition = into.pick({ 243 cut: silence, 244 fill: s("sn:3*16").gain(saw.range(.25, 1)).speed(saw.range(1, 1.4)).cut(2).orbit('drums'), 245 riser: s("white*16").decay(.06).sustain(0) 246 .hpf(saw.range(400, 9000)).gain(saw.range(.05, .35)), 247 dropout: silence, 248}).mask("<0!3 1>") 249// dropout: every part but the vocals stops for the section's last beat. The 250// final section has no next section, so its last beat plays. 251const lastBeat = "<0!3 [0 0 0 1]>" 252const gap = stack( 253 "<1!3 [1 1 1 0]>", 254 into.fmap((c) => c !== 'dropout').mask(lastBeat), 255 section.choice.fmap((s) => s === 'final').mask(lastBeat), 256) 257 258// The energy scales every part's velocity together (0.56 to 1: about -5 dB 259// up to as written), so the mix keeps its balance; the last hit is outside 260// it and always plays as written. The master level is outside everything: 261// superdough has no limiter, so it keeps the peaks under -1 dBFS. 262stack( 263 stack(parts.mask(gap), vocals, transition).mul(velocity(dj.energy.score.div(3).range(.56, 1))), 264 form.arrange(once(ending)), 265).mul(postgain(.36))