1// EMERGENCY MEETING — dark drum & bass, 174bpm, 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(174/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: 'EMERGENCY MEETING', 14 what: 'dark drum and bass in which Jev calls the emergency meeting on a jailbroken agent: curl, pipe, bash, remote code at point nine nine, denied, and Jev is not the impostor', 15 tempo: '174 bpm, 4/4, 4 bars a section', 16 key: 'F minor', 17 chords: 'Fm Fm Db Db Ab Ab Eb Eb, two bars each, on the sub and the reese', 18 lyrics: { 19 intro2: 'Jev is not the impostor', 20 build1a: 'Boi, why are you running?', 21 build1b: 'Boi, why are you running? / (the emergency meeting alarm) / sus', 22 verseA: 'Curl, pipe, bash / Remote code, point nine nine', 23 verseB: 'Denied / Jev is not the impostor', 24 build2: '(the emergency meeting alarm) / Boi, why are you running? / sus', 25 outro: 'Jev is not the impostor', 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: 'a sub pulse (a sine and its octave), hi-hats coming in', 34 intro2: 'the sub pulse and hats, a whispered line on the Eb bar', 35 build1a: 'build 1: a kick and hats over the pulse, the first meme', 36 build1b: 'build 1, second half: a snare roll, the Among Us alarm, and sus, into the drop', 37 drop1a: 'drop 1: the reese bass, two-step kick and snare, a filtered amen break, a crash', 38 drop1b: 'drop 1 continues', 39 drop1c: 'drop 1 continues', 40 drop1d: 'drop 1, last four bars', 41 verseA: 'the verse: half-time drums, the sub pulse, whispered vocoder lines', 42 verseB: 'the verse, second half', 43 build2: 'build 2: the alarm, the running meme, a short snare roll, sus, into the big drop', 44 drop2a: 'drop 2, the peak: the reese driven harder, sixteenth hats, a crash', 45 drop2b: 'drop 2 continues', 46 drop2c: 'drop 2 continues', 47 drop2d: 'drop 2, last four bars', 48 outro: 'the sub pulse, the whispered line once more, and a last hit: the end', 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 pulse: level('the sub pulse (a sine pulse on the chord roots, alone in the intro, under the builds, the verse and the outro; the drops have the reese instead)'), 63 reese: level('the reese (the detuned low-passed reese bass that is the drops, opening across drop 1, brighter and driven harder in drop 2)'), 64 drums: level('the drums (hats in the intro, kick and hats and snare rolls in the builds, the two-step kick, snare and filtered amen break in the drops, half-time in the verse)'), 65 memes: level('the memes (the Among Us alarm, why are you running and sus, in the builds only)'), 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 chopped amen fill, stuttering into the first beat', 69 riser: 'the reese on the chord root, its filter sweeping 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: about 5 dB under full', 74 'steady: about 3 dB under full', 75 'driving: about 1.5 dB under full', 76 'all out: full volume, 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// A transition only at a real boundary (SPEC.md, "Into the next section"): 84// a section that continues the one before is always a cut, and a drop is 85// entered by cut or dropout only, since a riser or fill would double the build. 86const intoAllowed = { 87 drop1a: ['cut', 'dropout'], drop2a: ['cut', 'dropout'], 88 verseA: ['cut', 'fill', 'riser', 'dropout'], build1a: ['cut', 'fill', 'riser', 'dropout'], 89 build2: ['cut', 'fill', 'riser', 'dropout'], outro: ['cut', 'fill', 'riser', 'dropout'], 90} 91const dj = djJev.every(4, { 92 after: { section }, 93 forget: ['into'], 94 fallback: { pulse: 2, reese: 2, drums: 2, memes: 2, into: 'cut', energy: 3 }, 95 allow: { into: ({ section }) => intoAllowed[section] ?? ['cut'] }, 96}) 97// the transition into the next section, heard over the last bar of this one 98const into = dj.into.choice.early(1) 99// the drops' break stops for a fill's bar, so the fill replaces it 100const noFill = stack("<1!3 0>", into.fmap((c) => c !== 'fill').mask("<0!3 1>")) 101 102// A part at Jev's level: silent when out, otherwise its velocity scaled by 103// the level. Each part has its own orbit, named for its question, so the 104// meter hears it alone (measuredSound). 105const part = (name, pat) => 106 pat.orbit(name).mask(dj[name].score.round().gt(0)).mul(velocity(dj[name].score.round().div(2))) 107 108// bars: 1-8 intro | 9-16 build 1 | 17-32 DROP 1 | 33-40 verse | 41-44 build 2 109// 45-60 DROP 2 | 61-64 outro (each mask = 64 steps) 110const hatsIn = "<0!2 1!6 0!56>" 111const builds = "<0!8 1!8 0!24 1!4 0!20>" 112const drops = "<0!16 1!16 0!12 1!16 0!4>" 113const drop1 = "<0!16 1!16 0!32>" 114const drop2 = "<0!44 1!16 0!4>" 115const verse = "<0!32 1!8 0!24>" 116const roll1 = "<0!12 1!4 0!48>" 117const roll2 = "<0!42 1!2 0!20>" 118const running = "<0!8 1 0!3 1 0!28 1 0!22>" 119const alarm = "<0!14 1 0!25 1 0!23>" 120const sus = "<0!15 1 0!27 1 0!20>" 121const crash = "<0!16 1 0!27 1 0!19>" 122const introWhisper = "<0!6 1 0!57>" // bar 7, an Eb bar: the story 123const lastWhisper = "<0!62 1 0>" // bar 63, an Eb bar: the ending 124const lastHit = "<0!63 1>" 125 126// two bars per chord: Fm Fm Db Db Ab Ab Eb Eb 127const roots = "<29!2 25!2 32!2 27!2>" // F1 Db1 Ab1 Eb1, as MIDI numbers 128const 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]>" 129 130// ── parts ───────────────────────────────────────── 131// intro / builds / verse / outro: a sine pulse on the roots, and its octave 132// on a filtered triangle so laptop speakers hear it (the sine is 35-52 Hz) 133const pulse = stack( 134 note(roots).s("sine").gain(.7), 135 note(roots).add(note(12)).s("triangle").lpf(400).gain(.7 * .35), 136).struct("x ~ ~ ~ x ~ ~ ~").decay(.4).sustain(.3) 137 .mask("<1!16 0!16 1!8 1!4 0!16 1!3 0>") 138 139// drops: detuned reese, low-passed, opening across drop 1 (cycles 16-32); 140// drop 2 starts brighter and opens further (cycles 44-60) 141const reese = stack( 142 note(roots).struct("x@8 x@4 x@4").s("supersaw").detune(.8) 143 .lpf(saw.range(250, 900).slow(16)).lpq(4).shape(.3).gain(.45).mask(drop1), 144 note(roots).struct("x@8 x@4 x@4").s("supersaw").detune(.8) 145 .lpf(saw.range(800, 1800).slow(16).late(44)).lpq(4).shape(.3).gain(.45).mask(drop2), 146 note(roots).struct("x@8 x@4 x@4").s("supersaw").detune(.9) 147 .lpf(1100).shape(.6).gain(.2).mask(drop2), 148 note(roots).struct("x@8 x@4 x@4").s("sine").gain(.65).mask(drops) 149) 150 151const drums = stack( 152 s("hh*8").gain(".22 .1").mask(hatsIn), 153 s("hh*8").gain(".28 .12").mask(builds), 154 s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~").gain(.8).mask(builds), 155 // one crescendo across each build's roll 156 s("sn:3*16").gain(saw.range(.2, .8).slow(4).late(12)).mask(roll1), 157 s("sn:3*16").gain(saw.range(.2, .8).slow(2).late(42)).mask(roll2), 158 159 // drops: two-step kick, snare + rimshot, filtered amen, sparse metal 160 s("[bd:3,808bd:1]").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.6).mask(drops), 161 s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.2).gain(.85).mask(drops), 162 s("rm").struct("~ ~ x ~ ~ ~ ~ x ~ x ~ ~ ~ ~ x ~").gain(.45).mask(drops), 163 n(amen).s("amencutup").hpf(400).gain(.5).mask(drops).mask(noFill), 164 s("hh*8").gain(".3 .14").mask(drop1), 165 s("hh*16").gain(".32 .12 .22 .12").mask(drop2), 166 s("metal").struct("<[~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~] ~>").speed(.5).room(.6).gain(.35).mask(drops), 167 s("hh27:1").gain(2.7).mask(crash), // the crash (cr is a ride) 168 169 // verse: half-time 170 s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.8).mask(verse), 171 s("sn:3").struct("~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~").room(.3).gain(.8).mask(verse), 172 s("rm").struct("~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~").gain(.35).mask(verse) 173) 174 175// the story: the verse on its chords, and the intro's whispered line. Not 176// one of Jev's parts: it always plays (SPEC.md, "The story"). Per-line gain 177// levels the lines (emremote is quieter; emnotimp is -19.8 LUFS) 178const whisperLine = s("emnotimp").gain(1.5).room(.5).delay(.3).delaytime(.345).delayfeedback(.35) 179const story = stack( 180 s("<emcurl ~ emremote ~ emdenied ~ emnotimp ~>").gain("<1.15 ~ 1.32 ~ 1.2 ~ 1.6 ~>").room(.3).mask(verse), 181 whisperLine.mask(introWhisper) 182).orbit('story') 183 184// builds only: the memes 185const memes = stack( 186 s("emrunning").gain(1.2).room(.2).mask(running), 187 s("emalarm").gain(1).mask(alarm), 188 s("emsus").gain(1.25).room(.3).mask(sus) 189) 190 191// the whispered line on the last Eb bar and the last hit. They always 192// play, so the song always ends on them. The line rides the energy as the 193// story does; the hit rides it with the band. 194const lastLine = whisperLine.mask(lastWhisper).orbit('ending') 195const ending = stack(s("bd:3").gain(.6), s("hh27:1").gain(2.7)) 196 .room(.7).size(.9).mask(lastHit).orbit('lasthit') 197 198const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 199 200// ── the form, as rules in code: which sections may follow which ── 201// `at` is where each starts as written; an empty `next` ends the song. The 202// written order is always allowed, and is what plays without Jev. 203// `times`: drop 1 goes round once more at most (SPEC.md, "Loops end"). 204const form = walk(section, { 205 max: 24, 206 sections: { 207 intro1: { at: 0, next: ['intro2'] }, 208 intro2: { at: 4, next: ['build1a'] }, 209 build1a: { at: 8, next: ['build1b'] }, 210 build1b: { at: 12, next: ['drop1a'] }, 211 drop1a: { at: 16, next: ['drop1b'], times: 2 }, 212 drop1b: { at: 20, next: ['drop1c'] }, 213 drop1c: { at: 24, next: ['drop1d', 'verseA'] }, 214 drop1d: { at: 28, next: ['verseA', 'drop1a'] }, 215 verseA: { at: 32, next: ['verseB'] }, 216 verseB: { at: 36, next: ['build2', 'build1b'] }, 217 build2: { at: 40, next: ['drop2a'] }, 218 drop2a: { at: 44, next: ['drop2b'] }, 219 drop2b: { at: 48, next: ['drop2c'] }, 220 drop2c: { at: 52, next: ['drop2d', 'outro'] }, 221 drop2d: { at: 56, next: ['outro', 'verseA', 'build2', 'drop1a'] }, 222 outro: { at: 60, next: [] }, 223 }, 224}) 225 226// ── the song: Jev's section, parts and energy, composed ── 227// Each section plays its own bars, each part at the level Jev chose; the 228// energy scales every part's velocity together, so the mix keeps its 229// balance. The ending always plays. 230const parts = form.arrange(once(stack( 231 part('pulse', pulse), 232 part('reese', reese), 233 part('drums', drums), 234 part('memes', memes), 235))) 236 237// ── into the next section: over the last bar of each, the next one's answer ── 238// Past the last section there is no next one, so nothing plays there. 239const transition = into.pick({ 240 cut: silence, 241 // unlike the break's own fourth bar: thicker toward beat 4 242 fill: n("0 2 4 6 [8 8] [10 10] [12 12] [12 12] [14 14 14] [14 14 14] [30 30 30] [30 30 30] [31 31 31 31] [31 31 31 31] [31 31 31 31] [31 31 31 31]").s("amencutup") 243 .hpf(300).gain(.6).orbit('drums'), 244 // on the root of the section it leaves 245 riser: form.arrange(once(note(roots).struct("x*8").s("supersaw").detune(.8) 246 .lpf(saw.range(200, 3000)).lpq(6).shape(.3).gain(saw.range(.15, .4)))).orbit('reese'), 247 dropout: silence, 248}).mask("<0!3 1>") 249// dropout: the parts stop for the section's last beat. The outro has no 250// 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 === 'outro').mask(lastBeat), 256) 257 258// energy: held back is about 5 dB under full, all out is as written. The 259// story rides only the top of it. The master gain is set last, from the 260// measured peak at full energy (SPEC.md, Style: "Mix"). 261const MASTER = .47 262stack( 263 stack(parts.mask(gap), transition, form.arrange(once(ending))) 264 .mul(velocity(dj.energy.score.div(3).range(.55, 1))), 265 form.arrange(once(stack(story, lastLine))).mul(velocity(dj.energy.score.div(3).range(.85, 1))), 266).mul(postgain(MASTER))