1// SELECT * FROM JEV — synthwave, 90bpm, 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(90/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: 'SELECT * FROM JEV', 14 what: 'synthwave about Jev inside SQL: a vocoder robot sings the query plan, select star from Jev where vibes are true', 15 tempo: '90 bpm, 8 bars a section', 16 key: 'F# minor; keyUp and outro are up a tone, in G# minor, however many of them play', 17 chords: 'F#m F#m D D A A E E, two bars each, arpeggiated in sixteenths; the song ends on G#m', 18 lyrics: { 19 verse1: 'Select star from Jev / Where vibes are true / Custom scan / Warm pool, cached answers', 20 chorus1: 'Select star from Jev / Where vibes are true / Hello there / General Kenobi / Where vibes are true', 21 verse2: 'Order by probability / Forty rows a batch / Two hundred fifty-five options / Then a label tree', 22 chorus2: 'Select star from Jev / Where vibes are true / Hello there / General Kenobi / Where vibes are true', 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 arpeggio alone, its filter opening, then the drums come in', 31 verse1: 'verse 1: the vocoder sings the query plan over arps, pulsing bass and gated snare', 32 chorus1: 'the big chorus: the SQL lines and the Hello there / General Kenobi hook, with a clap, sixteenth hats, the arpeggio opened up and the bright saw lead', 33 instrumental: 'an instrumental: the saw lead up front', 34 verse2: 'verse 2: more of the query plan', 35 chorus2: 'the big chorus again, the SQL lines and the Hello there / General Kenobi hook', 36 keyUp: 'the key change: the instrumental up a tone, the lead up front, no vocals', 37 outro: 'the last of the key change, then the arpeggio with the kick and bass fading under it, resolving to a final gated snare and crash: 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: under the rest of the mix', 46 'full: as written', 47]) 48const djJev = jev({ 49 state: song, 50 questions: { 51 arp: level('the arpeggio (sixteenth-note saw arps on the chords, the song\'s bed: filtered in the intro and opened up in the choruses, under every section as written, carrying the outro)'), 52 lead: level('the saw lead (the bright melody: at full in the choruses and up front in the instrumental and the key change)'), 53 bass: level('the bass (pulsing eighth-note chord roots with a sine sub, the floor under every section from verse 1 until the song\'s last bar)'), 54 drums: level('the drums (kick, gated snare and hats from bar 5 on, with a clap and sixteenth hats in the choruses; in the outro only the kick is left)'), 55 vocals: level('the vocoded SQL lines (verses and choruses only; nothing is sung in the intro, instrumental, key change or outro. The Hello there / General Kenobi hook is not part of this: it always plays in a chorus)'), 56 into: choice('How should the song move into the section being entered (decidedThisSection)? It plays over the last bar before it. Pick what suits that section on its own merits; the moves before it are no reason to repeat or avoid one', { 57 asWritten: 'as written: the 808 toms roll down across the second half of the bar into a chorus or the key change, and it goes straight in to any other section', 58 fill: 'the 808 toms rolling down across the second half of the bar, into any section', 59 riser: 'white noise swelling up across the whole bar through an opening high-pass', 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? It scales the whole band\'s velocity. Shape the energy of the whole song, following its history, how it measured and how the listener reacted', [ 63 'held back: the whole band at about half velocity (0.55)', 64 'steady: about 70%', 65 'driving: about 85%', 66 'all out: full velocity, as written', 67 ]), 68 }, 69}) 70 71// a little chance in the form: the section is drawn from Jev's probabilities 72const { section } = formJev.every(8, { sample: 0.7 }) 73const dj = djJev.every(8, { 74 after: { section }, 75 forget: ['into'], 76 fallback: { arp: 2, lead: 2, bass: 2, drums: 2, vocals: 2, into: 'asWritten', energy: 3 }, 77}) 78 79// A part at Jev's level: silent when out, otherwise its velocity scaled by 80// the level. Each part has its own orbit, named for its question, so the 81// meter hears it alone (measuredSound). 82const part = (name, pat) => 83 pat.orbit(name).mask(dj[name].score.round().gt(0)).mul(velocity(dj[name].score.round().div(2))) 84 85// bars: 1-8 intro | 9-16 verse 1 | 17-24 chorus | 25-32 instrumental | 33-40 verse 2 86// 41-48 chorus | 49-60 instrumental, up a tone | 61-64 outro (each mask = 64 steps) 87const drums = "<0!4 1!56 0!4>" 88const hats = "<0!4 1!12 0!8 1!16 0!8 1!12 0!4>" // eighths; the choruses have sixteenths 89const pulse = "<0!60 1!3 0>" // the outro's kick, bars 61-63 90const bassOn = "<0!8 1!55 0>" // verse 1 to bar 63 91const verse1 = "<0!8 1!8 0!48>" 92const verse2 = "<0!32 1!8 0!24>" 93const chorus = "<0!16 1!8 0!16 1!8 0!16>" 94const leadOn = "<0!16 1!16 0!8 1!20 0!4>" 95const crash = "<0!16 1 0!7 1 0!15 1 0!7 1 0!15>" 96const final = "<0!63 1>" 97const ending2 = "<0!62 1!2>" // the last two bars: G#m 98const arpLpf = "<700 1000 1400 1800 2200!12 3600!8 2200!16 3600!8 2200!16>" 99const arpGain = "<.54!4 .6!4 .36!8 .4!8 .5!8 .36!8 .4!8 .5!12 .7 .6 .5 .4>" 100const leadGain = "<0!16 1.2!16 0!8 1.2!20 0!4>" 101const outroLpf = "<20000!60 1600 900 500 300>" // the outro kick's low-pass, closing bar by bar 102const bassLpf = "<450!60 350 260 190 150>" // and the bass's 103const subGain = "<.45!60 .4 .32 .24 0>" 104 105// key change: up a tone for the last 16 bars (instrumental and outro only) 106const key = "<0!48 2!16>" 107 108// two bars per chord: F#m F#m D D A A E E — as MIDI numbers so the key change can add to them 109const fsm = "[54 57 61 66 69 66 61 57 54 57 61 66 69 66 61 57]" 110const arp = "<[54 57 61 66 69 66 61 57 54 57 61 66 69 66 61 57]!2 [50 54 57 62 66 62 57 54 50 54 57 62 66 62 57 54]!2 [57 61 64 69 73 69 64 61 57 61 64 69 73 69 64 61]!2 [52 56 59 64 68 64 59 56 52 56 59 64 68 64 59 56]!2>" 111const roots = "<30!2 26!2 33!2 28!2>" // F#1 D1 A1 E1 112const lead = "<[73 ~ 73 71 69 ~ 71 73] [~ 69 ~ 66 ~ ~ ~ ~] [74 ~ 74 73 71 ~ 69 71] [~ 66 ~ 62 ~ ~ ~ ~] [73 ~ 76 ~ 73 71 69 ~] [~ 71 ~ 73 ~ 69 ~ ~] [71 ~ 71 73 71 ~ 68 ~] [64 ~ 68 ~ 71 ~ ~ ~]>" 113// the last two bars play the F#m row, which the key change raises to G#m: the song resolves 114const arpNotes = stack(arp.mask("<1!62 0!2>"), fsm.mask(ending2)) 115const bassRoots = stack(roots.mask("<1!62 0!2>"), "30".mask(ending2)) 116 117// ── parts ───────────────────────────────────────── 118// sixteenth-note arpeggio: the whole song; its filter opens over the intro and in the choruses 119const arps = note(arpNotes.add(key)).s("sawtooth").decay(.14).sustain(0).lpf(arpLpf) 120 .delay(.3).delaytime(.5).delayfeedback(.35).room(.3).gain(arpGain) 121 122// bright saw lead: choruses and instrumentals 123const leadPart = note(lead.add(key)).s("supersaw").detune(.3).lpf(3800).attack(.01).decay(.3).sustain(.4) 124 .delay(.3).delaytime(.5).delayfeedback(.4).room(.5).gain(leadGain).mask(leadOn) 125 126// pulsing eighth-note bass, with a sine sub underneath; closes down through the outro 127const bass = stack( 128 note(bassRoots.add(key)).struct("x*8").s("sawtooth").lpf(bassLpf).decay(.2).sustain(.3).gain(.35).mask(bassOn), 129 note(bassRoots.add(key)).struct("x*8").s("sine").gain(subGain).mask(bassOn) 130) 131 132// The gate: every sound with reverb on the drums' orbit goes through the same 133// impulse, 250 ms of flat noise cut dead (samples/ssgate), so the orbit's 134// reverb is never regenerated between two impulses. 135const gated = (p, amount) => p.room(amount).ir('ssgate') 136const kickBeat = "x ~ ~ ~ ~ ~ ~ ~ x ~ x ~ ~ ~ ~ ~" 137const toms = gated(s("~ ~ ~ ~ ~ ~ ~ ~ 808ht 808ht 808mt 808mt 808lt 808lt 808lt 808lt"), .4).gain(.6).orbit('drums') 138 139const kit = stack( 140 s("bd:3").struct(kickBeat).gain(.4).mask(drums), 141 s("bd:3").struct(kickBeat).gain(.4).lpf(outroLpf).mask(pulse), 142 // gated snare: the snare through the gate, and a noise burst that holds 250 ms then cuts 143 gated(s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~"), .6).gain(.85).mask(drums), 144 s("white").struct("[~@4 x@1.5 ~@6.5 x@1.5 ~@2.5]").attack(.001).sustain(1).release(.008).hpf(1800).gain(.3).mask(drums), 145 s("linnhats*8").gain(".3 .15").mask(hats), 146 s("hh27:1").gain(2).mask(crash), 147 // the chorus lift: a clap doubling the snare, and sixteenth hats 148 gated(s("~ cp ~ cp"), .5).gain(.55).mask(chorus), 149 s("linnhats*16").gain(".26 .12 .18 .12").mask(chorus), 150) 151 152// the vocoded SQL lines: the part Jev levels 153const vox = stack( 154 // verses: vocoder, one line per 2 bars, each sung on its chord 155 s("<ssselect ~ ssvibes ~ ssscan ~ sspool ~>").gain(1.1).room(.3).mask(verse1), 156 s("<ssorderby ~ ssforty ~ ssoptions ~ sstree ~>").gain(1.1).room(.3).mask(verse2), 157 // chorus: select star / where vibes are true / (the hook) / where vibes are true 158 s("<ssselect ~ ssvibes ~ ~ ~ ssvibese ~>").gain(1.15).room(.3).mask(chorus), 159) 160// the hook, hello there / general kenobi: always plays in a chorus, on the vocals' orbit 161const hook = s("<~ ~ ~ ~ sshellothere sskenobi ~ ~>").gain(1.15).room(.3).mask(chorus).orbit('vocals') 162 163// the end: the last gated snare, crash and noise tail. It always lands, at its own level. 164const ending = stack( 165 gated(s("sn:3"), .9).gain(.9).mask(final), 166 s("hh27:1").gain(1.6).mask(final), 167 s("white").attack(.001).sustain(1).release(.008).hpf(1800).gain(.35).struct("[x ~@9]").mask(final), 168) 169 170const once = (p) => p.mask("<1!64 0!936>") // play once, then silence 171 172// ── the form, as rules in code: which sections may follow which ── 173// `at` is where each starts as written; an empty `next` ends the song. The 174// written order is always allowed, and is what plays without Jev. 175// The last 16 bars are up a tone, so "keyUp" and "outro" stay vocal-free. 176// `times`: the key change plays twice at most (SPEC.md, "Loops end"). 177const form = walk(section, { 178 max: 16, 179 sections: { 180 intro: { at: 0, next: ['verse1'] }, 181 verse1: { at: 8, next: ['chorus1'] }, 182 chorus1: { at: 16, next: ['instrumental', 'verse2'] }, 183 instrumental: { at: 24, next: ['verse2', 'chorus1'] }, 184 verse2: { at: 32, next: ['chorus2'] }, 185 chorus2: { at: 40, next: ['keyUp', 'instrumental'] }, 186 keyUp: { at: 48, next: ['outro', 'keyUp'], times: 2 }, 187 outro: { at: 56, next: [] }, 188 }, 189}) 190 191// ── the song: Jev's section, parts and energy, composed ── 192// Each section plays its own bars, each part at the level Jev chose. 193const parts = form.arrange(once(stack( 194 part('arp', arps), 195 part('lead', leadPart), 196 part('bass', bass), 197 part('drums', kit), 198 part('vocals', vox), 199 hook, 200))) 201 202// ── into the next section: over the last bar of each, the answer for the section being entered ── 203// Past the last section there is no next one, so nothing plays there. 204// The toms are written only here: as written, they roll into the choruses and 205// the key change, wherever the path goes. 206const into = dj.into.choice.early(1) 207const next = section.choice.early(1) 208const rollsInto = ['chorus1', 'chorus2', 'keyUp'] 209const transition = into.pick({ 210 asWritten: toms.mask(next.fmap((s) => rollsInto.includes(s))), 211 fill: toms, 212 // one note across the bar, its level and high-pass both rising: a swell 213 riser: s("white").attack(2.4).sustain(1).release(.05).hpf(400).hpenv(4.3).hpattack(2.6).hpsustain(1).gain(.6), 214 dropout: silence, 215}).mask("<0!7 1>") 216// dropout: the parts stop for the section's last beat. The outro has no 217// next section, so its last beat plays. 218const lastBeat = "<0!7 [0 0 0 1]>" 219const gap = stack( 220 "<1!7 [1 1 1 0]>", 221 into.fmap((c) => c !== 'dropout').mask(lastBeat), 222 section.choice.fmap((s) => s === 'outro').mask(lastBeat), 223) 224 225// Energy scales the band; the ending always lands at its own level. One 226// postgain for the whole mix keeps the master's peak under -1 dBFS (SPEC.md, "Mix"). 227stack( 228 stack(parts.mask(gap), transition).mul(velocity(dj.energy.score.div(3).range(.55, 1))), 229 form.arrange(once(ending)), 230).mul(postgain(.47))