song.jsannotatedsong.jssource349 lines · 20.6 KB · raw

ELEVEN THOUSAND VOLUMES — baroque electronica at 120bpm, 64 bars as written; Jev arranges up to 24 four-bar sections. Spec: SPEC.md Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count). (The MCP server rejects any code containing the substring e-v-a-l, comments included.)

4setcps(120/60/4)
5await samples('github:tidalcycles/dirt-samples')
6await samples('jev-samples/strudel.json')

── Jev: two of them, in TypeSafe's own shape (SPEC.md, "Jev in the booth") ── formJev picks the next section; djJev, asked once that is decided (questions in one request cannot see each other), says how much of each part plays it, how the song moves into it, and how hard. Every 4 bars. The form's rules (walk, at the bottom) narrow the sections Jev may pick and end the song; without Jev it plays as written.

14const song = {
15  title: 'ELEVEN THOUSAND VOLUMES',
16  what: 'baroque electronica about Lieutenant Commander Data pointing a browser at an ebook store and having Jev download the whole catalogue, too damn fast. Jev is not an agent: the browser loop does the clicking and asks Jev one yes/no question per book, should Data download this book?, and Jev answers yes every time, in about 100 ms. The harpsichord and the clicks speed up until the store answers four twenty-nine, too many requests. Then the punchline: eleven thousand volumes, DRM-locked, none of them openable, and the loan expires',
17  tempo: '120 bpm, 4/4, 4 bars a section',
18  key: 'A minor',
19  chords: 'the descending circle of fifths, two chords a bar: Am Dm | G C | F Bdim | E Am',
20  lyrics: {
21    intro: 'Browser log, supplemental. I have navigated to page four thousand one hundred and twelve of the catalog.',
22    introB: 'Should Data download this book? / Yes.',
23    verse1: 'Should Data download this book? / Yes. Yes. Yes.',
24    verse1b: 'Yes. Yes. / I am curious',
25    drop1: 'I find it most intriguing.',
26    drop1b: 'Should Data download this book? / Of course.',
27    break: 'Four twenty-nine. Too many requests. / 0.68 seconds, sir. For an android, that is nearly an eternity.',
28    verse2: '(the eternity rings on)',
29    verse2b: 'I have acquired eleven thousand volumes. I am unable to open any of them. / Access denied. / Should Data download this book?',
30    drop2b: 'I am curious',
31    outro: 'Your loan has expired.',
32  },
33}
35const formJev = jev({
36  state: song,
37  questions: {
38    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', {
39      intro: 'a TNG chirp, Data reading his browser log over the harpsichord in eighths, the filter opening, the first mouse click',
40      introB: 'the electro beat and the bass come in, the harpsichord in eighths; should Data download this book? and Jev answers yes, one click a bar',
41      verse1: 'the harpsichord in sixteenths, the violin in half notes; should Data download this book?, then Jev\'s yes once a bar; two clicks a bar',
42      verse1b: 'the verse continues: Jev\'s yes twice a bar, two clicks a bar; Data: I am curious',
43      build1: 'the build: the harpsichord in thirty-seconds, the clicks doubling every bar from 2 to 16, Jev\'s yes doubling with them from once to eight a bar, four on the floor, a noise riser across the four bars, a snare roll in the last bar',
44      drop1: 'drop 1: everything, the harpsichord in thirty-seconds, the violin\'s quarter-note line, the electro beat, eight clicks a bar, Jev\'s yes only as a tag on the last beat of every other bar, a crash; Data: I find it most intriguing',
45      drop1b: 'drop 1 continues, the electro beat, the bass in eighths and the violin\'s quarter-note line under the harpsichord in thirty-seconds: should Data download this book?, and Data answers himself: of course',
46      drop1c: 'the end of drop 1: the buzz over the electro beat, the bass and the violin\'s line; the harpsichord in sixty-fourths, the clicks a buzz, Jev\'s yes let loose and staggered in (none, then eighths quietly, then sixteenths, then full), heading for the door',
47      break: 'the break: the store slams the door, four twenty-nine, too many requests; the band stops dead, a low held note and LCARS beeps; then Data: 0.68 seconds, sir, for an android that is nearly an eternity, running on into the next section; the harpsichord in eighths under Data in its last bar',
48      verse2: 'the wait: Data\'s nearly an eternity still speaking; the violin alone in long notes over the harpsichord in eighths and a soft kick',
49      verse2b: 'the punchline: I have acquired eleven thousand volumes, I am unable to open any of them; access denied; over only a soft kick, the harpsichord and one click a bar, so the pause lands; then the beat and the bass slam back on its last bar as the loop starts again, should Data download this book?',
50      build2: 'the build again: the harpsichord in thirty-seconds, the clicks doubling every bar, Jev\'s yes doubling with them, a noise riser, a snare roll',
51      drop2: 'drop 2: everything again; Data: I find it most intriguing',
52      drop2b: 'drop 2 continues, the electro beat, the bass in eighths and the violin\'s quarter-note line under the harpsichord in thirty-seconds; Data: I am curious',
53      drop2c: 'drop 2, the peak: the buzz again over the electro beat, the bass and the violin\'s line; the harpsichord in sixty-fourths, the clicks a buzz, Jev\'s yes staggered in again and pitched up',
54      outro: 'the last A minor chord on harpsichord and violin left to ring, your loan has expired, a last TNG chirp: the end',
55    }),
56  },
57})

