A single chord fed into a two-second delay with 80% feedback. Plays one chord per minute. Made for empty rooms — turn it on, walk away, come back later, the chord's still hanging.
cathedral-previewimport type { ElementaryPatch } from '@/lib/engine/types'
/**
* Monolithic reverb pad. A single slowly-shifting chord fed into a long
* feedback delay (~2s) with high regen, then lowpass-smoothed. The
* chord changes about once a minute; everything else just decays into
* itself. Made for empty rooms.
*/
const cathedral: ElementaryPatch = {
id: 'cathedral',
title: 'Cathedral',
author: 'stuart',
year: 2026,
tags: ['ambient', 'drone', 'reverb'],
description:
"A single chord fed into a two-second delay with 80% feedback. Plays one chord per minute. Made for empty rooms — turn it on, walk away, come back later, the chord's still hanging.",
engine: 'elementary',
audio: ({ el, rng, schedule, render }) => {
// Wide voicings with extended tones — built for sustained ringing
const chords = [
[110, 164.81, 220, 277.18, 329.63, 415.3], // A min add9 wide (A E A C# E G#)
[98, 146.83, 196, 246.94, 293.66, 369.99], // G maj7 wide
[87.31, 130.81, 174.61, 220, 261.63, 329.63], // F maj9 wide
[82.41, 123.47, 164.81, 196, 246.94, 311.13], // E min7 wide
]
const interval = 60 // chord change every minute
schedule(interval, (tick) => {
const chord = rng.pick(chords, tick, 0)
const cutoffTarget = rng.range(800, 1500, tick, 1)
// Pad voices — sine + tiny detuned sine
const voices = chord.map((f, i) => {
const a = el.cycle(el.const({ key: `c-${i}`, value: f }))
const b = el.cycle(el.const({ key: `cd-${i}`, value: f * 1.003 }))
return el.mul(el.add(a, el.mul(b, 0.7)), 0.06)
})
const pad = el.add(...voices)
// Massive feedback delay — 2 second delay, 0.8 feedback. The room.
const longDelay = el.delay(
{ key: 'cathDelay', size: 192000 },
el.const({ key: 'dlen', value: 96000 }), // 2s @ 48k
el.const({ key: 'dfb', value: 0.8 }),
pad,
)
// Mix dry pad with delayed signal
const wetDry = el.add(el.mul(pad, 0.4), el.mul(longDelay, 0.6))
// Smooth the cutoff so it morphs slowly
const cutSm = el.smooth(
el.tau2pole(8),
el.const({ key: 'cut', value: cutoffTarget }),
)
const filtered = el.lowpass(cutSm, 0.5, wetDry)
// Master smoothing — softens any transient
const mastered = el.smooth(el.tau2pole(0.5), filtered)
const out = el.mul(el.tanh(el.mul(mastered, 0.5)), 0.18)
render(out, out)
})
},
visual: ({ ctx, t, width, height, getRms, mouseX, mouseY, mouseActive }) => {
// Vertical light beams — like cathedral windows. Audio swells the beams.
ctx.fillStyle = 'rgba(12, 12, 12, 0.06)'
ctx.fillRect(0, 0, width, height)
const beamCount = 8
const rms = getRms()
const intensity = 0.3 + Math.min(0.5, rms * 4)
for (let i = 0; i < beamCount; i++) {
const x = (width / (beamCount + 1)) * (i + 1)
const breath = 0.5 + 0.5 * Math.sin(t * 0.08 + i * 0.7)
const beamW = 60 + breath * 40
const grad = ctx.createLinearGradient(x - beamW / 2, 0, x + beamW / 2, 0)
const a = intensity * (0.4 + breath * 0.5)
grad.addColorStop(0, 'rgba(220, 200, 240, 0)')
grad.addColorStop(0.5, `rgba(220, 200, 240, ${a * 0.18})`)
grad.addColorStop(1, 'rgba(220, 200, 240, 0)')
ctx.fillStyle = grad
ctx.fillRect(x - beamW / 2, 0, beamW, height)
}
// Soft floor of warm light at the bottom
const floorGrad = ctx.createLinearGradient(0, height * 0.6, 0, height)
floorGrad.addColorStop(0, 'rgba(255, 220, 180, 0)')
floorGrad.addColorStop(1, `rgba(255, 220, 180, ${0.04 + intensity * 0.06})`)
ctx.fillStyle = floorGrad
ctx.fillRect(0, height * 0.6, width, height * 0.4)
if (mouseActive) {
const cx = mouseX * width
const cy = mouseY * height
const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 100)
grad.addColorStop(0, 'rgba(255, 230, 190, 0.18)')
grad.addColorStop(1, 'rgba(255, 230, 190, 0)')
ctx.fillStyle = grad
ctx.fillRect(cx - 100, cy - 100, 200, 200)
}
},
}
export default cathedral