1// TOO CHEAP TO METER — filter house, 124bpm, 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(124/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: 'TOO CHEAP TO METER', 14 what: 'French filter house celebrating the price of Jev (input tokens 42 dollars per billion, output tokens free) and the jobs it is built for: routing, jailbreak flags, gating commands', 15 tempo: '124 bpm, 4/4, 8 bars a section', 16 key: 'A minor', 17 chords: 'Am7 Am7 D9 D9 Gmaj7 Gmaj7 Cmaj7 Cmaj7, two bars each, as filtered disco stabs', 18 lyrics: { 19 verse1: 'Input tokens, forty-two per billion / Output tokens: free / Seventy to five hundred milliseconds / Too cheap to meter', 20 hook1: 'Output tokens: free / It’s free real estate (four times, the last call Too cheap to meter)', 21 verse2: 'Route the ticket / Flag the jailbreak / Gate the sudo command / Too cheap to meter', 22 hook2: 'Output tokens: free / It’s free real estate (four times, the last call Too cheap to meter)', 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: 'filtered chord stabs and the kick; the filter opens', 31 verse1: 'verse 1: the vocoder sings the price list over claps, hats and the pumping bass', 32 hook1: 'the hook: the robot quotes the price and Tim Heidecker answers, with a white-noise riser into the drop', 33 drop: 'the drop, first half: the filter fully open, sixteenth hats, a crash on the downbeat', 34 drop2: 'the drop, second half: eight more bars of the open filter and sixteenth hats', 35 verse2: 'verse 2: the vocoder sings what Jev is for', 36 hook2: 'the hook again: output tokens free, free real estate', 37 outro: 'the filter closes over four more bars of kick, then the last stab rings: the end', 38 }), 39 }, 40}) 41 42// how much of each part the section decidedThisSection has: out, back or full 43// (a choice, so `allow` below can leave out what a section cannot have) 44const 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', { 45 full: 'full: as written', 46 back: 'back: under the rest of the mix', 47 out: 'out: silent for the section', 48}) 49const djJev = jev({ 50 state: song, 51 questions: { 52 stabs: level('the filtered chord stabs (Am7 D9 Gmaj7 Cmaj7, the disco chords the song is built on: the filter sweeps open in the intro, both verses and both hooks, and stays open in the drop)'), 53 kick: level('the kick (four on the floor, the pulse of the whole song from the intro to the outro)'), 54 drums: level('the claps, hats, crash and white-noise riser (from verse 1 to the last hook, sixteenth hats in the drop, the riser into it)'), 55 bass: level('the bass (pumping octaves over a sine sub on the chord roots, from verse 1 to the last hook)'), 56 vocals: level('the vocals (the vocoder verse lines, and the hook where the robot quotes the price and Tim Heidecker answers)'), 57 into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', { 58 cut: 'straight in, no transition', 59 fill: 'a clap roll, doubling up into the first beat', 60 riser: 'the white-noise riser sweeping up over the last bar', 61 dropout: 'everything stops for the last beat, a gap before the section hits', 62 }), 63 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', [ 64 'held back: about 5 dB down', 65 'steady: about 3 dB down', 66 'driving: just under as written', 67 'all out: as written', 68 ]), 69 }, 70}) 71 72// a little chance in the form: the section is drawn from Jev's probabilities 73const { section } = formJev.every(8, { sample: 0.7 }) 74// Jev is asked only about what a section has (SPEC.md, "The parts" and 75// "Into the next section"); a single option is played without asking. 76const levels = ['full', 'back', 'out'] 77const has = (sections) => (d) => (sections.includes(d.section) ? levels : ['full']) 78const intos = { 79 verse1: ['cut', 'fill'], 80 verse2: ['cut', 'fill'], 81 hook1: ['cut', 'fill', 'riser'], 82 hook2: ['cut', 'fill', 'riser'], 83 drop: ['cut', 'fill', 'dropout'], // the drop's riser is written, at the end of hook 1 84 drop2: ['cut', 'fill'], 85 outro: ['cut', 'dropout'], 86} 87const dj = djJev.every(8, { 88 after: { section }, 89 forget: ['into'], 90 allow: { 91 stabs: (d) => (d.section === 'outro' ? ['full'] : levels), 92 kick: (d) => (d.section === 'outro' ? ['full'] : levels), 93 drums: has(['verse1', 'hook1', 'drop', 'drop2', 'verse2', 'hook2']), 94 bass: has(['verse1', 'hook1', 'drop', 'drop2', 'verse2', 'hook2']), 95 // the words are always sung: in the verses and hooks back or full 96 vocals: (d) => (/^(verse|hook)/.test(d.section) ? ['full', 'back'] : ['full']), 97 into: (d) => intos[d.section] ?? ['cut', 'fill', 'riser', 'dropout'], 98 }, 99 fallback: { stabs: 'full', kick: 'full', drums: 'full', bass: 'full', vocals: 'full', into: 'cut', energy: 3 }, 100}) 101 102// A part at Jev's level: silent when out, half velocity when back. Each 103// part has its own orbit, named for its question, so the meter hears it 104// alone (measuredSound). 105const part = (name, pat) => 106 pat.orbit(name).mask(dj[name].choice.fmap((c) => c !== 'out')) 107 .mul(velocity(dj[name].choice.fmap((c) => (c === 'back' ? .5 : 1)))) 108 109// bars: 1-8 intro | 9-16 verse 1 | 17-24 hook | 25-40 DROP | 41-48 verse 2 110// 49-56 hook | 57-64 outro (each mask = 64 steps) 111const verse1 = "<0!8 1!8 0!48>" 112const verse2 = "<0!40 1!8 0!16>" 113const hooks = "<0!16 1!8 0!24 1!8 0!8>" 114const drop = "<0!24 1!16 0!24>" 115const swept = "<1!24 0!16 1!16 0!8>" // stabs under the 8-bar opening sweep 116const outro = "<0!56 1!6 0!2>" 117const kick = "<1!56 0!8>" // the outro's kick is the ending's 118const outroKick = "<0!56 1!4 0!4>" 119const band = "<0!8 1!48 0!8>" // claps, hats, bass: verse to last hook 120const riser = "<0!23 [0 1] 0!40>" // after the last free real estate ends 121const crash = "<0!24 1 0!39>" 122const lastHit = "<0!62 1 0>" 123 124// two bars per chord: Am7 Am7 D9 D9 Gmaj7 Gmaj7 Cmaj7 Cmaj7 125const chords = "<[a3,c4,e4,g4]!2 [d3,f#3,a3,c4,e4]!2 [g3,b3,d4,f#4]!2 [c4,e4,g4,b4]!2>" 126const roots = "<33!2 38!2 31!2 36!2>" // A1 D2 G1 C2, as MIDI numbers 127const chop = "~ x ~ x ~ ~ x ~ ~ x ~ x ~ ~ x ~" 128 129// ── parts ───────────────────────────────────────── 130// chord stabs: the low-pass opens over each 8-bar phrase (saw.slow(8) 131// restarts every 8 bars), then stays fully open in the drop 132const stabs = stack( 133 note(chords).struct(chop).s("supersaw").detune(.25).decay(.18).sustain(0) 134 .lpf(saw.range(300, 3200).slow(8)).lpq(6).room(.25).gain(.4).mask(swept), 135 // open, a touch louder: it has no resonant peak to lift it 136 note(chords).struct(chop).s("supersaw").detune(.25).decay(.18).sustain(0) 137 .lpf(5000).lpq(3).room(.25).gain(.44).mask(drop) 138) 139 140// the ending: the filter closes over the outro and its four bars of kick, 141// and the last stab rings. It always plays: the outro's parts are not Jev's. 142const ending = stack( 143 note(chords).struct(chop).s("supersaw").detune(.25).decay(.18).sustain(0) 144 .lpf(saw.range(3200, 250).slow(8)).lpq(6).room(.3).gain(.4).mask(outro).orbit('stabs'), 145 note(chords).s("supersaw").detune(.25).decay(2.5).sustain(0) 146 .lpf(1800).room(.7).size(.9).gain(.5).mask(lastHit).orbit('stabs'), 147 s("bd:3*4").gain(.6).mask(outroKick).orbit('kick') 148) 149 150// four on the floor, and the claps, hats, crash and riser around it 151const fourOnFloor = s("bd:3*4").gain(.6).mask(kick) 152// the fill replaces the backbeat clap on the last beat of its bar 153const into = dj.into.choice.early(1) 154const lastBeat = "<0!7 [0 0 0 1]>" 155const noFillClap = stack("<1!7 [1 1 1 0]>", into.fmap((c) => c !== 'fill').mask(lastBeat)) 156const drums = stack( 157 s("~ cp ~ cp").room(.15).gain(.7).mask(band).mask(noFillClap), 158 s("[~ 808oh]*4").gain(.28).mask(band), 159 s("hh*16").gain(".2 .08").mask(drop), 160 s("hh27:1").gain(1.5).mask(crash), // hh27:1 is the crash; `cr` is a ride 161 s("white*16").decay(.06).sustain(0) 162 .hpf(saw.range(500, 9000)).gain(saw.range(.05, .3)).mask(riser) 163) 164 165// pumping octaves, off-beats louder, over a sine sub 166const bass = stack( 167 note("[0 12]*4".add(roots)).s("sawtooth").lpf(800).decay(.12).sustain(.3) 168 .gain(".3 .5").mask(band), 169 note(roots).struct("x x x x").s("sine").gain(.45).mask(band) 170) 171 172const vox = stack( 173 // verses: vocoder, one line per 2 bars, each sung on its chord 174 s("<tcinput ~ tcfree ~ tcseventy ~ tcmeter ~>").gain(1.1).room(.2).mask(verse1), 175 s("<tcroute ~ tcflag ~ tcgate ~ tcmeter ~>").gain(1.1).room(.2).mask(verse2), 176 // hook: the robot quotes the price, Tim Heidecker answers 177 s("<tcfreeam tcrealestate tcfree tcrealestate tcfreeg tcrealestate tcmeter tcrealestate>") 178 .gain(1).room(.2).mask(hooks) 179) 180 181const MASTER = .6 // the master level: measured, SPEC.md "Style" 182const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 183 184// ── the form, as rules in code: which sections may follow which ── 185// `at` is where each starts as written; an empty `next` ends the song. The 186// written order is always allowed, and is what plays without Jev. 187const form = walk(section, { 188 max: 16, 189 sections: { 190 intro: { at: 0, next: ['verse1'] }, 191 verse1: { at: 8, next: ['hook1'] }, 192 hook1: { at: 16, next: ['drop'] }, 193 drop: { at: 24, next: ['drop2'] }, 194 drop2: { at: 32, next: ['verse2'] }, // every path sings verse 2 before the outro 195 verse2: { at: 40, next: ['hook2'] }, 196 hook2: { at: 48, next: ['outro', 'drop', 'verse1'] }, 197 outro: { at: 56, next: [] }, 198 }, 199}) 200 201// ── the song: Jev's section, parts and energy, composed ── 202// Each section plays its own bars, each part at the level Jev chose; the 203// energy scales every part's velocity together, so the mix keeps its 204// balance. The ending always plays. 205const parts = form.arrange(once(stack( 206 part('stabs', stabs), 207 part('kick', fourOnFloor), 208 part('drums', drums), 209 part('bass', bass), 210 part('vocals', vox), 211))) 212 213// ── into the next section: over the last bar of each, the next one's answer ── 214// Past the last section there is no next one, so nothing plays there. The 215// transitions have their own orbit: no part's level controls them. 216const transition = into.pick({ 217 cut: silence, 218 fill: s("[~ cp] [~ cp] [cp cp] [cp cp cp cp]").room(.15).gain(saw.range(.4, .8)), 219 riser: s("white*16").decay(.06).sustain(0) 220 .hpf(saw.range(500, 9000)).gain(saw.range(.05, .3)), 221 dropout: silence, 222}).mask("<0!7 1>").orbit('into') 223// dropout: the parts stop for the section's last beat. The outro has no 224// next section, so its last beat plays. 225const gap = stack( 226 "<1!7 [1 1 1 0]>", 227 into.fmap((c) => c !== 'dropout').mask(lastBeat), 228 section.choice.fmap((s) => s === 'outro').mask(lastBeat), 229) 230 231// energy scales velocity (SPEC.md: about -5, -3, -1.4 and 0 dB); the master 232// level keeps the loudest pick under -1 dBFS (superdough has no limiter) 233stack(parts.mask(gap), transition, form.arrange(once(ending))) 234 .mul(velocity(dj.energy.score.div(3).range(.55, 1))) 235 .mul(postgain(MASTER))