how much of each part the section decidedThisSection has: out, back or full. Each question is answered on its own (questions in one request cannot see each other), so each asks only about its own part.

62const level = (part) => score('How much of ' + part + ' should the next section (decidedThisSection) have? Judge this part alone, against what that section is', [
63  'out: the section works without it',
64  'back: it colours the section',
65  'full: it carries the section',
66])
67const djJev = jev({
68  state: song,
69  questions: {
70    drums: level('the drums (an 808 kick and hats in introB, claps from verse1 on; four on the floor, a noise riser and a snare roll in the builds; the electro beat, open hats and a crash in the drops; a soft kick in verse2 and verse2b until its last bar, where the beat slams back; nothing in the break)'),
71    bass: level('the bass (a square-wave electro bass in eighths on the chord roots from introB on, through the builds and the drops, and a low held A under the break; none in verse2, back on the last bar of verse2b)'),
72    harpsichord: level('the harpsichord (the Bach-style arpeggio through every section, the song\'s speedometer: eighths in the intro, introB, verse2 and verse2b, sixteenths in the first verse, thirty-seconds in the builds and drops, sixty-fourths in the buzz at the end of each drop, in eighths under Data in the last bar of the break; it steps back while Data speaks in the drops)'),
73    violin: level('the violin, played by Data (half notes in the first verse, a quarter-note line in the drops, long notes alone in verse2)'),
74    computer: level('the computer (the TNG chirp and LCARS beeps: the intro, the first verse, the top of each drop, and the break)'),
75    clicks: level('the browser (a mouse click per download, off the downbeat: one in the last bar of the intro, one a bar in introB and the punchline, two a bar in the first verse, doubling every bar through the builds, eight a bar in the drops, and a buzz at the end of each drop; they step back while Data speaks in the drops)'),
76    yes: level('Jev, answering yes (from introB: once or twice a bar in the first verse, doubling through the builds, only a tag every other bar in a drop\'s first section and none in its second, let loose and staggered in through the buzz; none from the break through the punchline; it steps back while Data speaks in the drops)'),
77    vocals: level('Data\'s reactions, his real lines from TNG (I am curious in verse1b and drop2b, I find it most intriguing at the top of each drop, of course in drop1b); the story lines always play'),
78    into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it; only the moves that suit that change are offered', {
79      cut: 'straight in, no transition: when the next section should simply arrive',
80      fill: 'an electro fill, snare and toms, over the last half bar: to push into a verse or a build (only if the drums are in)',
81      riser: 'white noise swelling up over the last bar: to lift into a build or a drop',
82      dropout: 'the door: every part\'s notes stop for the last beat, a moment of silence before the section hits (a note already ringing rings on): before the store slams the door, a drop hits, or the song ends',
83    }),
84    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', [
85      'held back: noticeably quieter',
86      'steady: a little under full',
87      'driving: full volume',
88      'all out: full volume, nothing held back',
89    ]),
90  },
91})

