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.
static-previewimport 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