song.jsannotatedsong.jssource265 lines · 13.8 KB · raw

SYSTEM ONE — drum & bass, 174bpm, 64 bars as written; Jev arranges up to 24 4-bar sections. Spec: SPEC.md Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count). Half-time "thinking" is made with patterns; cps never changes, because every mask counts cycles.

4setcps(174/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.

13const song = {
14  title: 'SYSTEM ONE',
15  what: 'drum and bass about Jev as System One, in Kahneman terms: the beat collapses to half time while an LLM thinks, and snaps back when Jev answers',
16  tempo: '174 bpm; the think sections feel half time at the same tempo; 4 bars a section',
17  key: 'E minor',
18  chords: 'Em C G D, one bar each; every section starts on Em',
19  lyrics: {
20    intro: 'Thinking, fast and slow',
21    intro2: 'Thinking, fast and slow',
22    think1: 'Let him cook',
23    drop1: 'Noul. Choice. Score.',
24    think2: 'Let him cook now',
25    drop2: 'Noul. Choice. Score.',
26    think3: 'Let him cook',
27    drop3: 'Noul. Choice. Score.',
28    outro: 'Noul. Choice. Score.',
29  },
30}
32const formJev = jev({
33  state: song,
34  questions: {
35    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', {
36      intro: 'the amen break through a falling high-pass filter and a sub pulse; a robot voice',
37      intro2: 'the filtered intro continues; the robot voice again',
38      think1: 'half time, the build: the LLM is thinking; kick on 1, snare on 3, the reese held low, a Windows chime phrase that resolves to E',
39      drop1: 'drop 1 lands: full speed two-step and rolling amen, eighth hats, the reese bass; Jev answers',
40      drop1b: 'drop 1 continues, instrumental',
41      drop1c: 'drop 1 continues, instrumental',
42      drop1d: 'the end of drop 1, instrumental',
43      think2: 'half time, the build: a busier Windows chime phrase that resolves to E',
44      drop2: 'drop 2 lands: drop 1 plus sixteenth hats and chord stabs; Jev answers',
45      drop2b: 'drop 2 continues, instrumental',
46      drop2c: 'drop 2 continues, instrumental',
47      drop2d: 'the end of drop 2, instrumental',
48      think3: 'half time, the build: the chime stops on F#, never resolves',
49      drop3: 'drop 3 lands, the densest drop: stabs, sixteenth hats and the reese doubled an octave up; Jev answers',
50      drop3b: 'drop 3 continues, the peak, instrumental',
51      outro: 'the hook once more, then a final kick, crash and Em chord: the end',
52    }),
53  },
54})

how much of each part the section decidedThisSection has: out, back or full

57const 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', [
58  'out: silent for the section',
59  'back: under the rest of the mix',
60  'full: as written',
61])
62const djJev = jev({
63  state: song,
64  questions: {
65    drums: level('the drums (the amen break filtered in the intro; full-speed two-step kick and rolling amen in every drop; in the thinks, kick on 1, snare on 3, quarter hats, no amen)'),
66    bass: level('the bass (a sub pulse in the intro, the reese and sub on the chord roots in every drop, doubled an octave up in drop 3, the reese held low on E in every think: the floor under the whole song)'),
67    stabs: level('the chord stabs (short saw chords in drops 2 and 3 only, what makes them bigger than drop 1)'),
68    chime: level('the loading chime (the Windows chimes sound played as a phrase in each think only, the sound of the LLM thinking: think1 and think2 resolve to E, think3 stops on F#)'),
69    vocals: level('the vocals (the lines in the lyrics: the robot in the intro, let him cook in the thinks, the hook as each drop lands, and once more to start the outro)'),
70    into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', {
71      cut: 'straight in, no transition',
72      fill: 'the amen break stuttering on its snare across the second half of the bar',
73      riser: 'white noise swelling up over the last bar',
74      dropout: 'everything stops for the last beat, a gap before the section hits',
75    }),
76    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', [
77      'held back: about half volume, room to breathe',
78      'steady: well under full',
79      'driving: nearly full',
80      'all out: full volume, nothing held back',
81    ]),
82  },
83})

a little chance in the form: the section is drawn from Jev's probabilities

86const { section } = formJev.every(4, { sample: 0.7 })
87const dj = djJev.every(4, {
88  after: { section },
89  forget: ['into'],
90  fallback: { drums: 2, bass: 2, stabs: 2, chime: 2, vocals: 2, into: 'cut', energy: 3 },
91  // the opening has no section before it, so nothing to move from: it cuts
92  allow: { into: (d) => (d.playingNow ? ['cut', 'fill', 'riser', 'dropout'] : ['cut']) },
93})

A part at Jev's level, in whole steps (a score of 1.5 plays as full, not between): out is silent, back is half velocity, full is as written. Each part has its own orbit, named for its question, so the meter hears it alone (measuredSound).

