Skip to content

Instantly share code, notes, and snippets.

@capttwinky
Last active May 2, 2026 06:10
Show Gist options
  • Select an option

  • Save capttwinky/e78461b0efdc08bedb06a45cbf6450c9 to your computer and use it in GitHub Desktop.

Select an option

Save capttwinky/e78461b0efdc08bedb06a45cbf6450c9 to your computer and use it in GitHub Desktop.

Generative Blues

This work approaches blues as a condition of persistence rather than a fixed form. Instead of composing a linear piece, it constructs a system of motif families and behavioral rules through which four voices move within a shared harmonic space over time.

Each voice operates within a constrained vocabulary of phrases. These phrases are revisited, repeated, and slowly altered through a weighted walk, favoring return over departure. Ideas are not quickly resolved; they are held, reconsidered, and reintroduced. The system allows dissonances to linger, producing subtle instabilities—beating tones, suspended relationships, and partial alignments that never fully settle. Rather than correcting these tensions, the piece remains inside them.

The acoustic palette is intentionally limited. All voices are derived from simple waveforms with stable harmonic structures, shaped through slow filtering and extended release envelopes. These constraints transform discrete note events into overlapping fields of sound, where tones interfere, merge, and separate over time. The design avoids abrupt transients and sharp spectral spikes, favoring sustained interactions over percussive contrast. Tension emerges not from sudden change, but from the accumulation of small differences within a stable sonic environment.

Timing is deliberately elastic. Phrases stretch, overlap, and decay into one another, shifting attention away from isolated gestures toward continuous motion. Occasional intensifications occur—brief increases in activity or density—but they resolve into longer, sustained tones. These moments echo the structure of a blues phrase without directly reproducing it.

A set of parameters governs the rate at which the system develops, allowing the same structure to unfold over minutes or over hours. As density increases, the ensemble becomes more insistent: voices overlap more frequently, hold their ground longer, and assert themselves within the shared space. The piece does not simply become fuller—it becomes more confrontational in its use of repetition and unresolved material.

This work models a specific condition: remaining within a limited set of materials, allowing tension to accumulate, and resisting the impulse to resolve. Meaning emerges not through progression, but through duration—through staying with a sound long enough for its internal structure to become perceptible. The result is a continuously evolving performance that exists between composition and improvisation, where instability is not a problem to be solved, but a space to inhabit.

