song.jsannotatedsong.jssource265 lines · 13.8 KB · raw

TYPESAFE JUNGLE — 170bpm, 64 bars as written; Jev arranges up to 28 four-bar sections. Spec: SPEC.md vox: the repo's samples/ folder, served with the site (see README) 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.)

5setcps(170/60/4)
6await samples('github:tidalcycles/dirt-samples')
7await 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. djJev is asked every section; formJev only where the form branches. The form's rules (walk, at the bottom) narrow the sections Jev may pick and end the song; without Jev it plays as written.

15const song = {
16  title: 'TYPESAFE JUNGLE',
17  what: 'a jungle track about Jev, the typed-decision model from TypeSafe AI, built on the My Name Is Jeff meme: the hook swaps Jeff for Jev',
18  tempo: '170 bpm, 4/4, 4 bars a section',
19  key: 'C minor',
20  chords: 'Cm Ab Fm G, one chord per bar, under a music-box lead and a reese bassline',
21  lyrics: {
22    build1: 'My name is Jeff / My name is Jev. J, E, V.',
23    build1b: 'My name is Jeff / Type safe Jev / Type safe... Jev',
24    build2: 'My name is Jeff / My name is Jev. J, E, V. / Type safe Jev / Type safe... Jev',
25  },
26}
28const formJev = jev({
29  state: song,
30  questions: {
31    section: choice('Pick the section of the song that plays next, from those allowed. Choosing outro ends the song (outro, then the final hit): choose it once the song has built to its peak and made its point, before it drags', {
32      intro: 'the music-box lead alone',
33      tease: 'the lead with a thin, high-passed amen break coming in',
34      build1: 'the build: the hook vocals, the amen filter opening, a low sub',
35      build1b: 'the build peaks: the hook, a snare roll and a noise riser into the drop',
36      drop1: 'drop 1: full jungle drums, the reese and sine bassline, eighth hats, a crash on the downbeat',
37      drop1b: 'drop 1 continues',
38      drop1c: 'drop 1 with the rave stabs',
39      drop1d: 'the end of drop 1, stabs and all',
40      breakdown: 'the breakdown: drums out, just the lead and the low sub',
41      build2: 'the hook returns, the whole Jeff-to-Jev swap in 4 bars, with a noise riser into the second drop',
42      drop2: 'drop 2: everything, sixteenth hats, rave stabs, a crash on the downbeat',
43      drop2b: 'drop 2 continues',
44      drop2c: 'drop 2, the peak of the song',
45      drop2d: 'the last bars of drop 2',
46      outro: 'the outro: kick and bass out, the amen thins, the lead starts to fade: the way to the end',
47      final: 'one last kick and crash left to ring while the lead fades out: the end',
48    }),
49  },
50})

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

53const 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', {
54  out: 'silent for the section',
55  back: 'under the rest of the mix, at half velocity',
56  full: 'as written',
57})
58const djJev = jev({
59  state: song,
60  questions: {
61    lead: level('the music-box lead (the tune, on its own in the intro, over everything from the intro until it fades in the outro)'),
62    drums: level('the jungle drums (the amen break with kick and snare in the drops, a thin filtered amen in the tease and builds, hats, crash, and the snare roll and riser into each drop, and the amen alone, thinning, in the outro)'),
63    stabs: level('the rave stabs (in the second half of drop 1 and all of drop 2)'),
64    bass: level('the bass (the reese and sine bassline in the drops, a low sub in the builds: the floor under them)'),
65    vocals: level('the vocals (the My name is Jeff / Type safe Jev hook, only in the builds)'),
66    into: choice('How should the song move into the next section (decidedThisSection)? It plays over the last bar of the section before it', {
67      cut: 'straight in, no transition',
68      fill: 'a snare roll rising in pitch into the first beat',
69      riser: 'white noise sweeping up over the last bar',
70      dropout: 'everything stops for the last beat, a gap before the section hits',
71    }),
72    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', [
73      'held back: about 5 dB under full',
74      'steady: about 3 dB under full',
75      'driving: about 1.5 dB under full',
76      'all out: full, as written',
77    ]),
78  },
79})

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

82const { section } = formJev.every(4, { sample: 0.7 })

Where each part plays as written. Jev is asked about a part only in these sections; anywhere else it cannot sound, so it is not asked (full). The lead plays in every section.

