1// OPTION ORDER — glitch-hop, 100bpm, 64 bars as written; Jev arranges up to 16 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(100/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 10// it, how the song moves into it, and how hard. Every 8 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: 'OPTION ORDER', 14 what: 'glitch-hop about Jev answers flipping when only the order of the options changes: the same question, the options swapped, the other verdict', 15 tempo: '100 bpm, swung half-time, 8 bars a section', 16 key: 'D minor', 17 chords: 'Dm Bb F C, two bars each: an 8-bar loop, carried by the bass and short stabs', 18 lyrics: { 19 chorus: 'Fair or unfair? / Fair / Unfair or fair? / Unfair / Approve or fix now? / Approve / Fix now or approve? / Fix now', 20 bridge: 'Thirty-two out of two hundred / thir- thir- thir- thirty-two out of two hundred', 21 hook: 'Wait, it is all shuffled? / Always has been', 22 outro: 'the chorus again with every answer swapped: Fair or unfair? / Unfair / Unfair or fair? / Fair / Approve or fix now? / Fix now / Fix now or approve? / Approve', 23 }, 24} 25 26const formJev = jev({ 27 state: song, 28 questions: { 29 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', { 30 intro: 'the half-time kit and the wobble bass through a closed filter', 31 chorus: 'the vocoder chorus: a question with its options one way and its answer, then the options swapped and the other answer', 32 drop1: 'drop 1, first half: swung drums, the amen break straight, full wobble bass and stabs', 33 drop1b: 'drop 1, second half: the same groove on the break\'s other half, with a written stutter every other bar', 34 bridge: 'the bridge, the build: a stuttering break, the count on Dm, then a stuttered repeat on F, the bass filter opening across it', 35 hook: 'the meme hook, then drop 2 lands with the break reversed every other bar', 36 drop2: 'drop 2: the peak, the break alternating forwards and reversed, the bass doubled an octave up, the stabs wide open', 37 outro: 'the chorus once more with every answer swapped, as the drums thin out to a last hit: the end', 38 }), 39 }, 40}) 41 42// how much of each part the section decidedThisSection has: out, back or full 43const 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', [ 44 'out: silent for the section', 45 'back: half velocity, under the rest of the mix', 46 'full: as written', 47]) 48const djJev = jev({ 49 state: song, 50 questions: { 51 drums: level('the drums (the heavy swung half-time kit, in every section: through a closed filter in the intro; full in the chorus, drop1, drop1b and the bridge; only hats under the meme in the first half of the hook, then full through drop2; thinning out to a last hit in the outro)'), 52 breaks: level('the amen break (chopped over the kit, in drop1, drop1b, the bridge, the second half of the hook and drop2: straight in drop1, the break\'s other half with a stutter in drop1b, stuttering and dropping back under the count in the bridge, reversed every other bar in drop2; nothing in the intro, the chorus or the outro)'), 53 bass: level('the wobble bass (the chord roots under a wobbling filter, in every section but the outro: closed in the intro, full in the chorus, drop1 and drop1b, its filter opening across the bridge, in the second half of the hook only, doubled an octave up on the offbeats in drop2)'), 54 stabs: level('the chord stabs (short swung chords, the only harmony, in the chorus, drop1, drop1b, the second half of the hook and drop2, where they open fully; nothing in the intro, the bridge or the outro)'), 55 vocals: level('the bridge vocals (the count, thirty-two out of two hundred, and its stutter: in the bridge only; the chorus and hook lines always play)'), 56 into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', { 57 cut: 'straight in, no transition', 58 fill: 'a snare-and-clap roll doubling up across the last bar', 59 riser: 'white noise swelling up over the last bar', 60 dropout: 'everything stops for the last beat, a gap before the section hits', 61 }), 62 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', [ 63 'held back: 0.55 of full velocity, room to breathe', 64 'steady: 0.7 of full velocity', 65 'driving: 0.85 of full velocity', 66 'all out: full velocity, nothing held back', 67 ]), 68 }, 69}) 70 71// a little chance: the section is drawn from Jev's probabilities, and so is the transition 72const { section } = formJev.every(8, { sample: 0.7 }) 73const dj = djJev.every(8, { 74 after: { section }, 75 forget: ['into'], 76 sample: { into: 1 }, 77 fallback: { drums: 2, breaks: 2, bass: 2, stabs: 2, vocals: 2, into: 'cut', energy: 3 }, 78}) 79 80// A part at Jev's level. The score is rounded to its label, and the label 81// gives both the mask and the velocity: out (below 0.5) is silent, back 82// (0.5 to 1.5) plays at .5, full (from 1.5) as written. Each part has its 83// own orbit, named for its question, so the meter hears it alone. 84const rung = (score) => score.fmap((x) => (x < .5 ? 0 : x < 1.5 ? .5 : 1)) 85const part = (name, pat) => { 86 const v = rung(dj[name].score) 87 return pat.orbit(name).mask(v.fmap((x) => x > 0)).mul(velocity(v)) 88} 89 90// bars: 1-8 intro | 9-16 chorus | 17-24 DROP 1 | 25-32 drop 1b | 33-40 bridge 91// 41-44 hook | 45-56 DROP 2 | 57-64 outro (chorus, answers swapped) (each mask = 64 steps) 92const intro = "<1!8 0!56>" 93const chorus = "<0!8 1!8 0!48>" 94const groove = "<0!8 1!32 0!4 1!12 0!8>" 95const wobbles = "<0!8 1!24 0!12 1!12 0!8>" // the groove without the bridge 96const drop1 = "<0!16 1!8 0!40>" 97const drop1b = "<0!24 1!8 0!32>" 98const bridge = "<0!32 1!8 0!24>" 99const hook = "<0!40 1!4 0!20>" 100const drop2 = "<0!44 1!12 0!8>" 101const stabs = "<0!8 1!24 0!12 1!12 0!8>" 102const outro = "<0!56 1!8>" 103const outroDr = "<0!56 1!6 0!2>" 104const crash = "<0!16 1 0!27 1 0!19>" 105const stutter = "<0!36 1 0!27>" 106const final = "<0!63 1>" 107 108// two bars per chord: Dm Dm Bb Bb F F C C 109const chords = "<[d3,f3,a3]!2 [bb2,d3,f3]!2 [f3,a3,c4]!2 [c3,e3,g3]!2>" 110const roots = "<38!2 34!2 41!2 36!2>" // D2 Bb1 F2 C2, as MIDI numbers 111const wobbleRate = "<4 4 8 4 2 4 8 16>" 112// the wobble, struck in 32nds so every rate moves: a cosine read 32 times a 113// bar is never read only at its zero crossings, as a sine at 16ths was 114const wobble = (lo, hi) => cosine.range(lo, hi).fast(wobbleRate) 115 116// the whole kit swings together: odd 16ths a third late 117const swing = (p) => p.swingBy(1/3, 8) 118const kickHits = "x ~ ~ ~ ~ ~ ~ x ~ ~ x ~ ~ ~ ~ ~" 119// the half-time snare, with a written-out roll closing every 4th bar 120const snare = "<[~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~]!3 [~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ [x x] [x x] [x x x x] [x x x x]]>" 121// the break: forwards, and bar-for-bar reversed (slices in reverse order, each played backwards) 122const fwd = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" 123// drop 1b: the break's other half, with a written ply stutter on beat 4 of every 2nd bar 124const half2 = "<[16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31] [16 17 18 19 20 21 22 23 24 25 26 27 [28 28] [29 29] [30 30 30 30] [31 31 31 31]]>" 125 126// ── parts ───────────────────────────────────────── 127// drums: heavy, swung half-time 128const kit = stack( 129 swing(s("[bd:3,808bd:1]").struct(kickHits)).gain(.6).mask(groove), 130 swing(s("[sn:3,cp]").struct(snare)).room(.2).gain(.85).mask(groove), 131 swing(s("hh*16").gain(".3 .1 .2 .1")).mask(groove), 132 swing(s("hh*16").gain(".25 .05 .12 .05")).mask(hook), 133 // intro: the same kit through a closed filter 134 swing(s("[bd:3,808bd:1]").struct(kickHits)).lpf(500).gain(.6).mask(intro), 135 swing(s("[sn:3,cp]").struct(snare)).lpf(900).gain(.6).mask(intro), 136 // outro: kick and snare thin out, then stop 137 s("[bd:3,808bd:1]").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~").gain(.6).mask(outroDr), 138 s("sn:3").struct("~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~").room(.4).gain(.6).mask(outroDr), 139 // hh27:1 is a crash (Dirt-Samples' cr is a ride), about 15 dB quieter than the ride was 140 s("hh27:1").gain(2.8).mask(crash) 141) 142 143// the break, each slice cut by the next: straight in drop 1, its other half 144// in drop 1b, reversed every other bar in drop 2 145const brk = stack( 146 n(fwd).s("amencutup").gain(.6).mask(drop1), 147 n(half2).s("amencutup").gain(.6).mask(drop1b), 148 n("<[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] [15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0]>").s("amencutup").speed("<1 -1>").gain(.6).mask(drop2), 149 // bridge: the break with written-out stutters, dropping back while the count speaks (bars 33 and 37) 150 n("0 1 2 3 [4 4 4 4] 5 6 7 8 9 [10 10] 11 [12 12 12 12] 13 [14 14 14] 15") 151 .s("amencutup").gain("<.6!32 .2 .6 .6 .6 .2 .6 .6 .6 .6!24>").mask(bridge) 152).hpf(200).cut(2) 153 154// bass: wobble, struck in 32nds so the filter LFO moves in every bar 155const bass = stack( 156 note(roots).struct("x*32").s("sawtooth").lpf(wobble(250, 1600)) 157 .lpq(8).shape(.4).gain(.35).mask(wobbles), 158 // bridge: the same wobble with its filter opening over the 8 bars 159 note(roots).struct("x*32").s("sawtooth").lpf(wobble(150, 450).add(saw.slow(8).range(0, 2200))) 160 .lpq(8).shape(.4).gain(.35).mask(bridge), 161 note(roots).struct("x*32").s("sawtooth").lpf(wobble(150, 500)) 162 .lpq(6).gain(.3).mask(intro), 163 note(roots).s("sine").gain(.35).mask(groove), 164 // drop 2: the bass doubled an octave up on the offbeats 165 note(roots).add(note(12)).struct("[~ x]*4").s("sawtooth").lpf(2200) 166 .decay(.12).sustain(0).gain(.22).mask(drop2) 167) 168 169// harmony: swung stabs on odd 16ths, never a pad; fully open in drop 2 170const harmony = swing(note(chords).struct("~ ~ ~ x ~ ~ ~ x ~ ~ ~ x ~ ~ ~ ~")) 171 .s("sawtooth").lpf("<2500!44 6000!12 2500!8>").decay(.25).sustain(0).room(.3).gain(.85).mask(stabs) 172 173// the bridge count: Jev's to level 174const vox = stack( 175 // bridge: the count on Dm, then a written-out stutter of it on F 176 s("<oothirtydm ~ ~ ~ ~ ~ ~ ~>").gain(1.1).room(.2).mask(bridge), 177 s("[oothirtyf oothirtyf oothirtyf] oothirtyf@3").end("[.1 .1 .1] 1@3").cut(4) 178 .gain(1.1).room(.2).mask(stutter), 179) 180 181// the joke, which always plays: each question on its chord's first bar, the 182// answer on its second. The chorus answers in the options' order; the outro 183// asks the same questions and gets the other answers. 184const lines = stack( 185 s("<ooqfair ooafairdm ooqunfair ooaunfairbb ooqapprove ooaapprovef ooqfix ooafixc>") 186 .gain(1.1).room(.25).mask(chorus), 187 s("<ooqfair ooaunfairdm ooqunfair ooafairbb ooqapprove ooafixf ooqfix ooaapprovec>") 188 .gain(1.1).room(.35).mask(outro), 189 // hook: the astronaut asks, the other answers 190 s("<ooshuffled ~ ooalways ~>").gain(1.2).room(.3).mask(hook), 191).orbit('vocals') 192 193// the end: kick, sub kick and crash on the last bar. It always plays, in full. 194const ending = stack( 195 s("[bd:3,808bd:1]").gain(.6), 196 s("hh27:1").gain(2.8), 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. Sections 204// are 8-bar windows of the song as written, so `hook` holds the 4-bar hook 205// and the first 4 bars of drop 2. 206const form = walk(section, { 207 max: 16, 208 sections: { 209 intro: { at: 0, next: ['chorus'] }, 210 chorus: { at: 8, next: ['drop1'] }, 211 drop1: { at: 16, next: ['drop1b'] }, 212 drop1b: { at: 24, next: ['bridge', 'chorus'] }, 213 bridge: { at: 32, next: ['hook'] }, 214 hook: { at: 40, next: ['drop2'] }, 215 drop2: { at: 48, next: ['outro', 'chorus', 'drop1', 'bridge'] }, 216 outro: { at: 56, next: [] }, 217 }, 218}) 219 220// ── the song: Jev's section, parts and energy, composed ── 221// Each section plays its own bars, each part at the level Jev chose; the 222// energy scales every part's velocity together, so the mix keeps its 223// balance. The joke's lines and the final hit always play, outside it. 224const parts = form.arrange(once(stack( 225 part('drums', kit), 226 part('breaks', brk), 227 part('bass', bass), 228 part('stabs', harmony), 229 part('vocals', vox), 230))) 231 232// ── into the next section: over the last bar of each, the next one's answer ── 233// Past the last section there is no next one, so nothing plays there. Out of 234// the chorus, only the last two beats, so the last answer is heard. 235const into = dj.into.choice.early(1) 236const transition = into.pick({ 237 cut: silence, 238 fill: swing(s("[sn:3,cp]").struct("x ~ x ~ x x x x [x x] [x x] [x x] [x x] [x x x x] [x x x x] [x x x x] [x x x x]")) 239 .gain(saw.range(.35, .9)).room(.2).orbit('drums'), 240 riser: s("white*16").hpf(saw.range(400, 8000)).gain(saw.range(.05, .35)).decay(.07).sustain(0), 241 dropout: silence, 242}).mask(stack( 243 // the last two beats always; the two before them unless leaving the chorus 244 // (the halves never overlap, so no hit plays twice) 245 "<0!7 [0 1]>", 246 section.choice.fmap((s) => s !== 'chorus').mask("<0!7 [1 0]>"), 247)) 248// dropout: the parts stop for the section's last beat. The outro has no 249// next section, so its last beat plays. 250const lastBeat = "<0!7 [0 0 0 1]>" 251const gap = stack( 252 "<1!7 [1 1 1 0]>", 253 into.fmap((c) => c !== 'dropout').mask(lastBeat), 254 section.choice.fmap((s) => s === 'outro').mask(lastBeat), 255) 256 257// the master level keeps the peak at or under -1 dBFS (SPEC.md, Constraints) 258stack( 259 stack(parts.mask(gap), transition).mul(velocity(dj.energy.score.div(3).range(.55, 1))), 260 form.arrange(once(stack(lines, ending))), 261).mul(postgain(.36))