a little chance in the form: the section is drawn from Jev's probabilities Which moves suit which change (SPEC.md, "The transitions"): rules in code, so Jev is offered only these. A cut always is.

96const intoAllowed = ({ section: next, playingNow: now = '' }) => [
97  'cut',
98  ...(next.startsWith('verse') || next.startsWith('build') ? ['fill'] : []),
99  ...((next.startsWith('build') || next.startsWith('drop')) && !now.startsWith('build') ? ['riser'] : []),
100  ...(['break', 'drop1', 'drop2', 'outro'].includes(next) ? ['dropout'] : []),
101]
103const { section } = formJev.every(4, { sample: 0.7 })
104const dj = djJev.every(4, {
105  after: { section },
106  forget: ['into'],
107  allow: { into: intoAllowed },
108  sample: { into: 1 },
109  fallback: { drums: 2, bass: 2, harpsichord: 2, violin: 2, computer: 2, clicks: 2, yes: 2, vocals: 2, into: 'cut', energy: 2 },
110})

A part at Jev's level: silent when out, otherwise its velocity scaled by the level. Each part has its own orbit, named for its question, so the meter hears it alone (measuredSound).

115const part = (name, pat) =>
116  pat.orbit(name).mask(dj[name].score.round().gt(0)).mul(velocity(dj[name].score.round().div(2)))

bars: 1-4 intro | 5-8 intro B | 9-16 verse 1 | 17-20 build | 21-32 DROP 1 33-36 break | 37-44 verse 2 | 45-48 build | 49-60 DROP 2 | 61-64 outro (each mask = 64 steps; a bar is 2 s)

121const intro    = "<1!4 0!60>"
122const introB   = "<0!4 1!4 0!56>"
123const verse1   = "<0!8 1!8 0!48>"
124const builds   = "<0!16 1!4 0!24 1!4 0!16>"
125const dropsAB  = "<0!20 1!8 0!20 1!8 0!8>"
126const buzz     = "<0!28 1!4 0!24 1!4 0!4>"
127const drops    = "<0!20 1!12 0!16 1!12 0!4>"
128const brk      = "<0!32 1!4 0!28>"
129const verse2   = "<0!36 1!4 0!24>"
130const verse2b  = "<0!40 1!4 0!20>"
131const rollBars = "<0!19 1 0!27 1 0!16>"
132const crash    = "<0!20 1 0!3 1 0!3 1 0!19 1 0!3 1 0!3 1 0!7>"
133const final    = "<0!60 1 0!3>"

while Data speaks in the drops, the yes stutter, the clicks and the harpsichord step back (the line timings: SPEC.md, "Vocals")

136const duck     = "<1!20 [1 1 0 0] [0 0 0 1] 1!2 [0 0 0 1] [0 0 1 1] 1!22 [1 1 0 0] [0 0 0 1] 1!2 [1 1 0 0] 1!11>"

one line each, on the bar it starts (bar numbers in SPEC.md, "Vocals")

139const log        = "<1 0!63>"
140const should     = "<0!4 1 0 1 0 1 0!15 1 0!18 1 0!20>"
141const curiousA   = "<0!14 1 0!49>"
142const curiousB   = "<0!52 1 0!11>"
143const intriguing = "<0!20 1 0!27 1 0!15>"
144const ofCourse   = "<0!25 1 0!38>"
145const tooMany    = "<0!32 1 0!31>"
146const denied     = "<0!42 1 0!21>"
147const volumes    = "<0!40 1 0!23>"
148const eternity   = "<0!34 1 0!29>"
149const loan       = "<0!61 1 0!2>"

Am Dm | G C | F B° | E Am: the descending circle of fifths, two chords a bar roots at the bottom, so the arpeggio's lowest line walks the circle