87const plays = {
88  drums: ['tease', 'build1', 'build1b', 'drop1', 'drop1b', 'drop1c', 'drop1d', 'build2',
89    'drop2', 'drop2b', 'drop2c', 'drop2d', 'outro'],
90  stabs: ['drop1c', 'drop1d', 'drop2', 'drop2b', 'drop2c', 'drop2d'],
91  bass: ['build1', 'build1b', 'drop1', 'drop1b', 'drop1c', 'drop1d', 'breakdown', 'build2',
92    'drop2', 'drop2b', 'drop2c', 'drop2d'],
93  vocals: ['build1', 'build1b', 'build2'],
94}
95const levels = (name) => ({ section: next }) =>
96  plays[name] && !plays[name].includes(next) ? ['full']
97    : name === 'drums' && next === 'outro' ? ['back', 'full']   // the outro is the amen alone
98    : ['out', 'back', 'full']
99const dj = djJev.every(4, {
100  after: { section },
101  forget: ['into'],
102  allow: {
103    lead: levels('lead'), drums: levels('drums'), stabs: levels('stabs'),
104    bass: levels('bass'), vocals: levels('vocals'),
105    // out of a build the roll and riser are already written: a cut or a dropout
106    into: ({ playingNow }) => (['build1b', 'build2'].includes(playingNow)
107      ? ['cut', 'dropout'] : ['cut', 'fill', 'riser', 'dropout']),
108  },
109  fallback: { lead: 'full', drums: 'full', stabs: 'full', bass: 'full', vocals: 'full', into: 'cut', energy: 3 },
110})

A part at Jev's level: silent when out, half velocity when back, as written when full. Each part has its own orbit, named for its question, so the meter hears it alone (measuredSound); the lead and the vocals each get their own delay line there.

116const part = (name, pat) =>
117  pat.orbit(name)
118    .mask(dj[name].choice.fmap((c) => c !== 'out'))
119    .mul(velocity(dj[name].choice.fmap((c) => (c === 'back' ? .5 : 1))))

bars: 1-8 intro | 9-16 build+hook | 17-32 DROP 1 | 33-36 breakdown 37-40 build+hook | 41-56 DROP 2 | 57-64 outro (each mask = 64 steps)

123const tease1 = "<0!4 1!12 0!48>"            // filtered amen, opening from the tease into drop 1
124const tease2 = "<0!36 1!4 0!24>"            // and again over build 2
125const hook1  = "<0!8 1!8 0!48>"             // vocals ONLY here, in the builds
126const hook2  = "<0!36 1!4 0!24>"
127const drops  = "<0!16 1!16 0!8 1!16 0!8>"
128const drop1  = "<0!16 1!16 0!32>"
129const drop2  = "<0!40 1!16 0!8>"
130const stabs  = "<0!24 1!8 0!8 1!16 0!8>"
131const lowSub = "<0!8 1!8 0!16 1!8 0!24>"
132const riser  = "<0!14 1!2 0!22 1!2 0!24>"

the snare roll ducks out under every spoken word: in bar 15 it plays beat 4 only, in bar 16 beat 1 only; in build 2 the words fill bars 39-40, so the noise riser carries that build alone

136const roll   = "<0!14 [0 0 0 1] [1 0 0 0] 0!48>"
137const crash  = "<0!16 1 0!23 1 0!23>"
138const outroB = "<0!56 1!4 0!4>"
139const final  = "<0!60 1 0!3>"
140const leadGain = "<.3!16 .2!16 .3!8 .2!16 .28!4 .2 .14 .08 0>"
142const roots = "<c2 ab1 f1 g1>"   // F1-C2, 44-65 Hz: a sub that small speakers still carry
143const bassline = "<[c2@3 c2@3 c2@4 c3@2 bb1@4] [ab1@3 ab1@3 ab1@4 ab2@2 g1@4] [f1@3 f1@3 f1@4 f2@2 ab1@4] [g1@3 g1@3 g1@4 b1@2 d2@4]>"
144const 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 ───────────────────────────────────────── music-box lead: the tune, fading out at the end each bar outlines its chord; the G bar takes B natural (harmonic minor)

149const lead = n("<[0 ~ 2 ~ 4 ~ 7 ~] [~ 0 ~ 3 ~ 5 ~ 3] [5 ~ 3 ~ 0 ~ -2 ~] [1 ~ 4 ~ 6 ~ 4 ~]>")
150  .scale("<C4:minor!3 C4:harmonic:minor>").s("triangle").decay(.3).sustain(0)
151  .delay(.3).delaytime(.375).delayfeedback(.35).room(.4).gain(leadGain)

each amen slice cuts the one before (cut group 1); the roll is group 2

154const drums = stack(
155  s("[bd:3,808bd:1]").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~").gain(.75).mask(drops),
156  s("sn:3").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").room(.15).gain(.9).mask(drops),
157  n(amen).s("amencutup").hpf(150).gain(.75).cut(1).mask(drops),
158  // the filter opens in one sweep from the tease into the drop, and again over build 2
159  n("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15").s("amencutup")
160    .hpf(saw.range(2500,300).slow(12).late(4)).gain(.55).cut(1).mask(tease1),
161  n("0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15").s("amencutup")
162    .hpf(saw.range(2500,300).slow(4)).gain(.55).cut(1).mask(tease2),
163  n(amen).s("amencutup").hpf(saw.range(300,3000).slow(4)).gain(.6).cut(1).mask(outroB),
164  s("hh*8").gain(".35 .18").mask(drop1),
165  s("hh*16").gain(".35 .15 .25 .15").mask(drop2),
166  s("hh27:1").gain(2.5).mask(crash),   // the crash: Dirt-Samples' cr is a ride
167  // the roll rises across both bars of the build
168  s("sn:3*16").gain(saw.range(.25,.6).slow(2)).speed(saw.range(1,1.4).slow(2)).cut(2).mask(roll),
169  s("white*16").decay(.06).sustain(0)
170    .hpf(saw.range(400,9000).slow(2)).gain(saw.range(.05,.35).slow(2)).mask(riser)
171)