99const lvl = (name) => dj[name].score.round()
100const part = (name, pat) =>
101  pat.orbit(name).mask(lvl(name).gt(0)).mul(velocity(lvl(name).div(2)))

bars: 1-8 intro | 9-12 think | 13-28 DROP 1 | 29-32 think | 33-48 DROP 2 49-52 think | 53-60 DROP 3 | 61-64 outro (each mask = 64 steps)

105const intro   = "<1!8 0!56>"
106const thinks  = "<0!8 1!4 0!16 1!4 0!16 1!4 0!12>"
107const think1  = "<0!8 1!4 0!52>"
108const think2  = "<0!28 1!4 0!32>"
109const think3  = "<0!48 1!4 0!12>"
110const drops   = "<0!12 1!16 0!4 1!16 0!4 1!8 0!4>"
111const drop1   = "<0!12 1!16 0!36>"
112const bigger  = "<0!32 1!16 0!4 1!8 0!4>"          // drops 2 and 3
113const drop3   = "<0!52 1!8 0!4>"
114const hook    = "<0!12 1 0!19 1 0!19 1 0!7 1 0!3>"  // bars 13, 33, 53, 61
115const crash   = "<0!12 1 0!19 1 0!19 1 0!11>"
116const robot   = "<0!2 1 0!3 1 0!57>"
117const cook    = "<0!8 1 0!39 1 0!15>"
118const cookNow = "<0!28 1 0!35>"
119const rise    = "<0!61 1 0!2>"                       // bar 62, into the final hit
120const lastHit = "<0!62 1 0>"

one bar per chord: Em C G D. No root under 41 Hz: C and D sit above E.

123const roots  = "<28 36 31 38>"   // E1 C2 G1 D2, as MIDI numbers
124const chords = "<[e4,g4,b4] [c4,e4,g4] [d4,g4,b4] [d4,f#4,a4]>"
125const 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]>"

── parts ───────────────────────────────────────── drums: the filtered intro break, full speed (System One), and half time (the LLM is thinking). A short kick, no 808 tail: the sub carries the pitch. The break is choked (cut), so no slice outlasts its 16th.

131const drums = stack(
132  n("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15").s("amencutup").cut(1)
133    .hpf(saw.range(3000, 400).slow(8)).gain(.8).mask(intro),
134  s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.7).mask(drops),
135  s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.15).gain(1).mask(drops),
136  n(amen).s("amencutup").cut(1).hpf(150).gain(.72).mask(drops),
137  s("hh*8").gain("1.75 .9").mask(drop1),
138  s("hh*16").gain("1.75 .75 1.25 .75").mask(bigger),
139  s("hh27:1").gain(2.5).mask(crash),   // hh27:1 is the crash; `cr` is a ride
140  s("bd:3").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~").gain(.45).mask(thinks),
141  s("sn:3").struct("~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~").room(.4).gain(.52).mask(thinks),
142  s("hh*4").gain(.2).mask(thinks)
143)

bass: the intro sub, the reese in the drops (doubled an octave up in drop 3), and the reese held on E, filtered down, while thinking. The think notes stop after beat 3 of the section's last bar, so a dropout is silent.

148const bassRhythm = "x@3 x@3 x@4 x@2 x@4"
149const thinkE = "<28 28 28 [28@3 ~]>"
150const bass = stack(
151  note("28").s("sine").decay(.6).sustain(0).gain(.5).mask(intro),
152  note(roots).struct(bassRhythm).s("supersaw").detune(.6)
153    .lpf(sine.range(220, 1000).slow(8)).lpq(3).shape(.3).gain(.45).mask(drops),
154  note(roots).struct(bassRhythm).add(note(12)).s("supersaw").detune(.6)
155    .lpf(sine.range(600, 2400).slow(8)).lpq(2).gain(1.8).mask(drop3),
156  note(roots).struct(bassRhythm).s("sine").gain(.95).mask(drops),
157  note(thinkE).s("supersaw").detune(.6).lpf(280).attack(.05).release(.1).gain(.2).mask(thinks),
158  note(thinkE).s("sine").release(.05).gain(.27).mask(thinks)
159)

the chord stabs in drops 2 and 3

162const stabs = note(chords).struct("x ~ ~ x ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~")
163  .s("sawtooth").lpf(3000).decay(.15).sustain(0).room(.3).gain(2.3).mask(bigger)

the loading chime while thinking: the Windows chimes sound (samples/ sowinchime, rooted on A5 +8 cents) played as notes, so each think ends its own way: think1 and think2 resolve to E, think3 stops on F#. It starts after "Let him cook", and its last bar is short, silent by beat 4.

