leach.studio

Static

noise · texture · experimental
↓ source · seed

about this piece

Three layers of filtered noise — low rumble, midrange hiss, high crackle — each on its own LFO. Occasional probability-gated thumps. No notes, no chords. Texture as composition.

seed
static-preview
by
stuart
year
2026
tags
noise · texture · experimental
permalink
/a/static/static-preview

source

import type { ElementaryPatch } from '@/lib/engine/types'

/**
 * Pure noise texture, no pitched material. Three layers of pink/white
 * noise routed through bandpass filters at different center frequencies,
 * each modulated by its own slow LFO. Plus occasional impulses (drum-like)
 * triggered by probability gates.
 *
 * Lo-fi tape hiss, distant rain, the inside of a shell. Texture for
 * its own sake.
 */
const noisePatch: ElementaryPatch = {
  id: 'static',
  title: 'Static',
  author: 'stuart',
  year: 2026,
  tags: ['noise', 'texture', 'experimental'],
  description:
    'Three layers of filtered noise — low rumble, midrange hiss, high crackle — each on its own LFO. Occasional probability-gated thumps. No notes, no chords. Texture as composition.',
  engine: 'elementary',

  audio: ({ el, rng, schedule, render }) => {
    const interval = 6

    schedule(interval, (tick) => {
      // Layer 1: low rumble — bandpass at 80Hz, slow LFO sweep
      const lfoLow = el.cycle(el.const({ key: 'lfoL', value: 0.07 }))
      const lowFc = el.add(80, el.mul(lfoLow, 30))
      const lowAmp = rng.range(0.05, 0.12, tick, 0)
      const low = el.mul(
        el.bandpass(lowFc, 1.5, el.pinknoise()),
        el.const({ key: 'lowAmp', value: lowAmp }),
      )

      // Layer 2: midrange wash — bandpass around 800Hz, faster LFO
      const lfoMid = el.cycle(el.const({ key: 'lfoM', value: 0.18 }))
      const midFc = el.add(900, el.mul(lfoMid, 400))
      const midAmp = rng.range(0.025, 0.06, tick, 1)
      const mid = el.mul(
        el.bandpass(midFc, 2, el.pinknoise()),
        el.const({ key: 'midAmp', value: midAmp }),
      )

      // Layer 3: high crackle — highpass white noise, very quiet
      const lfoHi = el.cycle(el.const({ key: 'lfoH', value: 0.31 }))
      const hiAmp = el.mul(
        el.add(0.5, el.mul(lfoHi, 0.5)),
        el.const({ key: 'hiAmpB', value: 0.012 }),
      )
      const hi = el.mul(
        el.highpass(el.const({ key: 'hiHpf', value: 4000 }), 0.8, el.noise()),
        hiAmp,
      )

      // Occasional thump — sub pulse, probability gate
      const thumpOn = rng.at(tick, 5) > 0.65
      const thumpGate = el.metro({
        name: 'thumpM',
        interval: interval * 1000,
      })
      const thumpEnv = el.adsr(0.001, 0.4, 0, 0, thumpGate)
      const thump = el.mul(
        el.cycle(el.const({ key: 'thumpF', value: 50 })),
        el.mul(
          thumpEnv,
          el.const({ key: 'thumpAmp', value: thumpOn ? 0.45 : 0 }),
        ),
      )

      // Mix → very gentle saturation, master gain
      const mix = el.add(low, mid, hi, thump)
      const out = el.mul(el.tanh(el.mul(mix, 0.6)), 0.22)
      render(out, out)
    })
  },

  visual: ({ ctx, rng, t, width, height, getRms, mouseX, mouseY, mouseActive }) => {
    // TV-static-like noise field. Very dense particle field, sparse
    // refresh. RMS pumps the brightness slightly.
    ctx.fillStyle = 'rgba(12, 12, 12, 0.4)' // strong veil — fast refresh
    ctx.fillRect(0, 0, width, height)

    const rms = getRms()
    const intensity = 0.15 + Math.min(0.4, rms * 4)
    const particleCount = 200 + Math.floor(rms * 400)
    const ticks = Math.floor(t * 20) // fast-moving particles

    for (let i = 0; i < particleCount; i++) {
      const x = rng.range(0, width, ticks, i)
      const y = rng.range(0, height, ticks, i + 50000)
      const a = intensity * (0.5 + rng.at(ticks, i + 100000) * 0.5)
      ctx.fillStyle = `rgba(232, 230, 225, ${a})`
      ctx.fillRect(x, y, 1.2, 1.2)
    }

    // Cursor reveals a clean circle
    if (mouseActive) {
      const cx = mouseX * width
      const cy = mouseY * height
      ctx.fillStyle = 'rgba(0, 0, 0, 0.55)'
      ctx.beginPath()
      ctx.arc(cx, cy, 50, 0, Math.PI * 2)
      ctx.fill()
      ctx.strokeStyle = 'rgba(232, 230, 225, 0.4)'
      ctx.lineWidth = 1
      ctx.stroke()
    }
  },
}

export default noisePatch
← leach.studio