rave stabs, second half of drop 1 and all of drop 2 stab:3 is pitched at C#; these speeds put it on C, Ab, F and G

175const rave = s("stab:3").struct("x ~ ~ x ~ ~ x ~ ~ ~ x ~ ~ ~ ~ ~")
176  .speed("<.944 .749 .63 .707>").room(.3).gain(.45).mask(stabs)
178const bass = stack(
179  note(bassline).s("supersaw").detune(.6)
180    .lpf(sine.range(220,900).slow(8)).lpq(3).shape(.3).gain(.5).mask(drops),
181  note(bassline).s("sine").gain(.5).mask(drops),
182  // a little shape, so its second harmonic carries on small speakers
183  note(roots).s("sine").attack(.3).release(.5).shape(.25).gain(.45).mask(lowSub)
184)

the hook — builds only. Build 1: "my name is jeff … my name is jev, J E V … my name is jeff … type safe jev", then the pickup "type safe … jev" into the drop. Build 2 packs the swap into 4 bars; mnjev runs 2.3 s, so tsjev waits for beat 3 of bar 39.

190const vox = stack(
191  s("<jeff ~ mnjev ~ jeff ~ tsjev [~ ts ~ jev]>").mask(hook1),
192  s("<jeff mnjev [~ ~ tsjev ~] [~ ts ~ jev]>").mask(hook2),
193).gain(1.3).room(.25).delay(.25).delaytime(.375).delayfeedback(.3)

the end: one last hit, then let it ring. It always plays.

196const ending = stack(s("[bd:3,808bd:1]").gain(.75), s("hh27:1").gain(2.5))
197  .room(.6).size(.9).mask(final)
199const 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 drop's loop goes round once more at most (SPEC.md, "Loops end").

205const form = walk(section, {
206  max: 28,
207  sections: {
208    intro: { at: 0, next: ['tease'] },
209    tease: { at: 4, next: ['build1'] },
210    build1: { at: 8, next: ['build1b'] },
211    build1b: { at: 12, next: ['drop1'] },
212    drop1: { at: 16, next: ['drop1b'] },
213    drop1b: { at: 20, next: ['drop1c'] },
214    drop1c: { at: 24, next: ['drop1d'], times: 2 },
215    drop1d: { at: 28, next: ['breakdown', 'drop1c'] },
216    breakdown: { at: 32, next: ['build2'] },
217    build2: { at: 36, next: ['drop2'] },
218    drop2: { at: 40, next: ['drop2b'], times: 2 },
219    drop2b: { at: 44, next: ['drop2c'] },
220    drop2c: { at: 48, next: ['drop2d', 'drop2'] },
221    drop2d: { at: 52, next: ['outro', 'breakdown', 'drop2'] },
222    outro: { at: 56, next: ['final'] },
223    final: { 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 vocals stand apart so a dropout never cuts them: out of a build, the last "Jev" lands alone in the gap.

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

── 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.

241const into = dj.into.choice.early(1)
242const transition = into.pick({
243  cut: silence,
244  fill: s("sn:3*16").gain(saw.range(.25, 1)).speed(saw.range(1, 1.4)).cut(2).orbit('drums'),
245  riser: s("white*16").decay(.06).sustain(0)
246    .hpf(saw.range(400, 9000)).gain(saw.range(.05, .35)),
247  dropout: silence,
248}).mask("<0!3 1>")

dropout: every part but the vocals stops for the section's last beat. The final section has no next section, so its last beat plays.

251const lastBeat = "<0!3 [0 0 0 1]>"
252const gap = stack(
253  "<1!3 [1 1 1 0]>",
254  into.fmap((c) => c !== 'dropout').mask(lastBeat),
255  section.choice.fmap((s) => s === 'final').mask(lastBeat),
256)

The energy scales every part's velocity together (0.56 to 1: about -5 dB up to as written), so the mix keeps its balance; the last hit is outside it and always plays as written. The master level is outside everything: superdough has no limiter, so it keeps the peaks under -1 dBFS.

262stack(
263  stack(parts.mask(gap), vocals, transition).mul(velocity(dj.energy.score.div(3).range(.56, 1))),
264  form.arrange(once(ending)),
265).mul(postgain(.36))