153const chords = "<[[a2,e3,a3,c4] [d3,a3,d4,f4]] [[g2,d3,g3,b3] [c3,g3,c4,e4]] [[f2,c3,f3,a3] [b2,d3,f3,a3]] [[e2,b2,e3,g#3] [a2,e3,a3,c4]]>"
154const roots  = "<[a1 d2] [g1 c2] [f1 b1] [e1 a1]>"

── parts ───────────────────────────────────────── the harpsichord: a plucked sawtooth, and a quieter square an octave up (the 4-foot stop), arpeggiating each chord. idx sets the note value.

159const eighths      = "[0 1 2 3]*2"
160const sixteenths   = "[0 1 2 3 2 1 2 3]*2"
161const thirtySecond = "[0 1 2 3 2 1 2 3]*4"
162const sixtyFourth  = "[0 1 2 3]*16"
163const harpsichordAt = (idx, g) => stack(
164  note(chords).arp(idx).s("sawtooth").hpf(150).lpf(6000).decay(.14).sustain(0).gain(g),
165  note(chords).arp(idx).add(note(12)).s("square").hpf(1200).lpf(7000).decay(.08).sustain(0).gain(g * .3)
166)
167const harpsichord = stack(
168  harpsichordAt(eighths, .16).lpf(saw.range(900, 6000).slow(4)).mask(intro),
169  harpsichordAt(eighths, .16).mask(introB),
170  harpsichordAt(sixteenths, .16).mask(verse1),
171  harpsichordAt(thirtySecond, .14).mul(gain(saw.range(.5, 1).slow(4))).mask(builds),
172  harpsichordAt(thirtySecond, .14).mask(dropsAB),
173  harpsichordAt(sixtyFourth, .11).mask(buzz),
174  harpsichordAt(eighths, .12).mask("<0!35 1 0!28>"),
175  harpsichordAt(eighths, .13).mask("<0!36 1!8 0!20>")
176).mul(gain(duck.range(.35, 1)))

the violin: a sawtooth with a vibrato, a slow attack and a low-pass

179const bow = (pat, g) =>
180  note(pat).s("sawtooth").vib(5).vibmod(.15).attack(.12).release(.3).lpf(2600).room(.35).gain(g)
181const violin = stack(
182  bow("<[e5 d5] [d5 c5] [c5 b4] [b4 a4]>", .12).mask(verse1),
183  bow("<[e5 c5 f5 a5] [d5 b4 c5 e5] [a4 c5 b4 d5] [g#4 b4 a4 ~]>", .12).mask(drops),
184  bow("<e5 d5 c5 [b4 a4]>", .13).attack(.4).release(.8).mask(verse2)
185)
187const bass = stack(
188  note(roots).struct("x*8").s("square").lpf(700).decay(.1).sustain(.2).gain(.3)
189    .mask("<0!4 1!16 0!23 1!5 0!16>"),
190  note(roots).struct("x*8").s("square").lpf(900).decay(.1).sustain(.25).gain(.32).mask(drops),
191  note("a1").s("triangle").attack(.3).release(1).gain(.45).mask("<0!32 1!3 0!29>")
192)

the electro beat

195const beat = stack(
196  s("808bd:1").struct("x ~ ~ x ~ ~ x ~").gain(1),
197  s("~ cp ~ cp").gain(.7),
198  s("hh*8").gain(".35 .2")
199)
200const drums = stack(
201  stack(s("808bd:1").struct("x ~ ~ ~ x ~ ~ ~").gain(.9), s("hh*8").gain(".3 .15")).mask(introB),
202  beat.mask("<0!8 1!8 0!27 1 0!20>"),
203  s("808bd:1*4").gain(1).mask(builds),
204  s("~ cp ~ cp").gain(.7).mask(builds),
205  s("white*16").hpf(saw.range(300, 7000).slow(4)).gain(saw.range(.02, .25).slow(4)).decay(.06).sustain(0).mask(builds),
206  beat.mask(drops),
207  s("[~ 808oh]*4").gain(.3).mask(drops),
208  s("hh27:1").gain(2.5).mask(crash),
209  s("808bd:1").struct("x ~ ~ ~ x ~ ~ ~").gain(.7).mask("<0!36 1!7 0!21>")
210)