setcpm(72)
// Generative Blues
// Parser-safe version with weighted motif walking and light runtime evolution.
// Four square-based voices in four registers.
let conductor = "<1 0.92 0.96 0.9>".slow(32)
let sopSpeak = "<1 0.18 0.08 0.72 1 0.15 0.08 0.62>".slow(24)
let altoSpeak = "<0.08 1 0.22 0.12 0.08 0.9 0.2 0.1>".slow(24)
let tenorSpeak = "<0.12 0.16 1 0.2 0.1 0.18 0.92 0.16>".slow(24)
let bassSpeak = "<0.72 0.68 0.74 0.7 0.78 0.72 0.76 0.7>".slow(24)
let swing = sine.range(-0.03, 0.035).slow(2)
let leadS = "<1 0.22 0.08 0.65 1 0.18 0.08 0.55>".slow(48)
let leadA = "<0.08 1 0.18 0.1 0.08 0.92 0.16 0.1>".slow(48)
let leadT = "<0.12 0.18 1 0.2 0.1 0.16 0.94 0.18>".slow(48)
let leadB = "<0.62 0.62 0.66 0.66 0.7 0.7 0.74 0.74>".slow(48)
let push = "<0 0.008 -0.004 0.01>".slow(12)
// density / maturation control
// Lower values = reaches full density faster.
// Higher values = much longer bloom.
// Good starting points:
// densitySpan 48-72 -> roughly 7-10 minute recorded track
// densitySpan 256-512 -> long-form installation / hour-scale bloom
// walkerSpan can be slower than densitySpan if you want the ensemble to fill in
// without the motif changes accelerating as quickly.
let densitySpan = 56
let walkerSpan = 84
// macro energy arc: starts sparse, opens up, then stays more conversational
let energy = sine.range(0.35, 1.0).slow(densitySpan)
let openS = sine.range(0.6, 1.0).slow(densitySpan)
let openA = sine.range(0.55, 0.95).slow(densitySpan)
let openT = sine.range(0.5, 0.9).slow(densitySpan)
let openB = sine.range(0.75, 1.0).slow(densitySpan)
// blues landing / assertiveness helpers
let resolveS = "<e5 d5 g5 e5 d5 e5 g5 e5>"
let resolveT = "<e4 d4 g4 e4 d4 e4 g4 e4>"
let assertS = sine.range(0.85, 1.18).slow(densitySpan)
let assertA = sine.range(0.82, 1.12).slow(densitySpan)
let assertT = sine.range(0.8, 1.1).slow(densitySpan)
let assertB = sine.range(0.9, 1.12).slow(densitySpan)
let sopI = `
<
[e5@2 ~ ~ ~]
[g5@2 ~ ~ ~]
[d5@2 ~ e5@2 ~ ~]
[~ ~ g5@2 ~]
>
<
[e5@2 ~ g5@2 ~]
[~ ~ ~ bb5@2]
[d5@2 ~ ~ e5@2]
[~ e5@2 ~ ~]
>
`
let altoI = `
<
[~ g4@2 ~ ~]
[d5@2 ~ ~ ~]
[~ e5@2 ~ ~]
[~ d5@2 ~ ~]
>
<
[b4@2 ~ ~ d5@2]
[~ ~ g4@2 ~]
[~ e5@2 ~ ~]
[~ g4@2 ~ ~]
>
`
let tenorI = `
<
[e4@2 ~ ~ ~]
[~ d4@2 ~ ~]
[g4@2 ~ ~ ~]
[~ ~ d4@2 ~]
>
<
[e4@2 ~ ~ g4@2]
[~ d4@2 ~ ~]
[g4@2 ~ e4@2 ~]
[d4@2 ~ ~ ~]
>
`
let bassI = `
<
[e2@2 ~ ~ ~]
[d2@2 ~ ~ ~]
[e2@2 ~ ~ ~]
[b1@2 ~ d2@2 ~]
>
<
[e2@2 ~ ~ ~]
[d2@2 ~ e2@2 ~]
[g2@2 ~ e2@2 ~]
[~ b1@2 ~ ~]
>
`
let sopIV = `
<
[a5@2 ~ ~ ~]
[g5@2 a5@2 ~ ~]
>
<
[a5@2 ~ c6@2 ~]
[~ g5@2 ~ ~]
>
`
let altoIV = `
<
[~ a4@2 ~ ~]
[e5@2 ~ ~ ~]
>
<
[c5@2 ~ e5@2 ~]
[~ a4@2 ~ ~]
>
`
let tenorIV = `
<
[a4@2 ~ ~ ~]
[~ g4@2 ~ ~]
>
<
[c5@2 ~ a4@2 ~]
[~ ~ g4@2 ~]
>
`
let bassIV = `
<
[a2@2 ~ ~ ~]
[g2@2 ~ a2@2 ~]
>
<
[a2@2 ~ ~ e2@2]
[c3@2 ~ a2@2 ~]
>
`
let sopV = `
<
[b5@2 ~ ~ ~]
>
<
[d6@2 ~ b5@2 ~]
>
`
let altoV = `
<
[~ b4@2 ~ ~]
>
<
[fs5@2 ~ d5@2 ~]
>
`
let tenorV = `
<
[b4@2 ~ ~ ~]
>
<
[d5@2 ~ b4@2 ~]
>
`
let bassV = `
<
[b1@2 ~ ~ ~]
>
<
[fs2@2 ~ b1@2 ~]
>
`
let sopTurn = `
<
[d6@2 ~ bb5@2 ~]
[b5@2 ~ a5@2 ~]
[~ g5@2 ~ ~]
>
<
[d6@2 ~ ~ bb5@2]
[a5@2 ~ g5@2 ~]
[~ ~ g5@2 ~]
>
`
let altoTurn = `
<
[~ d5@2 ~ ~]
[bb4@2 ~ a4@2 ~]
[~ b4@2 ~ ~]
>
<
[d5@2 ~ g5@2 ~]
[a4@2 ~ bb4@2 ~]
[~ d5@2 ~ ~]
>
`
let tenorTurn = `
<
[d5@2 ~ b4@2 ~]
[a4@2 ~ g4@2 ~]
[e4@2 ~ ~ ~]
>
<
[b4@2 ~ d5@2 ~]
[g4@2 ~ a4@2 ~]
[~ e4@2 ~ ~]
>
`
let bassTurn = `
<
[a1@2 ~ e2@2 ~]
[b1@2 ~ a1@2 ~]
[e2@2 ~ ~ ~]
>
<
[a1@2 ~ ~ e2@2]
[b1@2 ~ d2@2 ~]
[e2@2 ~ ~ ~]
>
`
// weighted walker banks
// explicit selector patterns are more reliable here than choose(...).inhabit(...)
let walkS = "<a a a a b a c a a d>".slow(sine.range(7, 4).slow(walkerSpan))
let walkA = "<a a a b a c a d>".slow(sine.range(7, 4.5).slow(walkerSpan))
let walkT = "<a a a b a c a d>".slow(sine.range(8, 5).slow(walkerSpan))
let walkB = "<a a a a b a c a a d>".slow(sine.range(7, 4).slow(walkerSpan))
let soprano = walkS.inhabit({ a: sopI, b: sopIV, c: sopTurn, d: sopV })
let alto = walkA.inhabit({ a: altoI, b: altoIV, c: altoTurn, d: altoV })
let tenor = walkT.inhabit({ a: tenorI, b: tenorIV, c: tenorTurn, d: tenorV })
let bass = walkB.inhabit({ a: bassI, b: bassIV, c: bassTurn, d: bassV })
$: note(soprano).slow(8)
.late(swing)
.late(push)
.sometimesBy(0.12, x => x.rev())
.sometimesBy(0.04, x => x.slow(1.15))
.sometimesBy(0.18, x => x.degradeBy(0.22))
.sometimesBy(0.14, x => x.early(0.01))
// phrase landing bias: kept rare and slow to avoid exposed beep-like events
.sometimesBy(0.08, x => x.add(resolveS.sub(x)).slow(1.5))
.s("square")
.gain(sine.range(0.055, 0.07).slow(9).mul(conductor).mul(sopSpeak.add(openS)).mul(leadS.add(energy)).mul(assertS))
.release(sine.range(1.5, 3.5).slow(18))
.lpf(sine.range(700, 1000).slow(10))
.hpf(260)
.delay(0.18)
.delayfeedback(0.03)
.room(0.14)
.size(2.6)
.pan(sine.range(0.18, 0.34).slow(17))
$: note(alto).slow(9)
.late(swing.mul(0.8))
.sometimesBy(0.1, x => x.rev())
.sometimesBy(0.03, x => x.slow(1.12))
.sometimesBy(0.2, x => x.degradeBy(0.24))
.sometimesBy(0.12, x => x.early(0.008))
.sometimesBy(0.03, x => x.slow(1.25))
.s("square")
.gain(sine.range(0.06, 0.08).slow(12).mul(conductor).mul(altoSpeak.add(openA)).mul(leadA.add(energy)).mul(assertA))
.release(sine.range(2.0, 4.0).slow(20))
.lpf(sine.range(550, 800).slow(13))
.hpf(220)
.delay(0.18)
.delayfeedback(0.04)
.room(0.14)
.size(2.6)
.pan(sine.range(0.78, 0.62).slow(19))
$: note(tenor).slow(10)
.late(swing.mul(0.6))
.sometimesBy(0.08, x => x.rev())
.sometimesBy(0.22, x => x.degradeBy(0.26))
// phrase landing bias in the mid register, kept slow and rare
.sometimesBy(0.08, x => x.add(resolveT.sub(x)).slow(1.5))
.s("square")
.gain(sine.range(0.075, 0.1).slow(10).mul(conductor).mul(tenorSpeak.add(openT)).mul(leadT.add(energy)).mul(assertT))
.release(sine.range(2.5, 5.0).slow(22))
.lpf(sine.range(260, 420).slow(11))
.hpf(110)
.delay(0.08)
.delayfeedback(0.01)
.room(0.1)
.size(2.1)
.pan(sine.range(0.42, 0.58).slow(21))
$: note(bass).slow(8)
.late(swing.mul(0.3))
.sometimesBy(0.05, x => x.rev())
.sometimesBy(0.14, x => x.degradeBy(0.18))
.s("square")
.gain(sine.range(0.09, 0.12).slow(18).mul(conductor).mul(bassSpeak.add(openB)).mul(leadB.add(energy.mul(0.5))).mul(assertB))
.release(sine.range(3.0, 6.0).slow(24))
.lpf(sine.range(90, 170).slow(15))
.hpf(35)
.delay(0.14)
.delayfeedback(0)
.room(0.08)
.size(1.8)
.pan(0.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment