@@ -2,87 +2,82 @@ import type { SynthLoop } from './types'
22import { loopTeardown } from './synthUtils'
33
44/**
5- * Builds a vortex tunnel sound. The core character is rushing air with
6- * resonant sweeps that create a sense of forward motion. Tonal elements
7- * sit underneath as a subtle drone.
8- * - 'normal': smooth rushing wind with warm resonance
9- * - 'error': harsher, more turbulent — same tunnel, gone wrong
5+ * Builds a vortex loop — a quiet, low-frequency rumble designed for
6+ * long listening sessions (30+ minutes during downloads/compiles).
7+ * - 'normal': gentle low rumble, barely-there presence
8+ * - 'error': same character but darker and slightly more prominent
109 */
10+ /** Master volume multiplier for the vortex loop — tweak this to dial in overall level. */
11+ const VORTEX_VOLUME = 1.0
12+
1113function buildVortexLoop ( ctx : AudioContext , dest : AudioNode , variant : 'normal' | 'error' ) : ( ) => void {
1214 const nodes : AudioNode [ ] = [ ]
1315 const gains : GainNode [ ] = [ ]
1416 const t = ctx . currentTime
1517 const isError = variant === 'error'
18+ const v = VORTEX_VOLUME
1619
17- // --- Layer 1: Rushing air (primary) ---
18- const wind = ctx . createBufferSource ( )
19- const windBuf = ctx . createBuffer ( 2 , ctx . sampleRate * 4 , ctx . sampleRate )
20+ // --- Layer 1: Noise rumble through a steep lowpass ---
21+ const rumble = ctx . createBufferSource ( )
22+ const rumbleBuf = ctx . createBuffer ( 2 , ctx . sampleRate * 4 , ctx . sampleRate )
2023 for ( let ch = 0 ; ch < 2 ; ch ++ ) {
21- const data = windBuf . getChannelData ( ch )
24+ const data = rumbleBuf . getChannelData ( ch )
2225 for ( let i = 0 ; i < data . length ; i ++ ) data [ i ] = Math . random ( ) * 2 - 1
2326 }
24- wind . buffer = windBuf
25- wind . loop = true
26-
27- const windBP = ctx . createBiquadFilter ( )
28- windBP . type = 'bandpass'
29- windBP . frequency . setValueAtTime ( isError ? 600 : 680 , t )
30- windBP . Q . setValueAtTime ( 0.8 , t )
31-
32- const windGain = ctx . createGain ( )
33- windGain . gain . setValueAtTime ( 0.14 , t )
34- wind . connect ( windBP ) . connect ( windGain ) . connect ( dest )
35- wind . start ( )
36- nodes . push ( wind , windBP , windGain )
37- gains . push ( windGain )
38-
39- // --- Layer 2: High whistle / tunnel resonance ---
40- const whistle = ctx . createBufferSource ( )
41- const whistleBuf = ctx . createBuffer ( 1 , ctx . sampleRate * 4 , ctx . sampleRate )
42- const whistleData = whistleBuf . getChannelData ( 0 )
43- for ( let i = 0 ; i < whistleData . length ; i ++ ) whistleData [ i ] = Math . random ( ) * 2 - 1
44- whistle . buffer = whistleBuf
45- whistle . loop = true
46-
47- const whistleBP = ctx . createBiquadFilter ( )
48- whistleBP . type = 'bandpass'
49- whistleBP . frequency . setValueAtTime ( isError ? 1500 : 1800 , t )
50- whistleBP . Q . setValueAtTime ( 2 , t )
51-
52- // Offset sweep so the two bands don't move in sync
53- const whistleLfo = ctx . createOscillator ( )
54- const whistleDepth = ctx . createGain ( )
55- whistleLfo . type = 'sine'
56- whistleLfo . frequency . setValueAtTime ( isError ? 0.073 : 0.06 , t )
57- whistleDepth . gain . setValueAtTime ( isError ? 700 : 600 , t )
58- whistleLfo . connect ( whistleDepth ) . connect ( whistleBP . frequency )
59- whistleLfo . start ( )
60-
61- const whistleGain = ctx . createGain ( )
62- whistleGain . gain . setValueAtTime ( isError ? 0.05 : 0.04 , t )
63- whistle . connect ( whistleBP ) . connect ( whistleGain ) . connect ( dest )
64- whistle . start ( )
65- nodes . push ( whistle , whistleBP , whistleLfo , whistleDepth , whistleGain )
66- gains . push ( whistleGain )
67-
68- // --- Layer 3: Subtle tonal undertone ---
69- const drone = ctx . createOscillator ( )
70- const droneGain = ctx . createGain ( )
71- drone . type = 'sine'
72- drone . frequency . setValueAtTime ( isError ? 50 : 60 , t )
73- droneGain . gain . setValueAtTime ( isError ? 0.07 : 0.04 , t )
74- drone . connect ( droneGain ) . connect ( dest )
75- drone . start ( )
76- nodes . push ( drone , droneGain )
77- gains . push ( droneGain )
27+ rumble . buffer = rumbleBuf
28+ rumble . loop = true
29+
30+ const rumbleLP = ctx . createBiquadFilter ( )
31+ rumbleLP . type = 'lowpass'
32+ rumbleLP . frequency . setValueAtTime ( isError ? 140 : 160 , t )
33+ rumbleLP . Q . setValueAtTime ( 2.5 , t ) // resonant peak adds body
34+
35+ const rumbleGain = ctx . createGain ( )
36+ rumbleGain . gain . setValueAtTime ( 0.35 * v , t )
37+ rumble . connect ( rumbleLP ) . connect ( rumbleGain ) . connect ( dest )
38+ rumble . start ( )
39+ nodes . push ( rumble , rumbleLP , rumbleGain )
40+ gains . push ( rumbleGain )
41+
42+ // --- Layer 2: Sub-bass weight ---
43+ const sub = ctx . createOscillator ( )
44+ const subGain = ctx . createGain ( )
45+ sub . type = 'triangle'
46+ sub . frequency . setValueAtTime ( isError ? 35 : 42 , t )
47+ subGain . gain . setValueAtTime ( 0.12 * v , t )
48+ sub . connect ( subGain ) . connect ( dest )
49+ sub . start ( )
50+ nodes . push ( sub , subGain )
51+ gains . push ( subGain )
52+
53+ // --- Layer 3: Slow filter cutoff drift for organic movement ---
54+ const driftLfo = ctx . createOscillator ( )
55+ const driftDepth = ctx . createGain ( )
56+ driftLfo . type = 'sine'
57+ driftLfo . frequency . setValueAtTime ( 0.035 , t )
58+ driftDepth . gain . setValueAtTime ( 40 , t ) // ±40 Hz cutoff drift
59+ driftLfo . connect ( driftDepth ) . connect ( rumbleLP . frequency )
60+ driftLfo . start ( )
61+ nodes . push ( driftLfo , driftDepth )
62+
63+ // --- Layer 4: Very slow amplitude breathing ---
64+ const breathLfo = ctx . createOscillator ( )
65+ const breathDepth = ctx . createGain ( )
66+ breathLfo . type = 'sine'
67+ breathLfo . frequency . setValueAtTime ( 0.025 , t )
68+ breathDepth . gain . setValueAtTime ( 0.04 * v , t )
69+ breathLfo . connect ( breathDepth ) . connect ( rumbleGain . gain )
70+ breathLfo . connect ( breathDepth ) . connect ( subGain . gain )
71+ breathLfo . start ( )
72+ nodes . push ( breathLfo , breathDepth )
7873
7974 return loopTeardown ( gains , nodes , 0.5 )
8075}
8176
82- /** Rushing-through-a-tunnel vortex loop . */
77+ /** Quiet low rumble for the loading vortex . */
8378export const synthVortexLoop : SynthLoop = ( ctx , dest ) => buildVortexLoop ( ctx , dest , 'normal' )
8479
85- /** Harsher, turbulent variant of the vortex for error states. */
80+ /** Slightly darker/louder variant for error states. */
8681export const synthVortexError : SynthLoop = ( ctx , dest ) => buildVortexLoop ( ctx , dest , 'error' )
8782
8883/** Warm energy hum for portal hover — like standing near something powerful. */
0 commit comments