the snare roll plays through a dropout: its last beat is the build's

212const roll = s("sn:3*16").gain(saw.range(.25, 1)).mask(rollBars)

the computer: the TNG chirp and LCARS beeps

215const computer = stack(
216  s("etvchirp").gain(.9).mask(log),
217  s("etvbeep ~ ~ ~").gain(.7).mask("<0 1 0 1 0!60>"),
218  s("~ ~ ~ etvbeep").gain(.6).mask("<0!8 1!6 0 1 0!48>"),
219  s("etvchirp").gain(.9).mask("<0!20 1 0!27 1 0!15>"),
220  s("<[~ ~ etvbeep ~] [~ ~ ~ etvbeep] ~ [etvbeep etvbeep ~ ~]>").gain(.7).mask(brk)
221)

the browser: a click per download, off the downbeat so it clears the kick

224const clicks = stack(
225  s("~ etvclick ~ ~").gain(.9).mask("<0!3 1 0!60>"),
226  s("[~ etvclick] ~ ~ ~").gain(.9).mask(introB),
227  s("[~ etvclick]*2").gain(.9).mask(verse1),
228  s("[~ etvclick]*<2 4 8 16>").gain(.9).mask(builds),
229  s("[~ etvclick]*8").gain(.9).mask(dropsAB),
230  s("etvclick*32").gain(.8).mask(buzz),
231  s("[~ etvclick] ~ ~ ~").gain(.9).mask(verse2b)
232).mask(duck)

Jev: "Yes.", every time, as fast as the downloads

235const yes = stack(
236  s("~ ~ etvyes ~").gain(1).mask("<0!5 1 0 1 0!56>"),
237  s("~ ~ etvyes ~").gain(1).mask("<0!9 1!3 0!52>"),
238  s("~ ~ etvyes etvyes").gain(1).mask("<0!12 1!2 0 1 0!48>"),
239  // rationed: doubling through each build, a tag in a drop's first section,
240  // none in its second, let loose only in the buzz, staggered in; the second
241  // time round pitched up
242  s("<etvyes [etvyes etvyes] etvyes*4 etvyes*8>").clip(1).gain(.9).mask(builds),
243  s("~ ~ ~ etvyes").gain(.9).mask("<0!21 1 0 1 0!40>"),
244  s("~ ~ ~ etvyes").speed(1.12).gain(.9).mask("<0!49 1 0 1 0!12>"),
245  s("<~ etvyes*8 etvyes*16 etvyes*16>").clip(1).gain("<0 .45 .7 1>").mask("<0!28 1!4 0!32>"),
246  s("<~ etvyes*8 etvyes*16 etvyes*16>").clip(1).speed(1.25).gain("<0 .45 .7 1>").mask("<0!56 1!4 0!4>"),
247).mask(duck)

the story: every line a joke rests on. It always plays, like the ending.

250const story = stack(
251  s("etvlog").gain(1).room(.2).mask(log),
252  s("etvshould").gain(1).mask(should),
253  s("etv429").gain(1).room(.3).mask(tooMany),
254  s("~ etvdenied ~ ~").gain(1).room(.3).mask(denied),
255  s("etvvolumes").gain(1).room(.3).mask(volumes),
256  s("etvread").gain(1).room(.2).mask(eternity),
257).orbit('vocals')

Data's reactions, his real lines from TNG: Jev levels these

260const reactions = stack(
261  s("etvcurious").gain(1).room(.2).mask(curiousA),
262  s("~ ~ etvcurious ~").gain(1).room(.2).mask(curiousB),
263  s("~ ~ etvintriguing ~").gain(1).room(.2).mask(intriguing),
264  s("etvofcourse").gain(1).room(.2).mask(ofCourse),
265)

the end: the last A minor chord on harpsichord and violin, left to ring, then "Your loan has expired." and a last chirp. It always plays.

