song.jsannotatedsong.jssource262 lines · 16.2 KB · raw
1// COSMIC KNOT — post-hardcore / math-rock, 126bpm, 96 bars as written (8 more if Jev holds the fuse). Spec: SPEC.md
2// Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count).
3setcps(126/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 holds the fuse: when the bridge ends it decides whether the edge
9// comes now, or the float holds one more breath first. djJev, asked once
10// that is decided, says how much of the hidden strain shows. Every 8 bars.
11// After the snap nothing is Jev's and nothing leads back (walk, at the
12// bottom); without Jev it plays as written.
13const song = {
14  title: 'COSMIC KNOT',
15  what: 'experimental post-hardcore and math-rock, words and brief by oASTEROIDo: a knot held in a calm field. Weightless and floating for the whole first half, the heaviness withheld, tension stored where it does not show; then one hard cut, the snap, and everything lands at once; then a hollow float that cannot go back',
16  tempo: '126 bpm, 8 bars a section',
17  key: 'E major',
18  chords: 'E C#m A B, two bars each, all with an added ninth in the guitar',
19  sound: 'the float is a clean reverb-drenched guitar wash in groups of seven against the bar, a soft voice singing through it, and a low dissonance buried under it (the strain). The snap is a hard cut out of two bars of near silence into two tapped guitar lines weaving in sixteenths, and the voice a fifth up, overdriven; the impact is distorted chords, a sub bass and a half-time beat. No gradual heaviness: nothing heavy plays before the snap, and no drum',
20  lyrics: {
21    verse1: 'I float so still, you\'d swear I\'m fine / nothing in me moves, nothing shows the line / the quiet isn\'t peace / it\'s just the holding on',
22    verse2: 'under all this calm the strands are pulling tight / you can\'t hear it yet, it hides inside the light / something wound too far / to ever look that wrong',
23    prebuild: 'the drift goes thick / the still gets loud / I\'m smooth on top / and screaming underground',
24    bridge: 'this is the calmest I have ever been / right at the edge of what I\'m holding in / the knot don\'t show its teeth / till it\'s already done',
25    edge: 'one more breath of floating / one more second clean / then (two bars of nothing, and a cartoon dog in a burning room: This is fine)',
26    snap: 'it breaks, all of it, at once / every strand let go / the tapping tears the haze apart / the float was always loaded, now you know',
27    impact: 'and out of the break a whole world stands / matter from the tension, galaxies from my hands / the thing that broke me open / is the thing that made the land',
28    conversion: 'this is what I held, this is what it weighed / all that quiet drift was a fuse the whole way laid / I made something / the moment I gave way',
29    returnToFloat: 'I\'m floating again, but it isn\'t the same / the still is hollow now, the knot gave up its name / I can\'t get the quiet back / the quiet was the strain',
30  },
31}
32
33const formJev = jev({
34  state: song,
35  questions: {
36    section: choice('Pick the section of the song that plays next, from those allowed. You hold the fuse: after the bridge, either the edge comes now, and the snap straight after it, or the float holds one more breath first (the hold, eight bars without words). Judge it on how much of the strain has shown so far (history, measuredSound) and on the listeners\' reactions. After the snap there is no way back', {
37      intro: 'the guitar wash alone, weightless, and a cartoon dog saying This is fine',
38      verse1: 'verse 1: floating, the wash and the first sung lines; the strain hidden',
39      verse2: 'verse 2: still weightless, long string tails, the strands pulling tight underneath',
40      prebuild: 'the pre-build: the haze thickens, the tails stretch, nothing heavy',
41      bridge: 'the bridge: the calmest it has ever been, loaded underneath',
42      hold: 'the hold: one more breath of floating before the edge, no words: the wash with its longest tails and the strain at its most',
43      edge: 'the edge: the float at its stillest. One more breath of floating, one more second clean, then: two bars of near silence, and This is fine. The snap follows',
44      snap: 'the snap, a hard cut: two tapped guitar lines weaving fast, a broken beat, distorted stabs, a sub bass. It breaks, all of it, at once',
45      impact: 'the impact: distorted chords held long, sub bass, a half-time beat, crashes. A whole world stands',
46      conversion: 'the conversion: the impact with the tapping back over it, full weight: this is what I held',
47      decay: 'the decay, no words: one chord and one hit ringing out over the wash',
48      returnToFloat: 'the return to float: the wash thin and hollow, nothing under it. The quiet was the strain',
49      outro: 'one last chord fading over the hollow wash: the end',
50    }),
51  },
52})
53
54const djJev = jev({
55  state: song,
56  questions: {
57    strain: score('How much of the hidden strain should show in the next section (decidedThisSection)? It is a low dissonance, notes a semitone apart, buried under the float, and it is gone at the snap. As written it stays the same from verse 1 to the edge: whether it grows, and when, is yours. Judge from how the sections so far measured (measuredSound) and from the listeners\' reactions. The surface stays calm whatever you pick', [
58      'hidden: inaudible',
59      'a trace: felt more than heard',
60      'as written: heard under the wash on good speakers',
61      'loaded: plainly audible under the calm, on small speakers too',
62    ]),
63  },
64})
65
66// a little chance in the form: the section is drawn from Jev's own probabilities
67const { section } = formJev.every(8, { sample: 1 })
68const dj = djJev.every(8, {
69  after: { section },
70  fallback: { strain: 2 },
71})
72
73// bars: 1-8 intro | 9-16 verse 1 | 17-24 verse 2 | 25-32 pre-build | 33-40 bridge | 41-48 edge
74//       49-56 snap | 57-64 impact | 65-72 conversion | 73-80 decay | 81-88 return to float | 89-96 outro
75//       (each mask = 96 steps)
76//       105-112 the hold, which is not in the written order: it plays only if Jev picks it,
77//       between the bridge and the edge. It sits past a gap because the form reads what
78//       follows a section as written from where it sits.
79const inVerse1  = "<0!8 1!8 0!80>"
80const inVerse2  = "<0!16 1!8 0!72>"
81const inPre     = "<0!24 1!8 0!64>"
82const inBridge  = "<0!32 1!8 0!56>"
83const inEdge    = "<0!40 1!8 0!48>"
84const inSnap    = "<0!48 1!8 0!40>"
85const inImpact  = "<0!56 1!8 0!32>"
86const inConv    = "<0!64 1!8 0!24>"
87const inReturn  = "<0!80 1!8 0!8>"
88const broken    = "<0!48 1!24 0!24>"                    // the snap, the impact and the conversion
89const landed    = "<0!56 1!16 0!24>"                    // the impact and the conversion
90const decayHit  = "<0!72 1 0!23>"                       // the first bar of the decay
91const inHold    = "<0!104 1!8 0!888>"
92
93// two bars per chord: E C#m A B. The wash plays each as seven notes against
94// eight eighths, so the figure never lands the same way twice in a row.
95const washNotes = "<{e3 b3 f#4 g#4 b4 g#4 e4}%8!2 {c#3 g#3 d#4 e4 g#4 e4 b3}%8!2 {a2 e3 b3 c#4 e4 c#4 a3}%8!2 {b2 f#3 c#4 d#4 f#4 d#4 b3}%8!2>"
96const power = "<[e2,b2,e3]!2 [c#2,g#2,c#3]!2 [a1,e2,a2]!2 [b1,f#2,b2]!2>"
97const roots = "<e2!2 c#2!2 a1!2 b1!2>"                  // nothing under 55 Hz
98
99// the wash follows oASTEROIDo's headings, section by section: longer tails
100// and a thicker haze towards the bridge, the stillest at the edge
101const washOn    = "<0!8 1!38 0!26 1!20 0!4>"            // verse 1 to the edge's sixth bar; the decay to the outro's fourth
102const washGain  = "<.35!8 .22!32 .2!6 0!26 .25!8 .32!8 .28!4 0!4>"   // under the voice once it sings; stillest at the edge
103const washHpf   = "<100!80 700!16>"                     // thin and hollow once it returns to float
104const washTails = "<.5!16 .58!8 .65!8 .7!16 .5!32 .4!16>"            // the delay's feedback
105const washRing  = "<.9!16 1.4!8 1.8!8 2.2!16 .9!48>"                 // each note's release
106const washVib   = "<.06!16 .07!8 .09!8 .06!8 .02!8 .06!32 .04!16>"
107const thickOn   = "<0!24 1!16 0!56>"                    // the haze thickens: the pre-build and the bridge
108const crash     = "<0!48 1 0!7 1 0!7 1 0!31>"           // the snap, the impact, the conversion
109
110// ── parts ─────────────────────────────────────────
111// No grid: every note of the wash lands up to 20 ms late and of each tapped
112// line up to 8 ms, by its own amount, and the kit is loose.
113const drift = (p, n) => p.late(rand.seed(n).range(0, .01).segment(8))
114const washTone = (p, ring = washRing, tails = washTails, vib = washVib) =>
115  p.s("triangle").attack(.01).decay(.7).sustain(.15).release(ring)
116    .lpf(2400).hpf(washHpf).vib(4).vibmod(vib)
117    .delay(.4).delayfeedback(tails).room(.9).roomsize(9)
118const washLine = washTone(note(washNotes)).gain(washGain)
119const washUp = note(washNotes).add(note(12))            // the same figure an octave up, quieter: the haze thickening
120const wash = stack(
121  drift(washLine.mask(washOn), 1),
122  drift(washTone(washUp).gain(.2).mask(thickOn), 2),
123).orbit('wash')
124// the hold: the wash at its thickest, with its longest tails, as still as the edge
125const holdWash = stack(
126  drift(washTone(note(washNotes), 2.6, .7, .02).gain(.22).mask(inHold), 1),
127  drift(washTone(washUp, 2.6, .7, .02).gain(.2).mask(inHold), 2),
128).orbit('wash')
129const opening = drift(washLine.mask("<1!8 0!88>"), 1).orbit('opening')
130
131// the strain: notes a semitone apart, low and filtered, held under the calm
132// for a whole section at a time. Written flat; Jev's level is the whole of
133// its growth: each step is 6 dB and opens the filter.
134const strainLevel = dj.strain.score.round()
135// Jev's level, read where a held note begins. A note that lasts its section
136// would otherwise read the whole section at once, and the question about the
137// next one would go out as this one starts, not halfway through it.
138const atOnset = (p) => p.withQuerySpan((span) => span.withEnd(() => span.begin.add(1 / 8)))
139const drone = stack(note("[e2,f2]").gain(.2), note("[e3,f3]").gain(.07)).slow(8)
140  .s("sawtooth").attack(.8).release(.8).room(.6)
141const strain = drone.legato("<1!40 .75!8 1!48>").mask("<0!8 1!40 0!48>")   // verse 1 to the edge's sixth bar
142  .lpf(atOnset(strainLevel.pick([240, 330, 450, 650])))
143  .mul(gain(atOnset(strainLevel.pick([.25, .5, 1, 2])))).orbit('strain')
144// in the hold it is at its most, whatever Jev set before
145const holdStrain = drone.mask(inHold).lpf(650).mul(gain(2)).orbit('holdStrain')
146
147// two tapped lines, left and right, in sevens and nines against the
148// sixteenths, each figure on its chord: the snap's loudest pitched part
149const tapL = "<{e5 g#5 b5 e6 d#6 b5 g#5}%16!2 {c#5 e5 g#5 c#6 b5 g#5 e5}%16!2 {a4 c#5 e5 a5 g#5 e5 c#5}%16!2 {b4 d#5 f#5 b5 a5 f#5 d#5}%16!2>"
150const tapR = "<{b4 e5 f#5 g#5 b5 g#5 f#5 e5 d#5}%16!2 {g#4 c#5 d#5 e5 g#5 e5 d#5 c#5 b4}%16!2 {e4 a4 b4 c#5 e5 c#5 b4 a4 g#4}%16!2 {f#4 b4 c#5 d#5 f#5 d#5 c#5 b4 a4}%16!2>"
151const tapOn = "<0!48 1!8 0!8 1!8 0!24>"                 // the snap and the conversion
152const tapLine = (notes, n) => note(notes).s("square").attack(.002).decay(.09).sustain(.3).release(.05).lpf(5200)
153  .delay(.3).delayfeedback(.35).room(.4).gain(1.1).mask(tapOn)
154  .late(rand.seed(n).range(0, .004).segment(16))
155const tap = stack(tapLine(tapL, 3).pan(.2), tapLine(tapR, 4).pan(.8)).orbit('tap')
156
157const heavyTone = (p) => p.s("sawtooth").distort(2).room(.4)
158const heavy = stack(
159  // the snap: two stabs a bar, under the tapping
160  heavyTone(note(power).struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~ ~ ~")).lpf(2400).decay(.25).sustain(.5).gain(.42).mask(inSnap),
161  // held long; back in each bar a line starts in, so the words stay over them
162  heavyTone(note(power).struct("x@6 x@6 x@4")).lpf(1600).attack(.005).sustain(.8).release(.2).gain("<.13 .2>").mask(landed),
163).orbit('heavy')
164// the chord that rings through the decay: what the decay is made of. It rings four bars.
165const ringing = heavyTone(note("[e2,b2,e3]")).lpf(2400).attack(.005).decay(7).sustain(0).release(7).gain(.24)
166  .mask(decayHit).orbit('ringing')
167
168// the sub, from the snap on: a sine on the roots and a triangle an octave up, so small speakers hear it
169const bass = stack(
170  note(roots).s("sine").gain(.8),
171  note(roots).add(note(12)).s("triangle").gain(.2),
172).struct("x@6 x@6 x@4").attack(.005).sustain(.9).release(.2).mask(broken).orbit('bass')
173
174const loose = (p, n) => p.nudge(rand.seed(n).range(0, .012))
175const kit = stack(
176  // the snap: a broken beat in sixteenths. The first drum of the song.
177  loose(s("bd ~ ~ bd ~ sd ~ bd ~ ~ bd ~ sd ~ ~ bd").gain(.9).mask(inSnap), 5),
178  loose(s("hh*8").gain("[.3 .16]*4").mask(inSnap), 6),
179  // the impact: half time
180  loose(s("bd ~ ~ ~ ~ ~ bd ~ sd:3 ~ ~ ~ ~ ~ bd bd").gain(.68).mask(landed), 5),
181  loose(s("[~ hh]*4").gain(.25).mask(landed), 6),
182  s("hh27:1").gain("<1.8!56 1.3!40>").mask(crash),
183).orbit('drums')
184const lastHit = stack(s("bd").gain(.8), s("hh27:1").gain(1.8)).mask(decayHit).orbit('lastHit')
185
186// the sung lines, one per 2 bars, each on the melody in score.abc.
187// The float's are soft and wet and start off the downbeat, each by its own amount.
188const floatLine = (names, where) => s(names).room(.6).roomsize(6).delay(.25).delayfeedback(.4).gain(1)
189  .mask(where).late("<.09!2 .14!2 .06!2 .11!2>")
190// From the snap to the conversion: a fifth up, dry, overdriven, doubled a little sharp.
191const heavyLine = (names, where) => stack(
192  s(names).pan(.42),
193  s(names).speed(1.013).pan(.58),
194).distort(1.2).room(.25).gain(.92).mask(where)
195const vox = stack(
196  floatLine("<ck1a ~ ck1b ~ ck1c ~ ck1d ~>", inVerse1),
197  floatLine("<ck2a ~ ck2b ~ ck2c ~ ck2d ~>", inVerse2),
198  floatLine("<ck3a ~ ck3b ~ ck3c ~ ck3d ~>", inPre),
199  floatLine("<ck4a ~ ck4b ~ ck4c ~ ck4d ~>", inBridge),
200  floatLine("<ck5a ~ ck5b ~ ~ ~ ~ ~>", inEdge),
201  // the first line an eighth late: the hit lands, then "It breaks"
202  heavyLine("<ck6a ~ ck6b ~ ck6c ~ ck6d ~>", inSnap).late("<.125 0!7>"),
203  heavyLine("<ck7a ~ ck7b ~ ck7c ~ ck7d ~>", inImpact),
204  heavyLine("<ck8a ~ ck8b ~ ck8c ~ ck8d ~>", inConv),
205  // the float's voice again, behind the wash's own high-pass
206  floatLine("<ck9a ~ ck9b ~ ck9c ~ ck9d ~>", inReturn).hpf(700),
207).orbit('vocals')
208// "Then", and the dog: what the edge is made of
209const then = s("<~ ~ ~ ~ ck5c ~ ~ ~>").room(.6).roomsize(6).gain(1).mask(inEdge).orbit('vocals')
210const fine = s("ckfine").room(.3).gain("<.6!8 1.2!88>").mask("<0!6 1 0!40 1 0!48>").orbit('vocals')
211
212// the end: one last chord of the wash as the outro opens, fading over the hollow wash
213const ending = note("[e3,b3,f#4,g#4]").s("triangle").attack(.02).decay(6).sustain(0).release(6).lpf(2000).hpf(400)
214  .room(1).roomsize(10).gain(.6).mask("<0!88 1 0!7>").orbit('ending')
215
216const once = (p) => p.mask("<1!96 0!904>")   // play once, then silence
217
218// ── the form, as rules in code: which sections may follow which ──
219// `at` is where each starts as written; an empty `next` ends the song. The
220// only choice is the fuse, after the bridge: the edge now, or the hold and
221// then the edge. The edge is sung once and the snap always follows it;
222// nothing after the snap leads back before it (SPEC.md, "The form").
223const form = walk(section, {
224  max: 13,
225  sections: {
226    intro: { at: 0, next: ['verse1'] },
227    verse1: { at: 8, next: ['verse2'] },
228    verse2: { at: 16, next: ['prebuild'] },
229    prebuild: { at: 24, next: ['bridge'] },
230    bridge: { at: 32, next: ['edge', 'hold'] },
231    hold: { at: 104, next: ['edge'] },
232    edge: { at: 40, next: ['snap'] },
233    snap: { at: 48, next: ['impact'] },
234    impact: { at: 56, next: ['conversion'] },
235    conversion: { at: 64, next: ['decay'] },
236    decay: { at: 72, next: ['returnToFloat'] },
237    returnToFloat: { at: 80, next: ['outro'] },
238    outro: { at: 88, next: [] },
239  },
240})
241
242// ── the song: Jev's section and strain, composed ──
243// One postgain for the whole mix keeps the master's peak under -1 dBFS (SPEC.md, "Mix").
244// The hold sits outside the song as written (`once`), on its own masks.
245form.arrange(stack(holdWash, holdStrain, once(stack(
246  wash,
247  opening,
248  strain,
249  tap,
250  heavy,
251  ringing,
252  bass,
253  kit,
254  lastHit,
255  vox,
256  then,
257  fine,
258  ending,
259  // nothing sounds here: it reads Jev's level every bar, the intro included,
260  // so each section's question is asked in time (a jev() asks as it is read)
261  strainLevel.mask("0"),
262)))).mul(postgain(.32))