FORTY-FIVE PERCENT — lo-fi hip hop, 80bpm, 48 bars as written; Jev arranges up to 16 sections and it fades out. Spec: SPEC.md Start from STOPPED so the song begins at bar 1 (re-running it mid-play keeps the cycle count).
── 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: 'FORTY-FIVE PERCENT', 15 what: 'lo-fi hip hop about a stock-picking bot: on a three-way guess (up, flat or down) it scored 45 percent, better than the 33 percent of a blind guess and worse than an index fund; it fades out on This is fine', 16 tempo: '80 bpm, 16ths swung by a third, 4 bars a section', 17 key: 'D dorian (C major)', 18 chords: 'Dm7 Dm7 Cmaj7 Cmaj7 Fmaj7 Fmaj7 Em7 Em7, two bars each', 19 lyrics: { 20 verse1: 'Forty-five percent / Up, flat, or down', 21 verse1b: 'Better than a coin / Worse than an index fund', 22 verse2: 'A disciplined reasoning layer / Not a trading model', 23 verse2b: 'Don\'t trust the word / Probability', 24 outroEnd: 'This is fine', 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. The outro starts the fade that ends the song: choose it once the song has built to its peak and made its point, before it drags', { 32 intro: 'Rhodes keys with tape wobble, vinyl crackle and the chest-thump hum loop', 33 verse1: 'first half of verse 1, vocoded, over the boom-bap and bass', 34 verse1b: 'second half of verse 1, vocoded', 35 beat: 'the beat alone: the hum loop with Stonks chopped into it, no vocals', 36 beatB: 'the beat alone, second half', 37 verse2: 'first half of verse 2, vocoded', 38 verse2b: 'second half of verse 2, vocoded', 39 beatFull: 'the fuller beat: sixteenth hats, an open hat, a kick variation, the bass up to the fifth', 40 beatFullB: 'the fuller beat, middle', 41 beatFullC: 'the fuller beat, last four bars', 42 outro: 'the drums fall away and the bed starts to fade', 43 outroEnd: 'the bed fades down over the crackle, then This is fine: the end', 44 }), 45 }, 46})
how much of each part the section decidedThisSection has: out, back or full
49const 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', [ 50 'out: silent for the section', 51 'back: under the rest of the mix', 52 'full: as written', 53]) 54const djJev = jev({ 55 state: song, 56 questions: { 57 keys: level('the keys (Rhodes chords with tape wobble and the vinyl crackle, the bed under every section as written)'), 58 loop: level('the hum loop (the chest-thump hum\'s thumps on the beat grid, with Stonks chopped into it in the beat sections, from the intro to the outro)'), 59 drums: level('the drums (the swung boom-bap kick, snare and hats, from verse 1 until the outro)'), 60 bass: level('the bass (a round filtered bass locked to the kick, from verse 1 until the outro)'), 61 // the words are always sung: in a verse Jev only sets how far forward 62 vocals: choice('How far forward should the vocals (the vocoded verse lines) sit in the next section (decidedThisSection)? They are always sung', { 63 full: 'full: as written, the words up front', 64 back: 'back: a little under the beat', 65 }), 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 swung snare fill tumbling into the first beat, in place of the drums for that bar', 69 riser: 'the vinyl crackle thickening into a hiss that swells over the last bar', 70 dropout: 'everything stops for the last beat, a gap before the section comes in', 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 'hushed: background music, well under full', 74 'laid back: a little under full', 75 'as written', 76 'up front: louder than 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 }) 83const dj = djJev.every(4, { 84 after: { section }, 85 forget: ['into'], 86 // vocals only in the verses: elsewhere there is nothing to ask 87 allow: { vocals: (d) => (/^verse/.test(d.section) ? ['back', 'full'] : ['full']) }, 88 fallback: { keys: 2, loop: 2, drums: 2, bass: 2, vocals: 'full', into: 'cut', energy: 2 }, 89})
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).
The vocals are never out: back or full
bars: 1-4 intro | 5-12 verse 1 | 13-20 beat | 21-28 verse 2 | 29-40 beat, fuller 41-48 outro: drums fall away, the bed fades (each mask = 48 steps)
101const verse1 = "<0!4 1!8 0!36>" 102const verse2 = "<0!20 1!8 0!20>" 103const kick = "<0!4 1!40 0!4>" 104const snare = "<0!4 1!38 0!6>" 105const hats8 = "<0!4 1!24 0!20>" 106const hats16 = "<0!28 1!14 0!6>" 107const fuller = "<0!28 1!12 0!8>" 108const bassOn = "<0!4 1!24 0!12 1!4 0!4>" // the plain bass line; the fuller beat has its own
Stonks never plays in a section's last bar: that bar is the transition's. Chopped into stutters in each beat section's first two bars, the whole word on the last 8th of its third.
the bed falls to about -12 dB, never to nothing; the crackle is not in it
two bars per chord; the cycle starts on Dm7 so both verses open on Fmaj7: Dm7 Dm7 Cmaj7 Cmaj7 Fmaj7 Fmaj7 Em7 Em7 (MIDI numbers)
── parts ───────────────────────────────────────── Every part is straight here; one swing, 16ths by a third, is applied to the whole band below. ── keys: Rhodes-ish comping; the tape wobble is a slow vibrato ──
vinyl crackle: sparse, fixed, high-passed noise ticks
── percussion loop: the chest-thump hum's thumps, one per slice of ffhum (0 A, 1 E, 2 D, 3 a short thump), on the 8th grid, and Stonks ──
── drums: dusty boom-bap ──
151const drums = stack( 152 s("bd:4").struct("x ~ ~ ~ ~ ~ ~ x ~ ~ x ~ ~ ~ ~ ~").lpf(3500).gain(.31).mask(kick), 153 // the fuller beat's two-bar kick variation: a pickup into every other bar 154 s("bd:4").struct("<~ [~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ x]>").lpf(3500).gain(.26).mask(fuller), 155 s("sn:2").struct("~ ~ ~ ~ x ~ ~ ~ ~ ~ ~ ~ x ~ ~ ~").lpf(3500).room(.2).gain(.26).mask(snare), 156 s("hh*16").gain(".16 0 .08 0").lpf(6000).mask(hats8), 157 s("hh*16").gain(".14 .056 .1 .056").lpf(6000).mask(hats16), 158 // the fuller beat's open hat on the "and" of 4 159 s("808oh").struct("~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ x ~").lpf(7000).gain(.045).mask(fuller) 160)
── bass: locked to the kick, root, root, 5th ──
163const bassSound = (pat) => pat.s("sawtooth").attack(.005).decay(.3).sustain(.5).release(.15) 164 .lpf(700).lpq(2).shape(.3).gain(.26).vib(.7).vibmod(.1) 165const low = stack( 166 bassSound(note("0@7 0@3 7@6").add(note(roots))).mask(bassOn), 167 // the fuller beat: the bar's second half on the 5th, again on the "and" of 4 168 bassSound(note("0@7 0@3 7@4 7@2").add(note(roots))).mask(fuller) 169)
── vox: vocoder, one line per 2 bars, each on its chord ── <…> indexes by cycle mod 8; verses start on cycles 4 and 20, so line 1 sits at index 4. "Up, flat, or down" comes in a beat late: on the bar, the downbeat buried its "Up", and an 8th late, the beat-2 snare buried its "flat".
Dry on the word. The echo is a quiet copy of the line a dotted half (2.25 s) after it starts, so it lands after the line ends (the longest is 2.0 s). It is written out, not .delay(): superdough makes its DelayNode with the default one-second maximum, so a longer delaytime plays at 1 s, on the line.
the last word: it always plays, over the faded bed
188const lastWord = s("fffine").gain(.8).room(.3).mask(fine)
the outro fade (bars 41-47 fall to about -12 dB) multiplies velocity, as Jev's part levels do; Jev's energy below uses postgain
192const faded = (...parts) => stack(...parts).mul(velocity(fade))
play once, 48 bars as written; the last notes start on bar 47's third beat, so the song stops about a beat after This is fine
── 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.
The verse and beat halves are forced together, so a vocal line is never
cut; every path to the outro passes through verse 2, so every line is sung.
203const form = walk(section, { 204 max: 16, 205 sections: { 206 intro: { at: 0, next: ['verse1'] }, 207 verse1: { at: 4, next: ['verse1b'] }, 208 verse1b: { at: 8, next: ['beat'] }, 209 beat: { at: 12, next: ['beatB'] }, 210 beatB: { at: 16, next: ['verse2', 'verse1'] }, 211 verse2: { at: 20, next: ['verse2b'] }, 212 verse2b: { at: 24, next: ['beatFull', 'beat'] }, 213 beatFull: { at: 28, next: ['beatFullB'] }, 214 beatFullB: { at: 32, next: ['beatFullC', 'verse2', 'outro'] }, 215 beatFullC: { at: 36, next: ['outro', 'verse2'] }, 216 outro: { at: 40, next: ['outroEnd'] }, 217 outroEnd: { at: 44, next: [] }, 218 }, 219})
── 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.
a fill replaces the drums for its bar
226const noFill = stack("<1!3 0>", into.fmap((c) => c !== 'fill').mask(lastBar))
── the song: Jev's section, parts and energy, composed ── Each section plays its own bars, each part at the level Jev chose. The fade and the levels both multiply velocity, so neither undoes the other; one swing for the whole band; the last word always plays.
A transition plays at the level of the part it belongs to in the section it plays in, and fades with it.
245const transition = into.pick({ 246 cut: silence, 247 fill: stack( 248 s("bd:4").struct("x ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~").gain(.31), 249 s("sn:2").struct("~ ~ ~ ~ ~ ~ x ~ ~ ~ x ~ x ~ x x").gain(.26) 250 ).lpf(3500).room(.2).orbit('drums') 251 .mask(dj.drums.score.round().gt(0)).mul(velocity(dj.drums.score.round().div(2))), 252 riser: s("white*32").decay(.012).sustain(0).hpf(saw.range(1500, 7000)).lpf(9000) 253 .gain(saw.range(.016, .11)).orbit('keys') 254 .mask(dj.keys.score.round().gt(0)).mul(velocity(dj.keys.score.round().div(2))), 255 dropout: silence, 256}).mask(lastBar).swingBy(1/3, 8).mul(velocity(form.arrange(once(fade))))
dropout: the parts stop for the section's last beat