269const ending = stack(
270  note("[a2,a3,c4,e4,a4]").arp("0 1 2 3 4 ~ ~ ~").s("sawtooth").hpf(300).lpf(6000).decay(2).sustain(0).gain(.16).mask(final),
271  bow("a4", .13).attack(.3).release(1).mask("<0!60 1!4>"),
272  s("808bd:1").gain(1).mask(final),
273  s("hh27:1").gain(2.5).mask(final),
274  s("etvloan").gain(1).room(.3).mask(loan),
275  s("~ ~ ~ etvchirp").gain(.9).mask("<0!63 1>")
276)
278const once = (p) => p.mask("<1!64 0!936>")   // play once, then silence

── the form, as rules in code: which sections may follow which ── at is where each starts as written; an empty next ends the song. The written order is always allowed, and is what plays without Jev. times: each verse's and drop's loop goes round once more at most (SPEC.md, "Loops end").

284const form = walk(section, {
285  max: 24,
286  sections: {
287    intro: { at: 0, next: ['introB'] },
288    introB: { at: 4, next: ['verse1'] },
289    verse1: { at: 8, next: ['verse1b'], times: 2 },
290    verse1b: { at: 12, next: ['build1', 'verse1'] },
291    build1: { at: 16, next: ['drop1'] },
292    drop1: { at: 20, next: ['drop1b'] },
293    drop1b: { at: 24, next: ['drop1c', 'drop1b'], times: 2 },
294    drop1c: { at: 28, next: ['break', 'drop1b'] },
295    break: { at: 32, next: ['verse2'] },
296    verse2: { at: 36, next: ['verse2b'], times: 2 },
297    verse2b: { at: 40, next: ['build2', 'verse2'] },
298    build2: { at: 44, next: ['drop2'] },
299    drop2: { at: 48, next: ['drop2b'] },
300    drop2b: { at: 52, next: ['drop2c', 'drop2b'], times: 2 },
301    drop2c: { at: 56, next: ['outro', 'drop2b', 'break'] },
302    outro: { at: 60, next: [] },
303  },
304})

── the song: Jev's section, parts and energy, composed ── Each section plays its own bars, each part at the level Jev chose; the energy scales every part's velocity together, so the mix keeps its balance. The ending always plays.

310const parts = form.arrange(once(stack(
311  part('drums', drums.mul(gain(.4))), // the drums 8 dB down: measured 10 dB over the voices, and the peak
312  part('bass', bass),
313  part('harpsichord', harpsichord),
314  part('violin', violin),
315  part('computer', computer),
316  part('clicks', clicks),
317  part('yes', yes),
318  part('vocals', reactions),
319)))

── into the next section: over the last bar of each, the next one's answer ── Past the last section there is no next one, so nothing plays there. The fill needs the drums in, and a build already rises, so no riser out of one.

324const into = dj.into.choice.early(1)
325const transition = into.pick({
326  cut: silence,
327  fill: s("~ ~ [sn:3 sn:3 lt lt] [mt mt ht ht]").gain(.8).orbit('drums').mask(dj.drums.score.round().gt(0)),
328  riser: s("white*16").hpf(saw.range(400, 8000)).gain(saw.range(.05, .3)).decay(.07).sustain(0)
329    .mask(section.choice.fmap((s) => !s.startsWith('build') && s !== 'break' && s !== 'verse2')),
330  dropout: silence,
331}).mask("<0!3 1>")

dropout: every part's notes stop for the section's last beat (a note already ringing rings on). The outro has no next section, so its last beat plays.

335const lastBeat = "<0!3 [0 0 0 1]>"
336const gap = stack(
337  "<1!3 [1 1 1 0]>",
338  into.fmap((c) => c !== 'dropout').mask(lastBeat),
339  section.choice.fmap((s) => s === 'outro').mask(lastBeat),
340)

energy: held back, steady, driving, all out

343stack(
344  parts.mask(gap),
345  form.arrange(once(part('drums', roll.mul(gain(.4))))),
346  transition,
347  form.arrange(once(stack(story, ending))),
348).mul(velocity(dj.energy.score.fmap((s) => [.55, .8, .95, 1][Math.round(s)])))
349  .mul(postgain(.8)) // the master level: peaks under -1 dBFS (superdough has no limiter)