169const winChime = (phrase, decay) =>
170  note(phrase).transpose(-45).speed(.995).s("sowinchime").decay(decay).sustain(0)
171const chime = stack(
172  winChime("<[~ ~ ~ [~ b5]] [e5 ~ g5 ~] [f#5 ~ d5 ~] [e5 ~ ~ ~]>", "<1.2 1.2 1.2 .9>")
173    .room(.5).size(.8).gain(.5).mask(think1),
174  winChime("<[~ ~ ~ [~ b5]] [e5 g5 b5 e5] [a5 d5 f#5 ~] [g5 e5 ~ ~]>", "<1.4 1.4 1.4 .5>")
175    .room(.5).size(.8).gain(.42).mask(think2),
176  winChime("<[~ ~ ~ [~ b5]] [e5 ~ g5 ~] [f#5 ~ d5 ~] [f#5 ~ ~ ~]>", "<2 2 2 .9>")
177    .room(.5).size(.8).gain(.5).mask(think3)
178)
180const vox = stack(
181  s("sothinking").gain(.9).room(.5).delay(.3).delaytime(.345).delayfeedback(.35).mask(robot),
182  s("socook").gain(1.3).room(.3).mask(cook),
183  s("socooknow").gain(1.3).room(.3).mask(cookNow),
184  // Jev answers: the full-speed beat snaps back under it
185  s("sohook").gain(2.2).room(.2).mask(hook)
186)

the end: a riser over bar 62 into a kick, crash and an Em chord left to ring in bar 63. It always plays, on its own orbit.

190const ending = stack(
191  s("white*16").hpf(saw.range(400, 8000)).gain(saw.range(.2, 1.2)).decay(.07).sustain(0).mask(rise),
192  s("bd:3").room(.6).size(.9).gain(.6).mask(lastHit),
193  s("hh27:1").room(.6).size(.9).gain(2.5).mask(lastHit),
194  note("[e3,g3,b3]").s("sawtooth").lpf(1400).decay(1.5).sustain(0).room(.6).gain(.2).mask(lastHit)
195).orbit('ending')
197const 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. Sections are the song's 4-bar grid: every drop and think starts on one, on Em, and each think leads only into its own drop. The cap is 24 sections (96 bars, ~2:12), since 16 would only be the written length.

205const form = walk(section, {
206  max: 24,
207  sections: {
208    intro: { at: 0, next: ['intro2'] },
209    intro2: { at: 4, next: ['think1'] },
210    think1: { at: 8, next: ['drop1'] },
211    drop1: { at: 12, next: ['drop1b'] },
212    drop1b: { at: 16, next: ['drop1c', 'think2'] },
213    drop1c: { at: 20, next: ['drop1d'] },
214    drop1d: { at: 24, next: ['think2', 'think3'] },
215    think2: { at: 28, next: ['drop2'] },
216    drop2: { at: 32, next: ['drop2b'] },
217    drop2b: { at: 36, next: ['drop2c', 'think3'] },
218    drop2c: { at: 40, next: ['drop2d'] },
219    drop2d: { at: 44, next: ['think3', 'think2'] },
220    think3: { at: 48, next: ['drop3'] },
221    drop3: { at: 52, next: ['drop3b'] },
222    drop3b: { at: 56, next: ['outro', 'think2', 'drop2'] },
223    outro: { at: 60, next: [] },
224  },
225})

── 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, in whole steps, so the mix keeps its balance. The ending always plays.

231const parts = form.arrange(once(stack(
232  part('drums', drums),
233  part('bass', bass),
234  part('stabs', stabs),
235  part('chime', chime),
236  part('vocals', vox),
237)))

── 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 follows the drums level of the section whose bar it plays in (no read-ahead: that is the answer it sounds in); the riser has its own orbit.

243const into = dj.into.choice.early(1)
244const transition = into.pick({
245  cut: silence,
246  fill: n("~ ~ ~ ~ ~ ~ ~ ~ [12 12] [12 12] [12 12 12] [12 12 12] [30 30] [30 30 30] [30 30 30 30] 31")
247    .s("amencutup").cut(1).hpf(150).gain(saw.range(.5, .8)).orbit('drums')
248    .mask(lvl('drums').gt(0)).mul(velocity(lvl('drums').div(2))),
249  riser: s("white*16").hpf(saw.range(400, 8000)).gain(saw.range(.05, .35)).decay(.07).sustain(0).orbit('into'),
250  dropout: silence,
251}).mask("<0!3 1>")

dropout: the parts stop for the section's last beat. The outro has no next section, so its last beat plays.

254const lastBeat = "<0!3 [0 0 0 1]>"
255const gap = stack(
256  "<1!3 [1 1 1 0]>",
257  into.fmap((c) => c !== 'dropout').mask(lastBeat),
258  section.choice.fmap((s) => s === 'outro').mask(lastBeat),
259)

the master: the mix peaks at or under -1 dBFS (measured, tools/measure)

262stack(parts.mask(gap), transition, form.arrange(once(ending)))
263  .mul(velocity(dj.energy.score.round().div(3).range(.55, 1)))
264  .mul(postgain(.24))