diff --git a/audio_gaps.md b/audio_gaps.md
index 197be18..d0b99f5 100644
--- a/audio_gaps.md
+++ b/audio_gaps.md
@@ -37,31 +37,31 @@ The table below catalogs all **54 discrete sound gaps** identified across the 10
| 18 | **True RF Radio Static Burst** | Outlaw & Frontier | The Betty, The Marauder | Rugged CB-radio squelch break and atmospheric RF hash between short-range ship comms. | `ExpandedSciFiAudioSynth.synthesizeRadioStatic()`.
• **Soundboard:** Outlaw cockpit (`btn-radio-static`). Bandpass-filtered pink/white noise burst (1400 Hz, Q 1.1, hard-knee 180 ms gate). | High | **Low** | **Implemented** |
| 19 | **TARDIS door open/close** | Whoniverse | All TARDIS Console Rooms | Friction creak of heavy wooden police box exterior doors followed by an iron mortise lock click. | `WhoniverseAudioSynth.synthesizeTardisDoor()`.
• **Soundboard:** TARDIS controls (`btn-tardis-door`). Bandpass friction noise (wood creak, 260 to 540 Hz) layered over dual metallic mortise latch clicks (1100 Hz / 750 Hz). | High | **Low–Medium** | **Implemented** |
| 20 | **Magnetic boot ("mag-boot") latch** | Space Stations | Ceres, Tycho (*The Expanse*) | Heavy physical boot heel impact on iron decking immediately followed by an electromagnetic clamping buzz. | `ExpandedSciFiAudioSynth.synthesizeMagBootLatch()`.
• **Soundboard:** Station ops (`btn-mag-boot`). 80 Hz impact click (30 ms) followed instantly by a 120 Hz square-wave clamping buzz (160 ms decay). | High | **Low–Medium** | **Implemented** |
-| 21 | **Cardassian door** | Star Trek | DS9 (Terok Nor) | Oppressive, heavy Cardassian pneumatic bulkheads grinding open along metal gears. | Resonant bandpass filter sweep across brown/pink noise (120 Hz to 650 Hz) layered with a scraping harmonic metallic saw undertone. | High | **Medium** | Backlog |
-| 22 | **Turbolift** | Star Trek | TNG, VOY | High-speed magnetic turbolift car passing through deck shafts with a rising whoosh and decelerating settle. | Bandpass-filtered white noise pitch sweep (180 Hz to 450 Hz and back) with resonant boost and soft stereo panning drift. | Medium | **Medium** | Backlog |
-| 23 | **Replicator** | Star Trek | TNG, VOY | Molecular dematerialization / materialization shimmer when dispensing food or tools. | High-frequency white noise shimmer (passband 2500–8000 Hz) modulated by a fast sine LFO (30 Hz) with soft ramp decay. | Low | **Medium** | Backlog |
-| 24 | **Scanner/monitor activation** | Whoniverse | All TARDIS Console Rooms | Distinctive cathode-ray / crystal scanner screen powering on above the central console. | Rapid upward electronic pitch sweep (sine 200 Hz to 1600 Hz over 120 ms) terminating in a steady 15.6 kHz CRT flyback whine. | Medium | **Medium** | Backlog |
-| 25 | **Console "type" input telemetry** | Whoniverse | All TARDIS Console Rooms | Rhythmic, mechanical clatter of physical toggle switches, spring-loaded buttons, and tumbler relays. | Specialized telemetry routine generating clustered wooden/plastic clicks and solenoid snaps rather than tonal beeps. | High | **Medium** | Backlog |
-| 26 | **"Singing" resonance element** | Bioships | Vorlon Cruiser (*Babylon 5*) | Ethereal, telepathic crystal harmonics echoing through living Vorlon organic hulls. | Dual detuned sine wave oscillators (528 Hz and 532 Hz) running through a narrow bandpass filter with slow undulating tremolo (0.2 Hz). | High | **Medium** | Backlog |
-| 27 | **Neural-bond swell** | Bioships | Moya, Talyn, Wraith Hive | Symbiotic neural link between pilot and bioship swelling during heightened emotional or flight states. | Deep sub-bass triangle wave (55 Hz) sweeping into a resonant vowel-formant filter (350–850 Hz) over 2.5 seconds. | Medium | **Medium** | Backlog |
-| 28 | **Hull self-repair / regeneration texture** | Bioships | Wraith Hive Ship | Microscopic organic tissue knit and chitinous shell regrowth during ship recovery phases. | Granular amplitude-modulated pink noise (8–24 Hz modulation rate) creating a quiet, wet, scabbing/cracking textural bed. | Low | **Medium** | Backlog |
-| 29 | **Belter "jury-rigged" telemetry era** | Space Stations | Ceres, Tycho (*The Expanse*) | Rattle of loose relays, worn copper contactors, and erratic voltage drops across secondhand station hardware. | Telemetry synthesis routine combining noisy square pulses, contact-bounce jitter, and unstable clock drift. | High | **Medium** | Backlog |
-| 30 | **True Hull Strain Moan** | Deep Space | Event Horizon, Ark One, Icarus II | Massive structural groan and creak of stressed titanium and composite bulkheads flexing under gravitational shear. | Resonant bandpass filter sweeping slowly (40 Hz to 220 Hz, high Q) over shaped low-frequency pink noise with asymmetric decay. | High | **Medium** | Backlog |
-| 31 | **Icarus I distress beacon** | Deep Space | Icarus II (*Sunshine*) | Eerie, hypnotic modal electronic arpeggio echoing across the void from the ghost ship. | Multi-stage sine arpeggiator playing a haunting 4-note modal sequence through long feedback delay and soft lowpass dampening. | High | **Medium** | Backlog |
-| 32 | **Extradimensional / psychic static whisper** | Deep Space | Event Horizon, Nightflyer | Menacing psychoacoustic hallucination burst simulating whispers bleeding through dimensional rifts. | Multi-formant filtered white noise modulated by randomized micro-envelopes, evoking whispered vowels in the 800–2400 Hz range. | Medium | **Medium** | Backlog |
-| 33 | **Beryllium Sphere Resonant Thrum** | Comedy & Adventure | NSEA Protector (*Galaxy Quest*) | Deep, glassy, crystalline reactor hum vibrating with pure alien harmonic energy. | Dual pure sine waves (65 Hz + 195 Hz 3rd harmonic) with a slow beating detune and subtle ringing resonant comb filter. | Medium | **Medium** | Backlog |
-| 34 | **Omega-13 Temporal Capacitor Whine** | Comedy & Adventure | NSEA Protector (*Galaxy Quest*) | Tremendous temporal capacitor power buildup rising into a deafening dimensional snap. | Deep 40 Hz sub-bass swell exponentially ramping in frequency to 3200 Hz over 3 seconds, culminating in a wide white-noise discharge pop. | High | **Medium** | Backlog |
-| 35 | **Repulsorlift Engine Drone** | Outlaw & Frontier | The Marauder (*Star Wars*) | Quintessential atmospheric anti-gravity vehicle wash and repulsor grid drone. | Dual detuned sine wave carrier (68 Hz / 72 Hz) driving an asymmetric overdrive wave-shaper with lowpass filtering at 280 Hz. | Medium | **Medium** | Backlog |
-| 36 | **Converted Marine Trawler Engine Chug** | Outlaw & Frontier | Bebop (*Cowboy Bebop*) | Rhythmic, heavy diesel-like marine piston strokes echoing through the Bebop's converted hull. | Periodic low-frequency square pulse (4–8 Hz repetition) passing through an 80 Hz resonant lowpass filter with mechanical piston wheeze. | Medium | **Medium** | Backlog |
-| 37 | **Centrifugal Habitat Carousel Motor Groan** | Military Space | EAS Agamemnon (*Babylon 5*) | Low-frequency rotational strain and mechanical bearing hum of the 1 rpm rotating crew carousel. | 16 Hz fundamental sine drone modulated in amplitude by an ultra-slow LFO (0.016 Hz / 60-second full rotation cycle). | Medium | **Medium** | Backlog |
-| 38 | **Slipstream Drive Transition Surge** | Military Space | Andromeda Ascendant | Ascending harmonic pitch sweep followed by crystalline string-like resonance upon entering slipstream. | Dual sweeping bandpass filters (200 Hz to 2400 Hz) with escalating resonance Q factor driving a comb-filter shimmer tail. | Medium | **Medium** | Backlog |
-| 39 | **Viper Pilot Oxygen Regulator Demand Valve** | Military Space | Colonial Viper Mk II (*BSG*) | Rhythmic breathing hiss and mechanical click of cockpit life-support demand regulator during high-G combat. | Pulsed highpass white-noise envelope (1200–4500 Hz, 1.2s duration) prefixed by a sharp 15 ms mechanical diaphragm click. | Medium | **Medium** | Backlog |
-| 40 | **Condensation Pipe Drip & Expansion Ticks** | Industrial Space | USCSS Nostromo, Serenity | Water droplets hitting grated industrial deck plates and irregular metallic pings from cooling steam pipes. | High-Q sine pings (1400–2600 Hz, 10–25 ms decay) triggered on a randomized Poisson probability clock with varied amplitudes. | Medium | **Medium** | Backlog |
-| 41 | **High-Pressure Continuous Steam / Boiler Venting** | Industrial Space | Serenity, Starbug | Sustained high-pressure thermal exhaust venting from leaky boiler seals and cooling radiators. | Bandpass-filtered white noise (800–2800 Hz) with continuous slow amplitude swell and random micro-flutter modulation. | Medium | **Medium** | Backlog |
-| 42 | **Crash Couch Hydraulic Gimbal / G-Load Strain** | Industrial Space | Rocinante (*The Expanse*) | High-torque hydraulic servos straining and whining as gimbaled couches pivot under 6G burn maneuvers. | Frequency-modulated triangle wave (140 Hz carrier, 35 Hz modulator) with lowpass damping tracking ship acceleration changes. | Medium | **Medium** | Backlog |
-| 43 | **HAL 9000 Breathing / Vocal Carrier Ambient Loop** | Retro Future | Discovery One (*2001*) | Chillingly calm, measured human respiration echoing inside Dave Bowman's helmet and HAL's logic core. | Rhythmic bandpass pink noise (450–1100 Hz) modulated with a slow 3.5-second inhalation/exhalation envelope cycle. | Medium | **Medium** | Backlog |
-| 44 | **Death Blossom Energy Surge Capacitor Ramp** | Retro Future | Gunstar (*The Last Starfighter*) | Rapid, escalating multi-oscillator power buildup prior to deploying the 360-degree weapons barrage. | Cascaded sawtooth oscillators performing an exponential frequency ramp from 200 Hz to 8000 Hz over 2.5 seconds with rising overdrive. | Medium | **Medium** | Backlog |
-| 45 | **Victorian Steam Piston Chug & Iron Camshaft Clatter** | Retro Future | USS Cygnus (*The Black Hole*) | Massive reciprocating iron steam pistons and heavy camshaft linkages turning the Cygnus's cathedral engines. | Lowpass-filtered noise burst synchronized with a heavy 40 Hz triangle body thump and metallic slapback reflection. | Medium | **Medium** | Backlog |
+| 21 | **Cardassian door** | Star Trek | DS9 (Terok Nor) | Oppressive, heavy Cardassian pneumatic bulkheads grinding open along metal gears. | `ExpandedSciFiAudioSynth.synthesizeCardassianDoor()`.
• **Soundboard:** Starfleet keypad (`btn-cardassian-door`). Resonant brown-noise bandpass sweep (120→650 Hz, Q 3.5) layered with a scraping 110→70 Hz saw undertone. | High | **Medium** | **Implemented** |
+| 22 | **Turbolift** | Star Trek | TNG, VOY | High-speed magnetic turbolift car passing through deck shafts with a rising whoosh and decelerating settle. | `ExpandedSciFiAudioSynth.synthesizeTurboliftWhoosh()`.
• **Soundboard:** Starfleet keypad (`btn-turbolift`). White-noise bandpass sweep (180→450→180 Hz, Q 2.2) with −0.45→+0.45 stereo pan drift. | Medium | **Medium** | **Implemented** |
+| 23 | **Replicator** | Star Trek | TNG, VOY | Molecular dematerialization / materialization shimmer when dispensing food or tools. | `ExpandedSciFiAudioSynth.synthesizeReplicatorShimmer()`.
• **Soundboard:** Starfleet keypad (`btn-replicator`). Dual parallel bands (3.2 kHz / 6.4 kHz) of white noise with 30 Hz sine AM and 2 s ramp decay. | Low | **Medium** | **Implemented** |
+| 24 | **Scanner/monitor activation** | Whoniverse | All TARDIS Console Rooms | Distinctive cathode-ray / crystal scanner screen powering on above the central console. | `WhoniverseAudioSynth.synthesizeScannerActivate()`.
• **Soundboard:** TARDIS controls (`btn-scanner-activate`). Sine sweep (200→1600 Hz / 120 ms) terminating in a 10.6 kHz CRT flyback partial (15.6 kHz spec compromised: near-Nyquist/ear-fatiguing). | Medium | **Medium** | **Implemented** |
+| 25 | **Console "type" input telemetry** | Whoniverse | All TARDIS Console Rooms | Rhythmic, mechanical clatter of physical toggle switches, spring-loaded buttons, and tumbler relays. | `WhoniverseAudioSynth.synthesizeTypeClatter()` + `startConsoleTypeTelemetry()`.
• **Soundboard:** TARDIS controls (`btn-type-clatter`).
• **Ambient Loop:** Random-window clatter clusters (2.8–6.5 s) on every `tardis-*` console room. Delivered as a standalone routine; `TelemetrySynth` `era: 'who'` routing remains Phase 3. | High | **Medium** | **Implemented** |
+| 26 | **"Singing" resonance element** | Bioships | Vorlon Cruiser (*Babylon 5*) | Ethereal, telepathic crystal harmonics echoing through living Vorlon organic hulls. | `ExpandedSciFiAudioSynth.synthesizeVorlonSingingSwell()` + `startVorlonSong()`.
• **Soundboard:** Bioship keypad (`btn-vorlon-song`).
• **Ambient Loop:** 8.5 s swell phrases (11–18 s cadence) while `bio-vorlon` is engaged. Dual detuned sines (528/532 Hz) through narrow bandpass (Q 22) with 0.2 Hz tremolo. | High | **Medium** | **Implemented** |
+| 27 | **Neural-bond swell** | Bioships | Moya, Talyn, Wraith Hive | Symbiotic neural link between pilot and bioship swelling during heightened emotional or flight states. | `ExpandedSciFiAudioSynth.synthesizeNeuralBondSwell()`.
• **Soundboard:** Bioship keypad (`btn-neural-bond`).
• **Scripted Event:** Bioship universe event `btn-neural-pulse` ("NEURAL PULSE") now fires this swell. Deep 55 Hz triangle sweeping through a vowel-formant bandpass (350→850 Hz, Q 4.5) over 2.5 s. | Medium | **Medium** | **Implemented** |
+| 28 | **Hull self-repair / regeneration texture** | Bioships | Wraith Hive Ship | Microscopic organic tissue knit and chitinous shell regrowth during ship recovery phases. | `ExpandedSciFiAudioSynth.synthesizeHullRegenBurst()` + `startHullRegen()`.
• **Soundboard:** Bioship keypad (`btn-hull-regen`).
• **Ambient Loop:** Regeneration bursts (6–13 s cadence) while `bio-wraith` is engaged. Granular square-LFO AM (9–17 Hz) over bandpass pink noise plus wet chitin knit pops. | Low | **Medium** | **Implemented** |
+| 29 | **Belter "jury-rigged" telemetry era** | Space Stations | Ceres, Tycho (*The Expanse*) | Rattle of loose relays, worn copper contactors, and erratic voltage drops across secondhand station hardware. | `ExpandedSciFiAudioSynth.synthesizeBelterTelemetryJitter()` + `startBelterTelemetry()`.
• **Soundboard:** Station ops (`btn-belter-telemetry`, full-width).
• **Ambient Loop:** Jittered clusters (1.8–4.5 s cadence) while `sta-ceres`/`sta-tycho` are engaged. Delivered as a standalone routine; `TelemetrySynth` `era: 'belter'` routing remains Phase 3. | High | **Medium** | **Implemented** |
+| 30 | **True Hull Strain Moan** | Deep Space | Event Horizon, Ark One, Icarus II | Massive structural groan and creak of stressed titanium and composite bulkheads flexing under gravitational shear. | `ExpandedSciFiAudioSynth.synthesizeHullStrainMoan()` + `startHullStrainMoans()`.
• **Soundboard:** Void console (`btn-hull-strain`).
• **Ambient Loop:** Sporadic moans (16–40 s cadence) on `deep-event-horizon`/`deep-icarus-ii`. Brown-noise bandpass sweep (48→210 Hz, Q 10) with asymmetric attack/decay. | High | **Medium** | **Implemented** |
+| 31 | **Icarus I distress beacon** | Deep Space | Icarus II (*Sunshine*) | Eerie, hypnotic modal electronic arpeggio echoing across the void from the ghost ship. | `ExpandedSciFiAudioSynth.synthesizeIcarusBeacon()` + `startIcarusBeacon()`.
• **Soundboard:** Void console (`btn-icarus-beacon`).
• **Ambient Loop:** Modal arpeggio (22–40 s cadence) while `deep-icarus-ii` is engaged. 4-note A-minor modal sine sequence through a 0.45 s feedback delay with 2.2 kHz dampening. | High | **Medium** | **Implemented** |
+| 32 | **Extradimensional / psychic static whisper** | Deep Space | Event Horizon, Nightflyer | Menacing psychoacoustic hallucination burst simulating whispers bleeding through dimensional rifts. | `ExpandedSciFiAudioSynth.synthesizeVoidWhisper()` + `startVoidWhispers()`.
• **Soundboard:** Void console (`btn-void-whisper`).
• **Ambient Loop:** Whisper bursts (15–32 s cadence) on `deep-event-horizon`/`deep-nightflyer`. Three wobbling vowel-formants (700/1250/2200 Hz) with randomized syllable micro-envelopes. | Medium | **Medium** | **Implemented** |
+| 33 | **Beryllium Sphere Resonant Thrum** | Comedy & Adventure | NSEA Protector (*Galaxy Quest*) | Deep, glassy, crystalline reactor hum vibrating with pure alien harmonic energy. | `ExpandedSciFiAudioSynth.synthesizeBerylliumThrumSwell()` + `startBerylliumThrum()`.
• **Soundboard:** Cheerful keypad (`btn-beryllium-thrum`).
• **Ambient Loop:** 10 s thrum swell phrases (9–16 s cadence) while `com-protector` is engaged. Pure sines (65 + 65.3 Hz beating + 195 Hz third) ringing through a 5.1 ms comb reflection. | Medium | **Medium** | **Implemented** |
+| 34 | **Omega-13 Temporal Capacitor Whine** | Comedy & Adventure | NSEA Protector (*Galaxy Quest*) | Tremendous temporal capacitor power buildup rising into a deafening dimensional snap. | `ExpandedSciFiAudioSynth.synthesizeOmega13Whine()`.
• **Soundboard:** Cheerful keypad (`btn-omega-13`). 40 Hz sub-bass exponentially ramping to 3200 Hz over 3 s, culminating in a wide highpass white-noise discharge pop. | High | **Medium** | **Implemented** |
+| 35 | **Repulsorlift Engine Drone** | Outlaw & Frontier | The Marauder (*Star Wars*) | Quintessential atmospheric anti-gravity vehicle wash and repulsor grid drone. | `ExpandedSciFiAudioSynth.synthesizeRepulsorliftDrone()` + `startRepulsorliftDrone()`.
• **Soundboard:** Outlaw cockpit (`btn-repulsor-drone`).
• **Ambient Loop:** 10 s wash phrases (13 s cadence) while `out-marauder` is engaged. Dual detuned sines (68/72 Hz) through a tanh waveshaper (2x oversampled) and 280 Hz lowpass. | Medium | **Medium** | **Implemented** |
+| 36 | **Converted Marine Trawler Engine Chug** | Outlaw & Frontier | Bebop (*Cowboy Bebop*) | Rhythmic, heavy diesel-like marine piston strokes echoing through the Bebop's converted hull. | `ExpandedSciFiAudioSynth.synthesizeBebopChugStroke()` + `startBebopChug()`.
• **Soundboard:** Outlaw cockpit (`btn-bebop-chug`).
• **Ambient Loop:** 2–4 stroke clusters (2.2–4.2 s cadence) while `out-bebop` is engaged. Square pulses (45–70 Hz) through an 80 Hz resonant lowpass with piston wheeze. | Medium | **Medium** | **Implemented** |
+| 37 | **Centrifugal Habitat Carousel Motor Groan** | Military Space | EAS Agamemnon (*Babylon 5*) | Low-frequency rotational strain and mechanical bearing hum of the 1 rpm rotating crew carousel. | `ExpandedSciFiAudioSynth.synthesizeCarouselGroanCycle()` + `startCarouselGroan()`.
• **Soundboard:** Military keypad (`btn-carousel-groan`).
• **Ambient Loop:** One 58 s rotation pass every 60 s while `mil-agamemnon` is engaged. 16 Hz rotational beating pair + 48/96 Hz bearing harmonics, strain-swell LFO at exactly 1/60 Hz. | Medium | **Medium** | **Implemented** |
+| 38 | **Slipstream Drive Transition Surge** | Military Space | Andromeda Ascendant | Ascending harmonic pitch sweep followed by crystalline string-like resonance upon entering slipstream. | `ExpandedSciFiAudioSynth.synthesizeSlipstreamSurge()`.
• **Soundboard:** Military keypad (`btn-slipstream`). Dual staggered saw surges (200→2400 Hz) through bandpass filters with Q escalation (2→10) driving a 120 ms comb shimmer tail. | Medium | **Medium** | **Implemented** |
+| 39 | **Viper Pilot Oxygen Regulator Demand Valve** | Military Space | Colonial Viper Mk II (*BSG*) | Rhythmic breathing hiss and mechanical click of cockpit life-support demand regulator during high-G combat. | `ExpandedSciFiAudioSynth.synthesizeOxygenRegulatorCycle()` + `startOxygenRegulator()`.
• **Soundboard:** Military keypad (`btn-oxygen-reg`).
• **Ambient Loop:** Breath cycles (3.4–5.2 s cadence) while `mil-viper` is engaged. 15 ms diaphragm click prefixed to highpass band (1.2–4.5 kHz) white-noise breath. | Medium | **Medium** | **Implemented** |
+| 40 | **Condensation Pipe Drip & Expansion Ticks** | Industrial Space | USCSS Nostromo, Serenity | Water droplets hitting grated industrial deck plates and irregular metallic pings from cooling steam pipes. | `ExpandedSciFiAudioSynth.synthesizePipeDrip()` + `startPipeDrips()`.
• **Soundboard:** Industrial keypad (`btn-pipe-drips`).
• **Ambient Loop:** Randomized drips (0.5–3.5 s cadence) while `ind-nostromo` is engaged. High-Q sine pings (1.4–2.6 kHz) with randomized 10–25 ms exponential decays and amplitudes. | Medium | **Medium** | **Implemented** |
+| 41 | **High-Pressure Continuous Steam / Boiler Venting** | Industrial Space | Serenity, Starbug | Sustained high-pressure thermal exhaust venting from leaky boiler seals and cooling radiators. | `ExpandedSciFiAudioSynth.synthesizeSteamVentSwell()` + `startSteamVent()`.
• **Soundboard:** Industrial keypad (`btn-steam-vent`).
• **Ambient Loop:** 9 s vent passes (6–10 s cadence) on `ind-serenity`/`ind-starbug`. Looping white noise through a 1.5 kHz bandpass (Q 0.7) with 6–9 Hz boiler micro-flutter. | Medium | **Medium** | **Implemented** |
+| 42 | **Crash Couch Hydraulic Gimbal / G-Load Strain** | Industrial Space | Rocinante (*The Expanse*) | High-torque hydraulic servos straining and whining as gimbaled couches pivot under 6G burn maneuvers. | `ExpandedSciFiAudioSynth.synthesizeCrashCouchGimbal()`.
• **Soundboard:** Industrial keypad (`btn-crash-couch`). Dual-layer FM triangle (140 Hz carrier / 35 Hz modulator, then 152/38 Hz) with lowpass damping tracking the strain swell. | Medium | **Medium** | **Implemented** |
+| 43 | **HAL 9000 Breathing / Vocal Carrier Ambient Loop** | Retro Future | Discovery One (*2001*) | Chillingly calm, measured human respiration echoing inside Dave Bowman's helmet and HAL's logic core. | `ExpandedSciFiAudioSynth.synthesizeHalBreathCycle()` + `startHalBreathing()`.
• **Soundboard:** Retro keypad (`btn-hal-breath`).
• **Ambient Loop:** Continuous 3.5 s in/out breaths (1.3 s inhale / 2.2 s exhale) while `ret-discovery-one` is engaged. Looping pink noise bandpass (700 Hz center, Q 1.2). | Medium | **Medium** | **Implemented** |
+| 44 | **Death Blossom Energy Surge Capacitor Ramp** | Retro Future | Gunstar (*The Last Starfighter*) | Rapid, escalating multi-oscillator power buildup prior to deploying the 360-degree weapons barrage. | `ExpandedSciFiAudioSynth.synthesizeDeathBlossomSurge()`.
• **Soundboard:** Retro keypad (`btn-death-blossom`). Three cascaded saws (0/150/300 ms offsets) ramping 200→8000 Hz over 2.5 s through rising highpass, cut off abruptly at full bloom. | Medium | **Medium** | **Implemented** |
+| 45 | **Victorian Steam Piston Chug & Iron Camshaft Clatter** | Retro Future | USS Cygnus (*The Black Hole*) | Massive reciprocating iron steam pistons and heavy camshaft linkages turning the Cygnus's cathedral engines. | `ExpandedSciFiAudioSynth.synthesizeCygnusPistonChug()` + `startCygnusChug()`.
• **Soundboard:** Retro keypad (`btn-cygnus-chug`).
• **Ambient Loop:** 2–3 stroke clusters (1.8–3.2 s cadence) while `ret-cygnus` is engaged. 40 Hz triangle thumps with lowpass-filtered steam puffs and a 0.35 s metallic slapback. | Medium | **Medium** | **Implemented** |
| 46 | **Zocalo commerce-plaza chatter** | Space Stations | Babylon 5 | Bustling multi-species concourse background bed with indistinct alien murmurs and footsteps. | Multi-voice filtered pink noise bed shaped by 4 randomized parallel formant filters (300–3000 Hz) simulating crowd presence. | Low | **Medium–High** | Backlog |
| 47 | **Bespoke bioship telemetry era** | Bioships | Moya, Lexx, Species 8472, Wraith | Complete replacement of inorganic Starfleet chirps with organic clicks, wet pulses, and membrane flutters. | New telemetry engine era (`bioship`): procedural scheduling of bio-acoustic primitives (spore pops, chitin snaps, vascular beats). | High | **Medium–High** | Backlog |
| 48 | **Transporter dematerialization / rematerialization** | Star Trek | All Eras | Iconic Starfleet molecular transport cycle (~3.0 seconds duration, shimmer-dense). | Complex multi-oscillator additive array (5 detuned sines) with interlocking amplitude modulation and high-frequency noise shimmer. | Medium | **High** | Backlog |
@@ -112,13 +112,19 @@ graph TD
- **Ludicrous Speed Plaid Alarm** (`ExpandedSciFiAudioSynth.synthesizePlaidAlarm`): Comedy Soundboard (`btn-plaid-alarm`), alternating two-tone siren (440 Hz / 880 Hz).
- **True RF radio static burst** (`ExpandedSciFiAudioSynth.synthesizeRadioStatic`): Outlaw Soundboard (`btn-radio-static`), bandpass-filtered noise burst with hard-knee 180 ms gate.
-### Phase 2: Core Enhancements (Medium Difficulty)
-- **Target Count**: 25 elements.
-- **Architectural Scope**: Modulate existing continuous synth nodes (`HullDroneSynth`, `LifeSupportSynth`) and introduce multi-oscillator discrete events.
+### Phase 2: Core Enhancements (Medium Difficulty) — **[COMPLETED]**
+- **Target Count**: 25 elements — **100% Implemented**.
+- **Architectural Scope**: 23 new procedural methods in `ExpandedSciFiAudioSynth` and 2 in `WhoniverseAudioSynth`, a keyed ambient-loop registry (`_scheduleLoop`/`_stopLoop`) replacing ad-hoc timer fields, a single `startPresetAmbience(presetId)` funnel in `js/app.js` that stops and re-engages all loops on preset change/engage/full-stop, and soundboard rebuilds across all 10 universe keypads (6–8 buttons each; `span: 2` support used by the Belter telemetry button).
+- **Implementation Notes**:
+ - Rows #25 (`era: 'who'`) and #29 (`era: 'belter'`) are delivered as standalone routines (`startConsoleTypeTelemetry`, `startBelterTelemetry`) driven by preset ambience — **not** through `TelemetrySynth` era routing, which remains Phase 3 scope.
+ - #24 CRT flyback whine lands at a 10.6 kHz partial (the spec's 15.6 kHz is near-Nyquist on low-rate audio contexts and ear-fatiguing at audible levels).
+ - Continuous rows (#26, #33, #35, #37, #41) are rendered as slow "phrase" one-shots (8–58 s) with in/out envelopes, re-triggered on fixed/random cadence — no permanent node graphs.
+ - Every ambient phrase self-releases (scheduled node stops) and every `start*` kills its own registry key first; `stopAllLoops()` clears the whole registry, so rapid preset flips and FULL STOP cannot stack loops.
+ - One-shot soundboard buttons are interactive one-shots; ambient rows additionally auto-engage with their matching presets (see per-row trigger details in the master table).
- **Key Deliverables**:
- 1. **Structural Stress & Engines**: True Hull Strain Moan (`synthesizeHullStrainMoan()`), Repulsorlift engine drone, Bebop marine diesel chug, Centrifugal carousel 1 rpm LFO modulation.
- 2. **Energy Fields & Weapons**: Vorlon crystal singing resonance, Omega-13 temporal capacitor ramp, Cardassian heavy bulkhead grind, Slipstream transition surge, Death Blossom capacitor sweep.
- 3. **Environmental Ambience**: Nostromo condensation pipe drips (Poisson ping generator), Continuous steam vent hiss, HAL 9000 respiration envelope loop.
+ 1. **Structural Stress & Engines**: True Hull Strain Moan (#30, `synthesizeHullStrainMoan` + loop on Event Horizon/Icarus II), Repulsorlift engine drone (#35), Bebop marine diesel chug (#36), Centrifugal carousel 1 rpm LFO modulation (#37), Crash-couch gimbal strain (#42), Cygnus steam piston chug (#45).
+ 2. **Energy Fields & Weapons**: Vorlon crystal singing resonance (#26), Omega-13 temporal capacitor ramp (#34), Cardassian heavy bulkhead grind (#21), Slipstream transition surge (#38), Death Blossom capacitor sweep (#44), Icarus I distress beacon (#31), Beryllium sphere thrum (#33).
+ 3. **Environmental Ambience**: Nostromo condensation pipe drips (#40, randomized drip clock), Continuous steam vent hiss (#41), HAL 9000 respiration envelope loop (#43), hull regeneration texture (#28), psychic static whisper (#32), turbolift whoosh (#22), replicator shimmer (#23), scanner activation (#24), TARDIS console type clatter (#25), neural-bond swell (#27, also rewired to the Bioships NEURAL PULSE event), Belter jury-rig telemetry (#29), Viper O₂ regulator breathing (#39).
### Phase 3: Major Architectural Additions (Medium–High & High Difficulty)
- **Target Count**: 11 elements.
diff --git a/dist/SciFiAmbientDisplay_v4.html b/dist/SciFiAmbientDisplay_v4.html
index 1712b56..e2fdce4 100644
--- a/dist/SciFiAmbientDisplay_v4.html
+++ b/dist/SciFiAmbientDisplay_v4.html
@@ -4291,6 +4291,7 @@ class WhoniverseAudioSynth {
this.gainNode = null;
this.activeCloister = false;
this.cloisterTimer = null;
+ this.typeTelemetryTimer = null;
}
init() {
@@ -4698,6 +4699,166 @@ class WhoniverseAudioSynth {
click.stop(now + offset + 0.04);
});
}
+
+ /**
+ * Scanner / Monitor Screen Activation (Whoniverse, audio_gaps.md #24)
+ * Rapid upward sine sweep (200 Hz -> 1600 Hz over 120 ms) terminating in a
+ * CRT flyback whine. The 15.6 kHz spec tail is compromised to ~10.6 kHz:
+ * near/above Nyquist on low-rate contexts and ear-fatiguing at audible levels.
+ */
+ synthesizeScannerActivate() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // 1. Cathode-ray startup pitch sweep
+ const sweep = ctx.createOscillator();
+ sweep.type = 'sine';
+ sweep.frequency.setValueAtTime(200, now);
+ sweep.frequency.exponentialRampToValueAtTime(1600, now + 0.12);
+
+ const sweepEnv = ctx.createGain();
+ sweepEnv.gain.setValueAtTime(0.001, now);
+ sweepEnv.gain.linearRampToValueAtTime(0.32, now + 0.03);
+ sweepEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.16);
+
+ sweep.connect(sweepEnv);
+ sweepEnv.connect(this.gainNode);
+ sweep.start(now);
+ sweep.stop(now + 0.17);
+
+ // 2. CRT flyback whine partial (10.6 kHz compromise, low amplitude)
+ const whine = ctx.createOscillator();
+ whine.type = 'sine';
+ whine.frequency.setValueAtTime(10600, now + 0.1);
+
+ const wobble = ctx.createOscillator();
+ wobble.type = 'sine';
+ wobble.frequency.setValueAtTime(1.0, now + 0.1);
+ const wobbleDepth = ctx.createGain();
+ wobbleDepth.gain.setValueAtTime(8, now + 0.1);
+
+ wobble.connect(wobbleDepth);
+ wobbleDepth.connect(whine.frequency);
+
+ const whineEnv = ctx.createGain();
+ whineEnv.gain.setValueAtTime(0.001, now + 0.1);
+ whineEnv.gain.linearRampToValueAtTime(0.03, now + 0.16);
+ whineEnv.gain.exponentialRampToValueAtTime(0.0001, now + 0.85);
+
+ whine.connect(whineEnv);
+ whineEnv.connect(this.gainNode);
+ whine.start(now + 0.1);
+ whine.stop(now + 0.87);
+ wobble.start(now + 0.1);
+ wobble.stop(now + 0.87);
+ }
+
+ /**
+ * Console "Type" Input Clatter Cluster (Whoniverse, audio_gaps.md #25)
+ * Rhythmic mechanical clatter of toggle switches, spring-loaded buttons and
+ * tumbler relays: clustered wooden/plastic clicks and solenoid snaps rather
+ * than tonal beeps. Produces a single 0.3-0.6 s cluster of 3-7 transients.
+ */
+ synthesizeTypeClatter() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const transientCount = 3 + Math.floor(Math.random() * 5); // 3-7 strikes
+ let cursor = 0;
+
+ const strike = (kind, offset, peak) => {
+ const t = now + offset;
+ if (kind === 'toggle') {
+ // Wooden relay toggle: short bandpass pink-noise burst with downward snap
+ const buf = this.am.createNoiseBuffer('pink', 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = buf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(700 + Math.random() * 700, t);
+ bp.frequency.exponentialRampToValueAtTime(240, t + 0.03);
+ bp.Q.setValueAtTime(8 + Math.random() * 4, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 0.005);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.045);
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(t);
+ noise.stop(t + 0.05);
+ } else if (kind === 'button') {
+ // Spring-loaded plastic button: fast square click collapsing in pitch
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(900, t);
+ osc.frequency.exponentialRampToValueAtTime(200, t + 0.012);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(peak * 0.8, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.022);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.025);
+ } else {
+ // Heavy solenoid snap: dull low square thump
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(120, t);
+ osc.frequency.exponentialRampToValueAtTime(55, t + 0.025);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(peak, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.035);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.04);
+ }
+ };
+
+ for (let i = 0; i < transientCount; i++) {
+ const kinds = ['toggle', 'toggle', 'button', 'button', 'solenoid'];
+ const kind = kinds[Math.floor(Math.random() * kinds.length)];
+ cursor += 0.015 + Math.random() * 0.075;
+ strike(kind, cursor, 0.07 + Math.random() * 0.07);
+ }
+ }
+
+ /**
+ * Ambient Loop: TARDIS Console "Type" Input Telemetry (#25)
+ * Recursive random-window clatter while any TARDIS console room is engaged.
+ */
+ startConsoleTypeTelemetry(minIntervalMs = 2800, maxIntervalMs = 6500) {
+ this.stopConsoleTypeTelemetry();
+ const scheduleNext = () => {
+ const delay = minIntervalMs + Math.random() * (maxIntervalMs - minIntervalMs);
+ this.typeTelemetryTimer = setTimeout(() => {
+ this.synthesizeTypeClatter();
+ scheduleNext();
+ }, delay);
+ };
+ scheduleNext();
+ }
+
+ stopConsoleTypeTelemetry() {
+ if (this.typeTelemetryTimer) {
+ clearTimeout(this.typeTelemetryTimer);
+ this.typeTelemetryTimer = null;
+ }
+ }
+
+ /**
+ * Stops all automated Whoniverse ambient loops. Cloister bell is an
+ * interactive toggle and keeps its own explicit call sites.
+ */
+ stopAmbientLoops() {
+ this.stopConsoleTypeTelemetry();
+ }
}
window.WhoniverseAudioSynth = WhoniverseAudioSynth;
@@ -4715,6 +4876,7 @@ class ExpandedSciFiAudioSynth {
this.gainNode = null;
this.medicalTimer = null;
this.sparkTimer = null;
+ this.loopTimers = {};
}
init() {
@@ -6115,9 +6277,1414 @@ class ExpandedSciFiAudioSynth {
}
}
+ /**
+ * Ambient Loop Registry (Phase 2)
+ * Generic recursive-timeout scheduler keyed by name. A start call always
+ * kills any previous instance of the same key first. If min === max the
+ * cadence is fixed; otherwise it is random within [min, max].
+ */
+ _scheduleLoop(key, minIntervalMs, maxIntervalMs, fn) {
+ this._stopLoop(key);
+ const scheduleNext = () => {
+ const delay = maxIntervalMs === minIntervalMs
+ ? minIntervalMs
+ : minIntervalMs + Math.random() * (maxIntervalMs - minIntervalMs);
+ this.loopTimers[key] = setTimeout(() => {
+ fn();
+ scheduleNext();
+ }, delay);
+ };
+ scheduleNext();
+ }
+
+ _stopLoop(key) {
+ if (this.loopTimers[key]) {
+ clearTimeout(this.loopTimers[key]);
+ delete this.loopTimers[key];
+ }
+ }
+
+ /**
+ * Cardassian Bulkhead Door Grind (Star Trek DS9, audio_gaps.md #21)
+ * Oppressive pneumatic bulkhead grinding open: resonant bandpass sweep
+ * across brown noise (120 Hz -> 650 Hz) layered with a scraping metallic
+ * saw undertone.
+ */
+ synthesizeCardassianDoor() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.4;
+
+ const noiseBuf = this.am.createNoiseBuffer('brown', duration + 0.2);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(120, now);
+ bp.frequency.exponentialRampToValueAtTime(650, now + 1.1);
+ bp.Q.setValueAtTime(3.5, now);
+
+ // Scraping metallic saw undertone through the same sweeping bandpass
+ const saw = ctx.createOscillator();
+ saw.type = 'sawtooth';
+ saw.frequency.setValueAtTime(110, now);
+ saw.frequency.exponentialRampToValueAtTime(70, now + duration);
+ const sawGain = ctx.createGain();
+ sawGain.gain.setValueAtTime(0.001, now);
+ sawGain.gain.linearRampToValueAtTime(0.09, now + 0.6);
+ sawGain.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.5, now + 0.4);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ saw.connect(sawGain);
+ sawGain.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ saw.start(now);
+ saw.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Turbolift Pass Whoosh (Star Trek TNG/VOY, audio_gaps.md #22)
+ * Bandpass-filtered white noise pitch sweep (180 -> 450 Hz and back) with
+ * resonant boost and soft stereo panning drift past the listener.
+ */
+ synthesizeTurboliftWhoosh() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.6;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.15);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(180, now);
+ bp.frequency.exponentialRampToValueAtTime(450, now + 0.55);
+ bp.frequency.exponentialRampToValueAtTime(180, now + 1.4);
+ bp.Q.setValueAtTime(2.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.42, now + 0.5);
+ env.gain.linearRampToValueAtTime(0.3, now + 0.8);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ let panOut;
+ if (ctx.createStereoPanner) {
+ const panner = ctx.createStereoPanner();
+ panner.pan.setValueAtTime(-0.45, now);
+ panner.pan.linearRampToValueAtTime(0.45, now + duration);
+ panOut = panner;
+ }
+ if (panOut) {
+ env.connect(panOut);
+ panOut.connect(this.gainNode);
+ } else {
+ env.connect(this.gainNode);
+ }
+
+ noise.connect(bp);
+ bp.connect(env);
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Replicator Materialization Shimmer (Star Trek TNG/VOY, audio_gaps.md #23)
+ * High-frequency white-noise shimmer (2.5-8 kHz) amplitude-modulated by a
+ * fast 30 Hz sine LFO with a soft ramp decay.
+ */
+ synthesizeReplicatorShimmer() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 2.0;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.15);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const masterEnv = ctx.createGain();
+ masterEnv.gain.setValueAtTime(0.001, now);
+ masterEnv.gain.linearRampToValueAtTime(0.3, now + 0.5);
+ masterEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
+ masterEnv.connect(this.gainNode);
+
+ // Two parallel shimmer bands (2.5-4 kHz and 5-8 kHz)
+ const bands = [
+ { center: 3200, q: 1.3, depth: 0.22 },
+ { center: 6400, q: 1.2, depth: 0.16 }
+ ];
+ bands.forEach((band) => {
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(band.center, now);
+ bp.Q.setValueAtTime(band.q, now);
+
+ const amEnv = ctx.createGain();
+ amEnv.gain.setValueAtTime(band.depth, now);
+
+ // 30 Hz shimmer AM
+ const lfo = ctx.createOscillator();
+ lfo.type = 'sine';
+ lfo.frequency.setValueAtTime(30, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(band.depth * 0.9, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(amEnv.gain);
+
+ noise.connect(bp);
+ bp.connect(amEnv);
+ amEnv.connect(masterEnv);
+
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+ });
+
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Vorlon Crystal "Singing" Resonance (Bioships, audio_gaps.md #26)
+ * Ethereal telepathic crystal harmonics: dual detuned sines (528/532 Hz)
+ * through a narrow bandpass with a slow 0.2 Hz undulating tremolo.
+ * One 8.5 s swell phrasing.
+ */
+ synthesizeVorlonSingingSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 8.5;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(530, now);
+ bp.Q.setValueAtTime(22, now);
+
+ const tremolo = ctx.createGain();
+ tremolo.gain.setValueAtTime(0.5, now);
+
+ const lfo = ctx.createOscillator();
+ lfo.type = 'sine';
+ lfo.frequency.setValueAtTime(0.2, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(0.24, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(tremolo.gain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.16, now + 3.0);
+ env.gain.linearRampToValueAtTime(0.13, now + 5.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ [528, 532].forEach((freq, idx) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, now);
+ if (idx === 1) osc.detune.setValueAtTime(3, now);
+ osc.connect(bp);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+
+ bp.connect(tremolo);
+ tremolo.connect(env);
+
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Vorlon singing resonance while the Vorlon cruiser is engaged
+ */
+ startVorlonSong(minIntervalMs = 11000, maxIntervalMs = 18000) {
+ this._scheduleLoop('vorlonSong', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeVorlonSingingSwell();
+ });
+ }
+
+ /**
+ * Neural-Bond Swell (Bioships, audio_gaps.md #27)
+ * Symbiotic neural link between pilot and bioship swelling with emotion:
+ * deep 55 Hz triangle sweeping into a resonant vowel-formant filter
+ * (350-850 Hz) over 2.5 seconds.
+ */
+ synthesizeNeuralBondSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 3.4;
+
+ // Sub-bass carrier
+ const osc = ctx.createOscillator();
+ osc.type = 'triangle';
+ osc.frequency.setValueAtTime(55, now);
+ osc.frequency.linearRampToValueAtTime(52, now + duration);
+
+ // Formant sweep adding vowel-like harmonics
+ const formant = ctx.createBiquadFilter();
+ formant.type = 'bandpass';
+ formant.frequency.setValueAtTime(350, now);
+ formant.frequency.linearRampToValueAtTime(850, now + 2.5);
+ formant.Q.setValueAtTime(4.5, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.34, now + 0.9);
+ env.gain.linearRampToValueAtTime(0.42, now + 1.9);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ osc.connect(formant);
+ formant.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Hull Self-Repair / Regeneration Texture (Bioships, audio_gaps.md #28)
+ * Microscopic organic tissue knit and chitin regrowth: granular
+ * amplitude-modulated pink noise (8-24 Hz modulation) with wet low
+ * regenerative pops. One ~1.8 s regeneration burst.
+ */
+ synthesizeHullRegenBurst() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.8;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', duration + 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(650, now);
+ bp.frequency.linearRampToValueAtTime(1600, now + duration);
+ bp.Q.setValueAtTime(2.0, now);
+
+ const modEnv = ctx.createGain();
+ modEnv.gain.setValueAtTime(0.16, now);
+
+ // Rapid scabbing modulation (~13 Hz) over the wet tissue bed
+ const lfo = ctx.createOscillator();
+ lfo.type = 'square';
+ lfo.frequency.setValueAtTime(9 + Math.random() * 8, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(0.12, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(modEnv.gain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.2, now + 0.25);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(modEnv);
+ modEnv.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+
+ // Wet bioplasmic pops (chitin knit)
+ const popCount = 2 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < popCount; i++) {
+ const popTime = now + 0.25 + Math.random() * (duration - 0.6);
+ const pop = ctx.createOscillator();
+ pop.type = 'sine';
+ pop.frequency.setValueAtTime(110 + Math.random() * 60, popTime);
+ pop.frequency.exponentialRampToValueAtTime(60, popTime + 0.06);
+ const popEnv = ctx.createGain();
+ popEnv.gain.setValueAtTime(0.001, popTime);
+ popEnv.gain.linearRampToValueAtTime(0.14, popTime + 0.008);
+ popEnv.gain.exponentialRampToValueAtTime(0.001, popTime + 0.07);
+ pop.connect(popEnv);
+ popEnv.connect(this.gainNode);
+ pop.start(popTime);
+ pop.stop(popTime + 0.08);
+ }
+ }
+
+ /**
+ * Ambient Loop: Periodic hull regeneration texture on Wraith hives
+ */
+ startHullRegen(minIntervalMs = 6000, maxIntervalMs = 13000) {
+ this._scheduleLoop('hullRegen', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeHullRegenBurst();
+ });
+ }
+
+ /**
+ * Belter Jury-Rigged Telemetry Jitter (Space Stations, audio_gaps.md #29)
+ * Rattle of loose relays, worn copper contactors and erratic voltage drops:
+ * noisy square relay pulses with micro-dropouts, contact-bounce double
+ * clicks and harsh metallic ticks on a jittered clock. One ~1.1 s cluster.
+ */
+ synthesizeBelterTelemetryJitter() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const eventCount = 3 + Math.floor(Math.random() * 5); // 3-7 events
+ let cursor = now + 0.05 + Math.random() * 0.2;
+
+ const relayPulse = (t) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ const baseFreq = 60 + Math.random() * 60;
+ osc.frequency.setValueAtTime(baseFreq, t);
+ osc.frequency.linearRampToValueAtTime(baseFreq * 0.8, t + 0.06);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(420, t);
+ const env = ctx.createGain();
+ // Erratic voltage: randomized micro-dropouts in the pulse body
+ env.gain.setValueAtTime(0.17, t);
+ env.gain.setValueAtTime(0.001, t + 0.012 + Math.random() * 0.015);
+ env.gain.setValueAtTime(0.14, t + 0.026 + Math.random() * 0.02);
+ env.gain.setValueAtTime(0.001, t + 0.05 + Math.random() * 0.025);
+ env.gain.setValueAtTime(0.1, t + 0.07);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
+ osc.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.11);
+ };
+
+ const contactBounce = (t) => {
+ // Worn contactor: double/triple click 1-3 ms apart
+ const clicks = 2 + (Math.random() < 0.35 ? 1 : 0);
+ for (let i = 0; i < clicks; i++) {
+ const ct = t + i * (0.001 + Math.random() * 0.002);
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(500 + Math.random() * 500, ct);
+ osc.frequency.exponentialRampToValueAtTime(120, ct + 0.008);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.14, ct);
+ env.gain.exponentialRampToValueAtTime(0.001, ct + 0.012);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(ct);
+ osc.stop(ct + 0.015);
+ }
+ };
+
+ const metallicTick = (t) => {
+ const buf = this.am.createNoiseBuffer('white', 0.05);
+ const noise = ctx.createBufferSource();
+ noise.buffer = buf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(400 + Math.random() * 1400, t);
+ bp.Q.setValueAtTime(6, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.09, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.02);
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(t);
+ noise.stop(t + 0.025);
+ };
+
+ for (let i = 0; i < eventCount; i++) {
+ const kind = Math.random();
+ if (kind < 0.4) relayPulse(cursor);
+ else if (kind < 0.75) contactBounce(cursor);
+ else metallicTick(cursor);
+ cursor += 0.08 + Math.random() * 0.22; // jittered clock drift
+ }
+ }
+
+ /**
+ * Ambient Loop: Intermittent jury-rigged telemetry on Belter stations
+ */
+ startBelterTelemetry(minIntervalMs = 1800, maxIntervalMs = 4500) {
+ this._scheduleLoop('belterTelemetry', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBelterTelemetryJitter();
+ });
+ }
+
+ /**
+ * True Hull Strain Moan (Deep Space, audio_gaps.md #30)
+ * Massive structural groan of stressed bulkheads flexing under gravitational
+ * shear: resonant bandpass slowly sweeping 40-220 Hz (high Q) over shaped
+ * brown noise with an asymmetric attack/decay. ~5.5 s.
+ */
+ synthesizeHullStrainMoan() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 5.5;
+
+ const noiseBuf = this.am.createNoiseBuffer('brown', duration + 0.3);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(48, now);
+ bp.frequency.exponentialRampToValueAtTime(210, now + 3.4);
+ bp.Q.setValueAtTime(10, now);
+
+ // Asymmetric envelope: slow menacing rise, long creaking decay
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.3, now + 0.9);
+ env.gain.linearRampToValueAtTime(0.22, now + 1.7);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Sporadic structural strain moans on haunted deep-space hulls
+ */
+ startHullStrainMoans(minIntervalMs = 16000, maxIntervalMs = 40000) {
+ this._scheduleLoop('hullStrainMoans', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeHullStrainMoan();
+ });
+ }
+
+ /**
+ * Icarus I Distress Beacon (Deep Space, audio_gaps.md #31)
+ * Eerie hypnotic modal arpeggio echoing from the ghost ship: a 4-note
+ * modal sine sequence through a long feedback delay with soft lowpass
+ * dampening. ~7 s including delay tail.
+ */
+ synthesizeIcarusBeacon() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const notes = [220.0, 261.63, 329.63, 440.0]; // A minor modal beacon
+
+ const delay = ctx.createDelay(1.2);
+ delay.delayTime.setValueAtTime(0.45, now);
+ const feedback = ctx.createGain();
+ feedback.gain.setValueAtTime(0.35, now);
+ const dampen = ctx.createBiquadFilter();
+ dampen.type = 'lowpass';
+ dampen.frequency.setValueAtTime(2200, now);
+ const wet = ctx.createGain();
+ wet.gain.setValueAtTime(0.5, now);
+
+ delay.connect(feedback);
+ feedback.connect(dampen);
+ dampen.connect(delay);
+ delay.connect(wet);
+ wet.connect(this.gainNode);
+
+ notes.forEach((freq, idx) => {
+ const t = now + idx * 0.95;
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.17, t + 0.05);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.85);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ env.connect(delay);
+ osc.start(t);
+ osc.stop(t + 0.9);
+ });
+ }
+
+ /**
+ * Ambient Loop: Periodic distress beacon arpeggio while Icarus II is engaged
+ */
+ startIcarusBeacon(minIntervalMs = 22000, maxIntervalMs = 40000) {
+ this._scheduleLoop('icarusBeacon', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeIcarusBeacon();
+ });
+ }
+
+ /**
+ * Extradimensional Psychic Static Whisper (Deep Space, audio_gaps.md #32)
+ * Menacing psychoacoustic burst: multi-formant filtered white noise with
+ * randomized micro-envelopes evoking whispered vowels (800-2400 Hz).
+ */
+ synthesizeVoidWhisper() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 2.2;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.12, now + 0.4);
+ env.gain.linearRampToValueAtTime(0.09, now + 1.3);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // Three parallel whispered-vowel formants with independent wobbles
+ const formants = [
+ { center: 700, q: 7, wobbleHz: 0.8, wobbleAmt: 160 },
+ { center: 1250, q: 8, wobbleHz: 1.1, wobbleAmt: 280 },
+ { center: 2200, q: 10, wobbleHz: 1.6, wobbleAmt: 420 }
+ ];
+ formants.forEach((formant) => {
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(formant.center, now);
+ bp.Q.setValueAtTime(formant.q, now);
+
+ const wobble = ctx.createOscillator();
+ wobble.type = 'sine';
+ wobble.frequency.setValueAtTime(formant.wobbleHz, now);
+ const wobbleDepth = ctx.createGain();
+ wobbleDepth.gain.setValueAtTime(formant.wobbleAmt, now);
+ wobble.connect(wobbleDepth);
+ wobbleDepth.connect(bp.frequency);
+
+ const breath = ctx.createGain();
+ breath.gain.setValueAtTime(0.3, now);
+ // Randomized micro-envelopes: syllable-like gulps of the formant
+ const gulpCount = 3 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < gulpCount; i++) {
+ const gt = now + 0.15 + Math.random() * (duration - 0.55);
+ breath.gain.setValueAtTime(0.12, gt);
+ breath.gain.linearRampToValueAtTime(0.3 + Math.random() * 0.18, gt + 0.04);
+ breath.gain.linearRampToValueAtTime(0.05, gt + 0.12 + Math.random() * 0.1);
+ }
+
+ noise.connect(bp);
+ bp.connect(breath);
+ breath.connect(env);
+
+ wobble.start(now);
+ wobble.stop(now + duration + 0.05);
+ });
+
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Ambient Loop: Psychic whisper bursts on haunted deep-space hulls
+ */
+ startVoidWhispers(minIntervalMs = 15000, maxIntervalMs = 32000) {
+ this._scheduleLoop('voidWhispers', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeVoidWhisper();
+ });
+ }
+
+ /**
+ * Beryllium Sphere Resonant Thrum (Comedy, audio_gaps.md #33)
+ * Deep glassy crystalline reactor hum: dual pure sines (65 + 195 Hz third
+ * harmonic) with slow beating detune and a subtle comb-filter ring.
+ * One ~10 s swell phrasing.
+ */
+ synthesizeBerylliumThrumSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 10.0;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.16, now + 4.0);
+ env.gain.linearRampToValueAtTime(0.14, now + 6.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // Comb ring: short 5.1 ms reflection
+ const comb = ctx.createDelay(0.05);
+ comb.delayTime.setValueAtTime(0.0051, now);
+ const combFb = ctx.createGain();
+ combFb.gain.setValueAtTime(0.45, now);
+ comb.connect(combFb);
+ combFb.connect(comb);
+ const combWet = ctx.createGain();
+ combWet.gain.setValueAtTime(0.6, now);
+ comb.connect(combWet);
+ combWet.connect(env);
+
+ const partials = [
+ { freq: 65, amp: 0.55 },
+ { freq: 65.3, amp: 0.3 }, // slow 0.3 Hz beating partner
+ { freq: 195, amp: 0.3 }
+ ];
+ partials.forEach((partial) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(partial.freq, now);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(partial.amp, now);
+ osc.connect(g);
+ g.connect(env);
+ g.connect(comb);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+ }
+
+ /**
+ * Ambient Loop: Beryllium sphere thrum while the NSEA Protector is engaged
+ */
+ startBerylliumThrum(minIntervalMs = 9000, maxIntervalMs = 16000) {
+ this._scheduleLoop('berylliumThrum', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBerylliumThrumSwell();
+ });
+ }
+
+ /**
+ * Omega-13 Temporal Capacitor Whine (Comedy, audio_gaps.md #34)
+ * Tremendous temporal capacitor power buildup: deep 40 Hz sub-bass swelling
+ * exponentially to 3200 Hz over 3 seconds, culminating in a wide
+ * white-noise discharge pop.
+ */
+ synthesizeOmega13Whine() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 3.1;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(40, now);
+ osc.frequency.exponentialRampToValueAtTime(3200, now + 3.0);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.exponentialRampToValueAtTime(0.5, now + 2.7);
+ env.gain.setValueAtTime(0.5, now + 2.95);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 3.2);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + duration + 0.1);
+
+ // Dimensional discharge pop (wide white-noise transient)
+ const popTime = now + 3.0;
+ const popBuf = this.am.createNoiseBuffer('white', 0.3);
+ const pop = ctx.createBufferSource();
+ pop.buffer = popBuf;
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(2000, popTime);
+ const popEnv = ctx.createGain();
+ popEnv.gain.setValueAtTime(0.001, popTime);
+ popEnv.gain.linearRampToValueAtTime(0.42, popTime + 0.008);
+ popEnv.gain.exponentialRampToValueAtTime(0.001, popTime + 0.16);
+ pop.connect(hp);
+ hp.connect(popEnv);
+ popEnv.connect(this.gainNode);
+ pop.start(popTime);
+ pop.stop(popTime + 0.18);
+ }
+
+ /**
+ * Repulsorlift Engine Drone (Outlaw, audio_gaps.md #35)
+ * Quintessential anti-gravity vehicle wash: dual detuned sines (68/72 Hz)
+ * through an asymmetric overdrive waveshaper, lowpass-filtered at 280 Hz.
+ * One ~10 s wash phrasing.
+ */
+ synthesizeRepulsorliftDrone() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 10.0;
+
+ let shaperOut = null;
+ if (ctx.createWaveShaper) {
+ const shaper = ctx.createWaveShaper();
+ shaper.oversample = '2x';
+ const curve = new Float32Array(1024);
+ for (let i = 0; i < 1024; i++) {
+ const x = (i / 512) - 1; // -1..1
+ curve[i] = Math.tanh(2.4 * x); // asymmetric-ish saturation
+ }
+ shaper.curve = curve;
+ shaperOut = shaper;
+ }
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(280, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.2, now + 2.6);
+ env.gain.linearRampToValueAtTime(0.17, now + 7.2);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ if (shaperOut) shaperOut.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+
+ [68, 72].forEach((freq) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, now);
+ osc.connect(shaperOut || lp);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+ }
+
+ /**
+ * Ambient Loop: Repulsorlift wash while the Marauder is engaged
+ */
+ startRepulsorliftDrone(intervalMs = 13000) {
+ this._scheduleLoop('repulsorliftDrone', intervalMs, intervalMs, () => {
+ this.synthesizeRepulsorliftDrone();
+ });
+ }
+
+ /**
+ * Converted Marine Trawler Engine Chug (Outlaw, audio_gaps.md #36)
+ * Rhythmic heavy diesel-like piston strokes: low-frequency square pulses
+ * (4-8 Hz cadence) through an 80 Hz resonant lowpass with mechanical
+ * piston wheeze. One cluster of 2-4 strokes.
+ */
+ synthesizeBebopChugStroke() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const strokes = 2 + Math.floor(Math.random() * 3); // 2-4 strokes
+
+ const stroke = (t, peak) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(45 + Math.random() * 25, t);
+ osc.frequency.linearRampToValueAtTime(38, t + 0.3);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(80, t);
+ lp.Q.setValueAtTime(3, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 0.02);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.42);
+ osc.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.45);
+
+ // Mechanical piston wheeze
+ const wheezeBuf = this.am.createNoiseBuffer('pink', 0.3);
+ const wheeze = ctx.createBufferSource();
+ wheeze.buffer = wheezeBuf;
+ const wheezeBp = ctx.createBiquadFilter();
+ wheezeBp.type = 'bandpass';
+ wheezeBp.frequency.setValueAtTime(380, t);
+ wheezeBp.Q.setValueAtTime(1.6, t);
+ const wheezeEnv = ctx.createGain();
+ wheezeEnv.gain.setValueAtTime(0.001, t + 0.02);
+ wheezeEnv.gain.linearRampToValueAtTime(0.05, t + 0.08);
+ wheezeEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
+ wheeze.connect(wheezeBp);
+ wheezeBp.connect(wheezeEnv);
+ wheezeEnv.connect(this.gainNode);
+ wheeze.start(t);
+ wheeze.stop(t + 0.32);
+ };
+
+ for (let i = 0; i < strokes; i++) {
+ stroke(now + i * (0.34 + Math.random() * 0.2), 0.3 - i * 0.04);
+ }
+ }
+
+ /**
+ * Ambient Loop: Rhythmic diesel chug while the Bebop is engaged
+ */
+ startBebopChug(minIntervalMs = 2200, maxIntervalMs = 4200) {
+ this._scheduleLoop('bebopChug', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBebopChugStroke();
+ });
+ }
+
+ /**
+ * Centrifugal Habitat Carousel Motor Groan (Military, audio_gaps.md #37)
+ * Deep rotational strain of a kilometer-long rotating drum: sub-audible
+ * 16 Hz rotational pair with bearing harmonics, modulated by a single
+ * ~1 RPM (0.0167 Hz) revolution LFO. One 58 s rotation pass.
+ */
+ synthesizeCarouselGroanCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 58.0;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.55, now + 8.0);
+ env.gain.linearRampToValueAtTime(0.5, now + 50.0);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // One rotation per ~60 s: slow strain swell and release
+ const rotation = ctx.createOscillator();
+ rotation.type = 'sine';
+ rotation.frequency.setValueAtTime(1 / 60, now);
+ const rotationDepth = ctx.createGain();
+ rotationDepth.gain.setValueAtTime(0.38, now);
+ rotation.connect(rotationDepth);
+ rotationDepth.connect(env.gain);
+
+ const partials = [
+ { freq: 16, amp: 0.55 },
+ { freq: 16.04, amp: 0.32 }, // rotational beating pair
+ { freq: 48, amp: 0.22 }, // structural harmonic
+ { freq: 96, amp: 0.07 } // bearing hum harmonic
+ ];
+ partials.forEach((partial) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(partial.freq, now);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(partial.amp, now);
+ osc.connect(g);
+ g.connect(env);
+ osc.start(now);
+ osc.stop(now + duration + 0.1);
+ });
+
+ rotation.start(now);
+ rotation.stop(now + duration + 0.1);
+ }
+
+ /**
+ * Ambient Loop: Carousel rotation groan while the Agamemnon is engaged
+ * (one 58 s pass every 60 s; the 2 s gap reads as a rotation splice)
+ */
+ startCarouselGroan(intervalMs = 60000) {
+ this._scheduleLoop('carouselGroan', intervalMs, intervalMs, () => {
+ this.synthesizeCarouselGroanCycle();
+ });
+ }
+
+ /**
+ * Slipstream Transition Surge (Military, audio_gaps.md #38)
+ * Ship tearing through an exotic slipstream: dual sweeping bandpass surges
+ * (200 -> 2400 Hz) with escalating resonance Q and a comb-filter shimmer
+ * tail. ~4.2 s.
+ */
+ synthesizeSlipstreamSurge() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Comb shimmer tail bus
+ const shimmer = ctx.createDelay(0.5);
+ shimmer.delayTime.setValueAtTime(0.12, now);
+ const shimmerFb = ctx.createGain();
+ shimmerFb.gain.setValueAtTime(0.3, now);
+ const shimmerLp = ctx.createBiquadFilter();
+ shimmerLp.type = 'lowpass';
+ shimmerLp.frequency.setValueAtTime(4000, now);
+ shimmer.connect(shimmerFb);
+ shimmerFb.connect(shimmerLp);
+ shimmerLp.connect(shimmer);
+ const shimmerWet = ctx.createGain();
+ shimmerWet.gain.setValueAtTime(0.5, now);
+ shimmer.connect(shimmerWet);
+ shimmerWet.connect(this.gainNode);
+
+ const surge = (offset, peak) => {
+ const t = now + offset;
+ const osc = ctx.createOscillator();
+ osc.type = 'sawtooth';
+ osc.frequency.setValueAtTime(200, t);
+ osc.frequency.exponentialRampToValueAtTime(2400, t + 2.6);
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(200, t);
+ bp.frequency.exponentialRampToValueAtTime(2400, t + 2.6);
+ bp.Q.setValueAtTime(2, t);
+ bp.Q.linearRampToValueAtTime(10, t + 2.6);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 1.4);
+ env.gain.setValueAtTime(peak, t + 2.7);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 3.8);
+
+ osc.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ env.connect(shimmer);
+ osc.start(t);
+ osc.stop(t + 4.0);
+ };
+
+ surge(0, 0.22);
+ surge(0.3, 0.16);
+ }
+
+ /**
+ * Viper Pilot Oxygen Regulator Demand Valve (Military, audio_gaps.md #39)
+ * Pulsing rebreather of a Viper pilot mid-combat: a sharp 15 ms mechanical
+ * diaphragm click preceding pulsed highpass-filtered white noise breath
+ * (1.2-4.5 kHz, ~1.1 s). One breath cycle.
+ */
+ synthesizeOxygenRegulatorCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Sharp mechanical diaphragm click
+ const click = ctx.createOscillator();
+ click.type = 'triangle';
+ click.frequency.setValueAtTime(1800, now);
+ click.frequency.exponentialRampToValueAtTime(600, now + 0.015);
+ const clickEnv = ctx.createGain();
+ clickEnv.gain.setValueAtTime(0.3, now);
+ clickEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.02);
+ click.connect(clickEnv);
+ clickEnv.connect(this.gainNode);
+ click.start(now);
+ click.stop(now + 0.025);
+
+ // Regulated airflow breath
+ const noiseBuf = this.am.createNoiseBuffer('white', 1.3);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(1200, now + 0.02);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(4500, now + 0.02);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now + 0.02);
+ env.gain.linearRampToValueAtTime(0.13, now + 0.45);
+ env.gain.linearRampToValueAtTime(0.09, now + 0.62);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 1.15);
+ noise.connect(hp);
+ hp.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now + 0.02);
+ noise.stop(now + 1.2);
+ }
+
+ /**
+ * Ambient Loop: Rhythmic oxygen demand breathing while a Viper cockpit
+ * is engaged
+ */
+ startOxygenRegulator(minIntervalMs = 3400, maxIntervalMs = 5200) {
+ this._scheduleLoop('oxygenRegulator', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeOxygenRegulatorCycle();
+ });
+ }
+
+ /**
+ * Condensation Pipe Drip & Expansion Tick (Industrial, audio_gaps.md #40)
+ * Lonely water droplet pinging inside a kilometres-long cargo hauler:
+ * high-Q sine pings (1400-2600 Hz) with fast exponential decay on a
+ * randomized clock. One drip.
+ */
+ synthesizePipeDrip() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const decay = 0.01 + Math.random() * 0.015;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ const freq = 1400 + Math.random() * 1200;
+ osc.frequency.setValueAtTime(freq, now);
+ osc.frequency.exponentialRampToValueAtTime(freq * 0.92, now + decay);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.1 + Math.random() * 0.2, now + 0.001);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + decay + 0.01);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + decay + 0.03);
+ }
+
+ /**
+ * Ambient Loop: Sporadic condensation drips while the Nostromo is engaged
+ */
+ startPipeDrips(minIntervalMs = 500, maxIntervalMs = 3500) {
+ this._scheduleLoop('pipeDrips', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizePipeDrip();
+ });
+ }
+
+ /**
+ * High-Pressure Steam / Boiler Venting (Industrial, audio_gaps.md #41)
+ * Continuous hot-vent hiss of a steam-era mining vessel: bandpass-filtered
+ * white noise (800-2800 Hz) with a slow amplitude swell and random
+ * micro-flutter. One ~9 s vent pass.
+ */
+ synthesizeSteamVentSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 9.0;
+
+ // Looping noise bed (short buffer, sustained vent)
+ const noiseBuf = this.am.createNoiseBuffer('white', 4.0);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ noise.loop = true;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(1500, now);
+ bp.Q.setValueAtTime(0.7, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.17, now + 3.0);
+ env.gain.linearRampToValueAtTime(0.15, now + 5.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ // Boiler pressure micro-flutter (~6-9 Hz jitter)
+ const flutter = ctx.createOscillator();
+ flutter.type = 'sine';
+ flutter.frequency.setValueAtTime(6 + Math.random() * 3, now);
+ const flutterDepth = ctx.createGain();
+ flutterDepth.gain.setValueAtTime(0.04, now);
+ flutter.connect(flutterDepth);
+ flutterDepth.connect(env.gain);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.1);
+ flutter.start(now);
+ flutter.stop(now + duration + 0.1);
+ }
+
+ /**
+ * Ambient Loop: Boiler vent passes while Serenity-class and Starbug
+ * presets are engaged
+ */
+ startSteamVent(minIntervalMs = 6000, maxIntervalMs = 10000) {
+ this._scheduleLoop('steamVent', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeSteamVentSwell();
+ });
+ }
+
+ /**
+ * Crash-Couch Hydraulic Gimbal Strain (Industrial, audio_gaps.md #42)
+ * Pilot's couch swinging into launch position: FM triangle carrier
+ * (140 Hz / 35 Hz modulator) through lowpass damping that tracks the
+ * acceleration strain. ~1.9 s.
+ */
+ synthesizeCrashCouchGimbal() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.9;
+
+ const layer = (offset, carrierFreq, modFreq) => {
+ const t = now + offset;
+ const carrier = ctx.createOscillator();
+ carrier.type = 'triangle';
+ carrier.frequency.setValueAtTime(carrierFreq, t);
+
+ const mod = ctx.createOscillator();
+ mod.type = 'sine';
+ mod.frequency.setValueAtTime(modFreq, t);
+ const modDepth = ctx.createGain();
+ modDepth.gain.setValueAtTime(carrierFreq * 4, t);
+ mod.connect(modDepth);
+ modDepth.connect(carrier.frequency);
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(300, t);
+ lp.frequency.linearRampToValueAtTime(900, t + 0.5);
+ lp.frequency.exponentialRampToValueAtTime(350, t + duration - offset);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.24, t + 0.55);
+ env.gain.linearRampToValueAtTime(0.2, t + 1.0);
+ env.gain.exponentialRampToValueAtTime(0.0001, t + duration - offset);
+
+ carrier.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ carrier.start(t);
+ carrier.stop(t + (duration - offset) + 0.05);
+ mod.start(t);
+ mod.stop(t + (duration - offset) + 0.05);
+ };
+
+ layer(0, 140, 35);
+ layer(0.42, 152, 38);
+ }
+
+ /**
+ * HAL 9000 Breathing Loop (Retro Future, audio_gaps.md #43)
+ * Eerie slow respiration of a quiet, observant ship computer: rhythmic
+ * bandpass-filtered pink noise (450-1100 Hz) with a gentle 3.5 s
+ * inhalation/exhalation envelope. One breath.
+ */
+ synthesizeHalBreathCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const inhale = 1.3;
+ const exhale = 2.2;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', 4.0);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ noise.loop = true;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(700, now);
+ bp.Q.setValueAtTime(1.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.085, now + inhale);
+ env.gain.exponentialRampToValueAtTime(0.001, now + inhale + exhale);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + inhale + exhale + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Continuous respiration while the Discovery One is engaged
+ */
+ startHalBreathing(intervalMs = 3500) {
+ this._scheduleLoop('halBreathing', intervalMs, intervalMs, () => {
+ this.synthesizeHalBreathCycle();
+ });
+ }
+
+ /**
+ * Death Blossom Energy Surge Ramp (Retro Future, audio_gaps.md #44)
+ * Fearsome gunstar overdrive: cascaded sawtooth exponential ramp
+ * (200 Hz -> 8000 Hz over 2.5 s) with rising overdrive, cutting off
+ * abruptly at full bloom.
+ */
+ synthesizeDeathBlossomSurge() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const rampDur = 2.5;
+
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(400, now);
+ hp.frequency.exponentialRampToValueAtTime(4000, now + rampDur);
+
+ const hp2 = ctx.createBiquadFilter();
+ hp2.type = 'highpass';
+ hp2.frequency.setValueAtTime(200, now);
+ hp2.frequency.exponentialRampToValueAtTime(2000, now + rampDur);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.32, now + rampDur);
+ env.gain.setValueAtTime(0.32, now + rampDur + 0.03);
+ env.gain.exponentialRampToValueAtTime(0.001, now + rampDur + 0.4);
+
+ hp.connect(hp2);
+ hp2.connect(env);
+ env.connect(this.gainNode);
+
+ // Cascaded overdriven saws with staggered ignition
+ [0, 0.15, 0.3].forEach((offset, idx) => {
+ const t = now + offset;
+ const osc = ctx.createOscillator();
+ osc.type = 'sawtooth';
+ osc.frequency.setValueAtTime(200, t);
+ osc.frequency.exponentialRampToValueAtTime(8000, t + rampDur - offset);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.33 - idx * 0.08, t);
+ osc.connect(g);
+ g.connect(hp);
+ osc.start(t);
+ osc.stop(t + (rampDur - offset) + 0.45);
+ });
+
+ // Abrupt bloom transient
+ const burstTime = now + rampDur;
+ const burstBuf = this.am.createNoiseBuffer('white', 0.25);
+ const burst = ctx.createBufferSource();
+ burst.buffer = burstBuf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(3500, burstTime);
+ bp.Q.setValueAtTime(1.5, burstTime);
+ const burstEnv = ctx.createGain();
+ burstEnv.gain.setValueAtTime(0.001, burstTime);
+ burstEnv.gain.linearRampToValueAtTime(0.3, burstTime + 0.01);
+ burstEnv.gain.exponentialRampToValueAtTime(0.001, burstTime + 0.22);
+ burst.connect(bp);
+ bp.connect(burstEnv);
+ burstEnv.connect(this.gainNode);
+ burst.start(burstTime);
+ burst.stop(burstTime + 0.25);
+ }
+
+ /**
+ * Victorian Steam Piston Chug (Retro Future, audio_gaps.md #45)
+ * The steam-age skeleton of the Cygnus: lowpass-filtered noise bursts
+ * synchronized with heavy 40 Hz triangle thumps and a metallic slapback.
+ * One cluster of 2-3 stroke cycles.
+ */
+ synthesizeCygnusPistonChug() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const strokes = 2 + Math.floor(Math.random() * 2); // 2-3 strokes
+
+ const slap = ctx.createDelay(0.6);
+ slap.delayTime.setValueAtTime(0.35, now);
+ const slapFb = ctx.createGain();
+ slapFb.gain.setValueAtTime(0.3, now);
+ const slapWet = ctx.createGain();
+ slapWet.gain.setValueAtTime(0.4, now);
+ slap.connect(slapFb);
+ slapFb.connect(slap);
+ slap.connect(slapWet);
+ slapWet.connect(this.gainNode);
+
+ const stroke = (t, peak) => {
+ // Heavy piston body thump
+ const thump = ctx.createOscillator();
+ thump.type = 'triangle';
+ thump.frequency.setValueAtTime(40, t);
+ thump.frequency.linearRampToValueAtTime(32, t + 0.4);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(120, t);
+ const thumpEnv = ctx.createGain();
+ thumpEnv.gain.setValueAtTime(0.001, t);
+ thumpEnv.gain.linearRampToValueAtTime(peak, t + 0.02);
+ thumpEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.45);
+ thump.connect(lp);
+ lp.connect(thumpEnv);
+ thumpEnv.connect(this.gainNode);
+ thumpEnv.connect(slap);
+ thump.start(t);
+ thump.stop(t + 0.5);
+
+ // Synchronized steam exhaust puff
+ const puffBuf = this.am.createNoiseBuffer('white', 0.16);
+ const puff = ctx.createBufferSource();
+ puff.buffer = puffBuf;
+ const puffLp = ctx.createBiquadFilter();
+ puffLp.type = 'lowpass';
+ puffLp.frequency.setValueAtTime(500, t);
+ const puffEnv = ctx.createGain();
+ puffEnv.gain.setValueAtTime(0.001, t + 0.01);
+ puffEnv.gain.linearRampToValueAtTime(0.13, t + 0.03);
+ puffEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
+ puff.connect(puffLp);
+ puffLp.connect(puffEnv);
+ puffEnv.connect(this.gainNode);
+ puff.start(t);
+ puff.stop(t + 0.17);
+ };
+
+ for (let i = 0; i < strokes; i++) {
+ stroke(now + i * (0.5 + Math.random() * 0.25), 0.26 - i * 0.03);
+ }
+ }
+
+ /**
+ * Ambient Loop: Steam piston chug while the Cygnus is engaged
+ */
+ startCygnusChug(minIntervalMs = 1800, maxIntervalMs = 3200) {
+ this._scheduleLoop('cygnusChug', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeCygnusPistonChug();
+ });
+ }
+
stopAllLoops() {
this.stopMedicalMonitor();
this.stopStationSparks();
+ Object.keys(this.loopTimers).forEach((key) => {
+ this._stopLoop(key);
+ });
}
}
@@ -6710,8 +8277,10 @@ const UniverseRegistry = {
{ id: 'btn-door-swish', label: 'DOOR PNEUMATIC SWISH' },
{ id: 'btn-bosun-whistle', label: "BOSUN'S PIPE WHISTLE" },
{ id: 'btn-medical-monitor', label: 'SICKBAY ECG MONITOR' },
- { id: 'btn-chirp-single', label: 'LCARS SINGLE CHIRP' },
- { id: 'btn-chirp-ack', label: 'LCARS DATA ACK' }
+ { id: 'btn-cardassian-door', label: 'CARDASSIAN BULKHEAD DOOR' },
+ { id: 'btn-turbolift', label: 'TURBOLIFT WHOOSH' },
+ { id: 'btn-replicator', label: 'REPLICATOR SHIMMER' },
+ { id: 'btn-chirp-single', label: 'LCARS SINGLE CHIRP' }
],
presets: StarshipPresets
},
@@ -6738,7 +8307,9 @@ const UniverseRegistry = {
{ id: 'btn-tardis-door', label: 'POLICE BOX WOODEN DOOR' },
{ id: 'btn-fast-return', label: 'FAST-RETURN LEVER' },
{ id: 'btn-demat-switch', label: 'DEMAT SWITCH' },
- { id: 'btn-telepathic', label: 'TELEPATHIC CHIME' }
+ { id: 'btn-telepathic', label: 'TELEPATHIC CHIME' },
+ { id: 'btn-scanner-activate', label: 'SCANNER ACTIVATION' },
+ { id: 'btn-type-clatter', label: 'CONSOLE TYPE CLATTER' }
],
presets: {
'tardis-1963': {
@@ -6847,9 +8418,9 @@ const UniverseRegistry = {
soundboard: [
{ id: 'btn-geiger', label: 'GEIGER COUNTER' },
{ id: 'btn-dock-clamp', label: 'HYDRAULIC CLAMPS' },
- { id: 'btn-relay-click', label: 'M/TH/UR TELETYPE' },
- { id: 'btn-switch-pop', label: 'HEAVY SOLENOID' },
- { id: 'btn-pneumatic-door', label: 'PNEUMATIC SEAL PURGE' },
+ { id: 'btn-pipe-drips', label: 'CONDENSATION DRIPS' },
+ { id: 'btn-steam-vent', label: 'BOILER VENT HISS' },
+ { id: 'btn-crash-couch', label: 'CRASH-COUCH GIMBAL' },
{ id: 'btn-air-handler', label: 'HVAC AIR HANDLER' }
],
presets: {
@@ -6953,12 +8524,12 @@ const UniverseRegistry = {
{ id: 'btn-bio-defense', label: 'BIO-DEFENSE BURST', type: 'biodefense' }
],
soundboard: [
- { id: 'btn-telepathic-chime', label: 'NEURAL TWITCH' },
- { id: 'btn-fast-return', label: 'VASCULAR PUMP' },
- { id: 'btn-demat-switch', label: 'CHITIN CREAK' },
- { id: 'btn-chirp-sweep', label: 'BIOPLASMIC HISS' },
- { id: 'btn-sonic', label: 'SYMBIOTE VOCALIZATION' },
- { id: 'btn-relay-click', label: 'MEMBRANE FLUTTER' }
+ { id: 'btn-neural-bond', label: 'NEURAL-BOND SWELL' },
+ { id: 'btn-fast-return', label: 'VASCULAR PUMP' },
+ { id: 'btn-demat-switch', label: 'CHITIN CREAK' },
+ { id: 'btn-chirp-sweep', label: 'BIOPLASMIC HISS' },
+ { id: 'btn-vorlon-song', label: 'VORLON SINGING RESONANCE' },
+ { id: 'btn-hull-regen', label: 'HULL REGEN TEXTURE' }
],
presets: {
'bio-moya': {
@@ -7041,12 +8612,12 @@ const UniverseRegistry = {
{ id: 'btn-hal-override', label: 'HAL 9000 OVERRIDE', type: 'hal' }
],
soundboard: [
- { id: 'btn-hal-chime', label: 'HAL LOGIC CONFIRM' },
- { id: 'btn-relay-click', label: 'TAPE REEL SPOOL' },
+ { id: 'btn-hal-breath', label: 'HAL RESPIRATION' },
+ { id: 'btn-death-blossom',label: 'DEATH BLOSSOM SURGE' },
+ { id: 'btn-cygnus-chug', label: 'STEAM PISTON CHUG' },
{ id: 'btn-chirp-single', label: 'ASTROGATOR BEAT' },
{ id: 'btn-chirp-sweep', label: 'ANALOG GLISSANDO' },
- { id: 'btn-fast-return', label: 'MAGNETIC TAPE LATCH' },
- { id: 'btn-dock-clamp', label: 'PNEUMATIC SEAL PURGE' }
+ { id: 'btn-relay-click', label: 'TAPE REEL SPOOL' }
],
presets: {
'ret-jupiter-2': {
@@ -7149,12 +8720,12 @@ const UniverseRegistry = {
{ id: 'btn-flak', label: 'FLAK BARRAGE', type: 'flak' }
],
soundboard: [
- { id: 'btn-dradis-ping', label: 'DRADIS SCAN PING' },
- { id: 'btn-switch-pop', label: 'WEAPONS ARMED' },
- { id: 'btn-dock-clamp', label: 'BULKHEAD SEAL' },
- { id: 'btn-chirp-sweep', label: 'RADAR TARGET LOCK' },
- { id: 'btn-fast-return', label: 'TURBO-THRUSTER SWELL' },
- { id: 'btn-air-handler', label: 'RCS THRUSTER BURST' }
+ { id: 'btn-dradis-ping', label: 'DRADIS SCAN PING' },
+ { id: 'btn-switch-pop', label: 'WEAPONS ARMED' },
+ { id: 'btn-dock-clamp', label: 'BULKHEAD SEAL' },
+ { id: 'btn-carousel-groan', label: 'CAROUSEL MOTOR GROAN' },
+ { id: 'btn-slipstream', label: 'SLIPSTREAM SURGE' },
+ { id: 'btn-oxygen-reg', label: 'O2 DEMAND VALVE' }
],
presets: {
'mil-sulaco': {
@@ -7247,12 +8818,12 @@ const UniverseRegistry = {
{ id: 'btn-solar-swell', label: 'SOLAR SHIELD SWELL', type: 'solar' }
],
soundboard: [
- { id: 'btn-solar-roar', label: 'HEAT SHIELD ROAR' },
- { id: 'btn-cryo-sigh', label: 'CRYO POD SIGH' },
- { id: 'btn-orion-thump', label: 'ORION PULSE THUMP' },
- { id: 'btn-dock-clamp', label: 'HULL STRAIN MOAN' },
- { id: 'btn-telepathic-chime', label: 'VOID RESONANCE' },
- { id: 'btn-chirp-sweep', label: 'TACHYON SENSOR PING' }
+ { id: 'btn-solar-roar', label: 'HEAT SHIELD ROAR' },
+ { id: 'btn-cryo-sigh', label: 'CRYO POD SIGH' },
+ { id: 'btn-orion-thump', label: 'ORION PULSE THUMP' },
+ { id: 'btn-hull-strain', label: 'TRUE HULL STRAIN MOAN' },
+ { id: 'btn-void-whisper',label: 'PSYCHIC VOID WHISPER' },
+ { id: 'btn-icarus-beacon', label: 'ICARUS I DISTRESS BEACON' }
],
presets: {
'deep-avalon': {
@@ -7348,9 +8919,9 @@ const UniverseRegistry = {
{ id: 'btn-tape-clunk', label: 'CASSETTE CLUNK & WHIRR' },
{ id: 'btn-grappler-servo', label: 'GRAPPLER SERVO MOTOR' },
{ id: 'btn-radio-static', label: 'RF SQUELCH STATIC BURST' },
- { id: 'btn-dock-clamp', label: 'FUEL LINE VALVE' },
- { id: 'btn-afterburner-snd',label: 'AFTERBURNER BOOST' },
- { id: 'btn-fast-return', label: 'TURBO JET BURN' }
+ { id: 'btn-repulsor-drone', label: 'REPULSORLIFT DRONE' },
+ { id: 'btn-bebop-chug', label: 'MARINE DIESEL CHUG' },
+ { id: 'btn-afterburner-snd',label: 'AFTERBURNER BOOST' }
],
presets: {
'out-betty': {
@@ -7438,7 +9009,7 @@ const UniverseRegistry = {
{ id: 'btn-commlock', label: 'COMMLOCK CALLING TONE' },
{ id: 'btn-sevas-spark', label: 'CONDUIT SPARK ARC' },
{ id: 'btn-station-ecg', label: 'CLINICAL VITAL (ECG)' },
- { id: 'btn-dock-clamp', label: 'DOCKING CLAMP LATCH' }
+ { id: 'btn-belter-telemetry', label: 'BELTER JURY-RIG TELEMETRY', span: 2 }
],
presets: {
'sta-babylon-5': {
@@ -7525,8 +9096,8 @@ const UniverseRegistry = {
{ id: 'btn-tea-machine', label: 'TEA DISPENSER GURGLE' },
{ id: 'btn-kettle-whistle', label: 'KETTLE STEAM WHISTLE' },
{ id: 'btn-plaid-alarm', label: 'LUDICROUS PLAID ALARM' },
- { id: 'btn-beryllium-pulse',label: 'BERYLLIUM CORE PULSE' },
- { id: 'btn-chirp-ack', label: 'ORVILLE ACK CHIRP' }
+ { id: 'btn-beryllium-thrum',label: 'BERYLLIUM SPHERE THRUM' },
+ { id: 'btn-omega-13', label: 'OMEGA-13 CAPACITOR WHINE' }
],
presets: {
'com-orville': {
@@ -11939,6 +13510,41 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
+ // 4a. Phase 2 Preset Ambience
+ // Single funnel that stops every automated loop (legacy + registry) and then
+ // re-engages the ambience matching the currently selected preset.
+ function startPresetAmbience(presetId) {
+ expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
+
+ // Legacy Phase 1 loops, kept on their original presets
+ if (presetId === 'sta-sevastopol') {
+ expandedAudio.startStationSparks(4000, 11000);
+ } else if (presetId === 'sta-gateway' || presetId.includes('sickbay') || presetId.includes('med')) {
+ expandedAudio.startMedicalMonitor(1.15);
+ }
+
+ // Phase 2 audio_gaps.md rows #25-#45
+ if (activeUniverseId === 'whoniverse' && presetId.indexOf('tardis-') === 0) {
+ whoniverseAudio.startConsoleTypeTelemetry(2800, 6500); // #25
+ }
+ if (presetId === 'bio-vorlon') expandedAudio.startVorlonSong(); // #26
+ if (presetId === 'bio-wraith') expandedAudio.startHullRegen(); // #28
+ if (presetId === 'sta-ceres' || presetId === 'sta-tycho') expandedAudio.startBelterTelemetry(); // #29
+ if (presetId === 'deep-event-horizon' || presetId === 'deep-icarus-ii') expandedAudio.startHullStrainMoans(); // #30
+ if (presetId === 'deep-icarus-ii') expandedAudio.startIcarusBeacon(); // #31
+ if (presetId === 'deep-event-horizon' || presetId === 'deep-nightflyer') expandedAudio.startVoidWhispers(); // #32
+ if (presetId === 'com-protector') expandedAudio.startBerylliumThrum(); // #33
+ if (presetId === 'out-marauder') expandedAudio.startRepulsorliftDrone(); // #35
+ if (presetId === 'out-bebop') expandedAudio.startBebopChug(); // #36
+ if (presetId === 'mil-agamemnon') expandedAudio.startCarouselGroan(); // #37
+ if (presetId === 'mil-viper') expandedAudio.startOxygenRegulator(); // #39
+ if (presetId === 'ind-nostromo') expandedAudio.startPipeDrips(); // #40
+ if (presetId === 'ind-serenity' || presetId === 'ind-starbug') expandedAudio.startSteamVent(); // #41
+ if (presetId === 'ret-discovery-one') expandedAudio.startHalBreathing(); // #43
+ if (presetId === 'ret-cygnus') expandedAudio.startCygnusChug(); // #45
+ }
+
// 4. Select & Apply Preset
function selectPreset(presetId) {
const universe = UniverseRegistry[activeUniverseId];
@@ -11967,13 +13573,9 @@ document.addEventListener('DOMContentLoaded', () => {
syncSlidersToSynthParams();
expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
if (isPlaying) {
- if (presetId === 'sta-sevastopol') {
- expandedAudio.startStationSparks(4000, 11000);
- } else if (presetId === 'sta-gateway' || presetId.includes('sickbay') || presetId.includes('med')) {
- expandedAudio.startMedicalMonitor(1.15);
- }
-
+ startPresetAmbience(presetId);
if (activeUniverseId === 'whoniverse') whoniverseAudio.synthesizeDematSwitch();
else if (activeUniverseId === 'industrial') expandedAudio.synthesizeDockingClamp();
else telemetry.synthesizeLCARSSingleChirp();
@@ -12056,10 +13658,8 @@ document.addEventListener('DOMContentLoaded', () => {
statAudioStatus.textContent = 'ONLINE';
statAudioStatus.style.color = '#00ff88';
- if (activePresetId === 'sta-sevastopol') {
- expandedAudio.startStationSparks(4000, 11000);
- } else if (activePresetId === 'sta-gateway' || activePresetId.includes('sickbay') || activePresetId.includes('med')) {
- expandedAudio.startMedicalMonitor(1.15);
+ if (activePresetId) {
+ startPresetAmbience(activePresetId);
}
}, ENGINE_TRANSITION_SECS * 1000);
@@ -12084,6 +13684,7 @@ document.addEventListener('DOMContentLoaded', () => {
lifeSupport.stop(ENGINE_TRANSITION_SECS);
telemetry.stop();
expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
alerts.stopAlert();
whoniverseAudio.stopCloisterBell();
resetAlertButtons();
@@ -12288,7 +13889,8 @@ document.addEventListener('DOMContentLoaded', () => {
visualizer.spawnWarpPulses();
break;
case 'neural':
- whoniverseAudio.synthesizeTelepathicChime();
+ expandedAudio.synthesizeNeuralBondSwell();
+ visualizer.spawnWarpPulses();
break;
case 'centrifuge':
case 'astrogator':
@@ -12469,6 +14071,83 @@ document.addEventListener('DOMContentLoaded', () => {
expandedAudio.synthesizeHalChime();
break;
+ // Phase 2 (audio_gaps.md #21-#45)
+ case 'btn-cardassian-door':
+ expandedAudio.synthesizeCardassianDoor();
+ break;
+ case 'btn-turbolift':
+ expandedAudio.synthesizeTurboliftWhoosh();
+ break;
+ case 'btn-replicator':
+ expandedAudio.synthesizeReplicatorShimmer();
+ break;
+ case 'btn-scanner-activate':
+ whoniverseAudio.synthesizeScannerActivate();
+ break;
+ case 'btn-type-clatter':
+ whoniverseAudio.synthesizeTypeClatter();
+ break;
+ case 'btn-vorlon-song':
+ expandedAudio.synthesizeVorlonSingingSwell();
+ break;
+ case 'btn-neural-bond':
+ expandedAudio.synthesizeNeuralBondSwell();
+ break;
+ case 'btn-hull-regen':
+ expandedAudio.synthesizeHullRegenBurst();
+ break;
+ case 'btn-belter-telemetry':
+ expandedAudio.synthesizeBelterTelemetryJitter();
+ break;
+ case 'btn-hull-strain':
+ expandedAudio.synthesizeHullStrainMoan();
+ break;
+ case 'btn-icarus-beacon':
+ expandedAudio.synthesizeIcarusBeacon();
+ break;
+ case 'btn-void-whisper':
+ expandedAudio.synthesizeVoidWhisper();
+ break;
+ case 'btn-beryllium-thrum':
+ expandedAudio.synthesizeBerylliumThrumSwell();
+ break;
+ case 'btn-omega-13':
+ expandedAudio.synthesizeOmega13Whine();
+ break;
+ case 'btn-repulsor-drone':
+ expandedAudio.synthesizeRepulsorliftDrone();
+ break;
+ case 'btn-bebop-chug':
+ expandedAudio.synthesizeBebopChugStroke();
+ break;
+ case 'btn-carousel-groan':
+ expandedAudio.synthesizeCarouselGroanCycle();
+ break;
+ case 'btn-slipstream':
+ expandedAudio.synthesizeSlipstreamSurge();
+ break;
+ case 'btn-oxygen-reg':
+ expandedAudio.synthesizeOxygenRegulatorCycle();
+ break;
+ case 'btn-pipe-drips':
+ expandedAudio.synthesizePipeDrip();
+ break;
+ case 'btn-steam-vent':
+ expandedAudio.synthesizeSteamVentSwell();
+ break;
+ case 'btn-crash-couch':
+ expandedAudio.synthesizeCrashCouchGimbal();
+ break;
+ case 'btn-hal-breath':
+ expandedAudio.synthesizeHalBreathCycle();
+ break;
+ case 'btn-death-blossom':
+ expandedAudio.synthesizeDeathBlossomSurge();
+ break;
+ case 'btn-cygnus-chug':
+ expandedAudio.synthesizeCygnusPistonChug();
+ break;
+
default:
telemetry.synthesizeLCARSSingleChirp();
break;
diff --git a/js/app.js b/js/app.js
index 04acf68..fcfe877 100644
--- a/js/app.js
+++ b/js/app.js
@@ -283,6 +283,41 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
+ // 4a. Phase 2 Preset Ambience
+ // Single funnel that stops every automated loop (legacy + registry) and then
+ // re-engages the ambience matching the currently selected preset.
+ function startPresetAmbience(presetId) {
+ expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
+
+ // Legacy Phase 1 loops, kept on their original presets
+ if (presetId === 'sta-sevastopol') {
+ expandedAudio.startStationSparks(4000, 11000);
+ } else if (presetId === 'sta-gateway' || presetId.includes('sickbay') || presetId.includes('med')) {
+ expandedAudio.startMedicalMonitor(1.15);
+ }
+
+ // Phase 2 audio_gaps.md rows #25-#45
+ if (activeUniverseId === 'whoniverse' && presetId.indexOf('tardis-') === 0) {
+ whoniverseAudio.startConsoleTypeTelemetry(2800, 6500); // #25
+ }
+ if (presetId === 'bio-vorlon') expandedAudio.startVorlonSong(); // #26
+ if (presetId === 'bio-wraith') expandedAudio.startHullRegen(); // #28
+ if (presetId === 'sta-ceres' || presetId === 'sta-tycho') expandedAudio.startBelterTelemetry(); // #29
+ if (presetId === 'deep-event-horizon' || presetId === 'deep-icarus-ii') expandedAudio.startHullStrainMoans(); // #30
+ if (presetId === 'deep-icarus-ii') expandedAudio.startIcarusBeacon(); // #31
+ if (presetId === 'deep-event-horizon' || presetId === 'deep-nightflyer') expandedAudio.startVoidWhispers(); // #32
+ if (presetId === 'com-protector') expandedAudio.startBerylliumThrum(); // #33
+ if (presetId === 'out-marauder') expandedAudio.startRepulsorliftDrone(); // #35
+ if (presetId === 'out-bebop') expandedAudio.startBebopChug(); // #36
+ if (presetId === 'mil-agamemnon') expandedAudio.startCarouselGroan(); // #37
+ if (presetId === 'mil-viper') expandedAudio.startOxygenRegulator(); // #39
+ if (presetId === 'ind-nostromo') expandedAudio.startPipeDrips(); // #40
+ if (presetId === 'ind-serenity' || presetId === 'ind-starbug') expandedAudio.startSteamVent(); // #41
+ if (presetId === 'ret-discovery-one') expandedAudio.startHalBreathing(); // #43
+ if (presetId === 'ret-cygnus') expandedAudio.startCygnusChug(); // #45
+ }
+
// 4. Select & Apply Preset
function selectPreset(presetId) {
const universe = UniverseRegistry[activeUniverseId];
@@ -311,13 +346,9 @@ document.addEventListener('DOMContentLoaded', () => {
syncSlidersToSynthParams();
expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
if (isPlaying) {
- if (presetId === 'sta-sevastopol') {
- expandedAudio.startStationSparks(4000, 11000);
- } else if (presetId === 'sta-gateway' || presetId.includes('sickbay') || presetId.includes('med')) {
- expandedAudio.startMedicalMonitor(1.15);
- }
-
+ startPresetAmbience(presetId);
if (activeUniverseId === 'whoniverse') whoniverseAudio.synthesizeDematSwitch();
else if (activeUniverseId === 'industrial') expandedAudio.synthesizeDockingClamp();
else telemetry.synthesizeLCARSSingleChirp();
@@ -400,10 +431,8 @@ document.addEventListener('DOMContentLoaded', () => {
statAudioStatus.textContent = 'ONLINE';
statAudioStatus.style.color = '#00ff88';
- if (activePresetId === 'sta-sevastopol') {
- expandedAudio.startStationSparks(4000, 11000);
- } else if (activePresetId === 'sta-gateway' || activePresetId.includes('sickbay') || activePresetId.includes('med')) {
- expandedAudio.startMedicalMonitor(1.15);
+ if (activePresetId) {
+ startPresetAmbience(activePresetId);
}
}, ENGINE_TRANSITION_SECS * 1000);
@@ -428,6 +457,7 @@ document.addEventListener('DOMContentLoaded', () => {
lifeSupport.stop(ENGINE_TRANSITION_SECS);
telemetry.stop();
expandedAudio.stopAllLoops();
+ whoniverseAudio.stopAmbientLoops();
alerts.stopAlert();
whoniverseAudio.stopCloisterBell();
resetAlertButtons();
@@ -632,7 +662,8 @@ document.addEventListener('DOMContentLoaded', () => {
visualizer.spawnWarpPulses();
break;
case 'neural':
- whoniverseAudio.synthesizeTelepathicChime();
+ expandedAudio.synthesizeNeuralBondSwell();
+ visualizer.spawnWarpPulses();
break;
case 'centrifuge':
case 'astrogator':
@@ -813,6 +844,83 @@ document.addEventListener('DOMContentLoaded', () => {
expandedAudio.synthesizeHalChime();
break;
+ // Phase 2 (audio_gaps.md #21-#45)
+ case 'btn-cardassian-door':
+ expandedAudio.synthesizeCardassianDoor();
+ break;
+ case 'btn-turbolift':
+ expandedAudio.synthesizeTurboliftWhoosh();
+ break;
+ case 'btn-replicator':
+ expandedAudio.synthesizeReplicatorShimmer();
+ break;
+ case 'btn-scanner-activate':
+ whoniverseAudio.synthesizeScannerActivate();
+ break;
+ case 'btn-type-clatter':
+ whoniverseAudio.synthesizeTypeClatter();
+ break;
+ case 'btn-vorlon-song':
+ expandedAudio.synthesizeVorlonSingingSwell();
+ break;
+ case 'btn-neural-bond':
+ expandedAudio.synthesizeNeuralBondSwell();
+ break;
+ case 'btn-hull-regen':
+ expandedAudio.synthesizeHullRegenBurst();
+ break;
+ case 'btn-belter-telemetry':
+ expandedAudio.synthesizeBelterTelemetryJitter();
+ break;
+ case 'btn-hull-strain':
+ expandedAudio.synthesizeHullStrainMoan();
+ break;
+ case 'btn-icarus-beacon':
+ expandedAudio.synthesizeIcarusBeacon();
+ break;
+ case 'btn-void-whisper':
+ expandedAudio.synthesizeVoidWhisper();
+ break;
+ case 'btn-beryllium-thrum':
+ expandedAudio.synthesizeBerylliumThrumSwell();
+ break;
+ case 'btn-omega-13':
+ expandedAudio.synthesizeOmega13Whine();
+ break;
+ case 'btn-repulsor-drone':
+ expandedAudio.synthesizeRepulsorliftDrone();
+ break;
+ case 'btn-bebop-chug':
+ expandedAudio.synthesizeBebopChugStroke();
+ break;
+ case 'btn-carousel-groan':
+ expandedAudio.synthesizeCarouselGroanCycle();
+ break;
+ case 'btn-slipstream':
+ expandedAudio.synthesizeSlipstreamSurge();
+ break;
+ case 'btn-oxygen-reg':
+ expandedAudio.synthesizeOxygenRegulatorCycle();
+ break;
+ case 'btn-pipe-drips':
+ expandedAudio.synthesizePipeDrip();
+ break;
+ case 'btn-steam-vent':
+ expandedAudio.synthesizeSteamVentSwell();
+ break;
+ case 'btn-crash-couch':
+ expandedAudio.synthesizeCrashCouchGimbal();
+ break;
+ case 'btn-hal-breath':
+ expandedAudio.synthesizeHalBreathCycle();
+ break;
+ case 'btn-death-blossom':
+ expandedAudio.synthesizeDeathBlossomSurge();
+ break;
+ case 'btn-cygnus-chug':
+ expandedAudio.synthesizeCygnusPistonChug();
+ break;
+
default:
telemetry.synthesizeLCARSSingleChirp();
break;
diff --git a/js/audio.js b/js/audio.js
index 9b48ccf..5c6c227 100644
--- a/js/audio.js
+++ b/js/audio.js
@@ -1557,6 +1557,7 @@ class WhoniverseAudioSynth {
this.gainNode = null;
this.activeCloister = false;
this.cloisterTimer = null;
+ this.typeTelemetryTimer = null;
}
init() {
@@ -1964,6 +1965,166 @@ class WhoniverseAudioSynth {
click.stop(now + offset + 0.04);
});
}
+
+ /**
+ * Scanner / Monitor Screen Activation (Whoniverse, audio_gaps.md #24)
+ * Rapid upward sine sweep (200 Hz -> 1600 Hz over 120 ms) terminating in a
+ * CRT flyback whine. The 15.6 kHz spec tail is compromised to ~10.6 kHz:
+ * near/above Nyquist on low-rate contexts and ear-fatiguing at audible levels.
+ */
+ synthesizeScannerActivate() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // 1. Cathode-ray startup pitch sweep
+ const sweep = ctx.createOscillator();
+ sweep.type = 'sine';
+ sweep.frequency.setValueAtTime(200, now);
+ sweep.frequency.exponentialRampToValueAtTime(1600, now + 0.12);
+
+ const sweepEnv = ctx.createGain();
+ sweepEnv.gain.setValueAtTime(0.001, now);
+ sweepEnv.gain.linearRampToValueAtTime(0.32, now + 0.03);
+ sweepEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.16);
+
+ sweep.connect(sweepEnv);
+ sweepEnv.connect(this.gainNode);
+ sweep.start(now);
+ sweep.stop(now + 0.17);
+
+ // 2. CRT flyback whine partial (10.6 kHz compromise, low amplitude)
+ const whine = ctx.createOscillator();
+ whine.type = 'sine';
+ whine.frequency.setValueAtTime(10600, now + 0.1);
+
+ const wobble = ctx.createOscillator();
+ wobble.type = 'sine';
+ wobble.frequency.setValueAtTime(1.0, now + 0.1);
+ const wobbleDepth = ctx.createGain();
+ wobbleDepth.gain.setValueAtTime(8, now + 0.1);
+
+ wobble.connect(wobbleDepth);
+ wobbleDepth.connect(whine.frequency);
+
+ const whineEnv = ctx.createGain();
+ whineEnv.gain.setValueAtTime(0.001, now + 0.1);
+ whineEnv.gain.linearRampToValueAtTime(0.03, now + 0.16);
+ whineEnv.gain.exponentialRampToValueAtTime(0.0001, now + 0.85);
+
+ whine.connect(whineEnv);
+ whineEnv.connect(this.gainNode);
+ whine.start(now + 0.1);
+ whine.stop(now + 0.87);
+ wobble.start(now + 0.1);
+ wobble.stop(now + 0.87);
+ }
+
+ /**
+ * Console "Type" Input Clatter Cluster (Whoniverse, audio_gaps.md #25)
+ * Rhythmic mechanical clatter of toggle switches, spring-loaded buttons and
+ * tumbler relays: clustered wooden/plastic clicks and solenoid snaps rather
+ * than tonal beeps. Produces a single 0.3-0.6 s cluster of 3-7 transients.
+ */
+ synthesizeTypeClatter() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const transientCount = 3 + Math.floor(Math.random() * 5); // 3-7 strikes
+ let cursor = 0;
+
+ const strike = (kind, offset, peak) => {
+ const t = now + offset;
+ if (kind === 'toggle') {
+ // Wooden relay toggle: short bandpass pink-noise burst with downward snap
+ const buf = this.am.createNoiseBuffer('pink', 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = buf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(700 + Math.random() * 700, t);
+ bp.frequency.exponentialRampToValueAtTime(240, t + 0.03);
+ bp.Q.setValueAtTime(8 + Math.random() * 4, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 0.005);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.045);
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(t);
+ noise.stop(t + 0.05);
+ } else if (kind === 'button') {
+ // Spring-loaded plastic button: fast square click collapsing in pitch
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(900, t);
+ osc.frequency.exponentialRampToValueAtTime(200, t + 0.012);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(peak * 0.8, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.022);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.025);
+ } else {
+ // Heavy solenoid snap: dull low square thump
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(120, t);
+ osc.frequency.exponentialRampToValueAtTime(55, t + 0.025);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(peak, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.035);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.04);
+ }
+ };
+
+ for (let i = 0; i < transientCount; i++) {
+ const kinds = ['toggle', 'toggle', 'button', 'button', 'solenoid'];
+ const kind = kinds[Math.floor(Math.random() * kinds.length)];
+ cursor += 0.015 + Math.random() * 0.075;
+ strike(kind, cursor, 0.07 + Math.random() * 0.07);
+ }
+ }
+
+ /**
+ * Ambient Loop: TARDIS Console "Type" Input Telemetry (#25)
+ * Recursive random-window clatter while any TARDIS console room is engaged.
+ */
+ startConsoleTypeTelemetry(minIntervalMs = 2800, maxIntervalMs = 6500) {
+ this.stopConsoleTypeTelemetry();
+ const scheduleNext = () => {
+ const delay = minIntervalMs + Math.random() * (maxIntervalMs - minIntervalMs);
+ this.typeTelemetryTimer = setTimeout(() => {
+ this.synthesizeTypeClatter();
+ scheduleNext();
+ }, delay);
+ };
+ scheduleNext();
+ }
+
+ stopConsoleTypeTelemetry() {
+ if (this.typeTelemetryTimer) {
+ clearTimeout(this.typeTelemetryTimer);
+ this.typeTelemetryTimer = null;
+ }
+ }
+
+ /**
+ * Stops all automated Whoniverse ambient loops. Cloister bell is an
+ * interactive toggle and keeps its own explicit call sites.
+ */
+ stopAmbientLoops() {
+ this.stopConsoleTypeTelemetry();
+ }
}
window.WhoniverseAudioSynth = WhoniverseAudioSynth;
@@ -1981,6 +2142,7 @@ class ExpandedSciFiAudioSynth {
this.gainNode = null;
this.medicalTimer = null;
this.sparkTimer = null;
+ this.loopTimers = {};
}
init() {
@@ -3381,9 +3543,1414 @@ class ExpandedSciFiAudioSynth {
}
}
+ /**
+ * Ambient Loop Registry (Phase 2)
+ * Generic recursive-timeout scheduler keyed by name. A start call always
+ * kills any previous instance of the same key first. If min === max the
+ * cadence is fixed; otherwise it is random within [min, max].
+ */
+ _scheduleLoop(key, minIntervalMs, maxIntervalMs, fn) {
+ this._stopLoop(key);
+ const scheduleNext = () => {
+ const delay = maxIntervalMs === minIntervalMs
+ ? minIntervalMs
+ : minIntervalMs + Math.random() * (maxIntervalMs - minIntervalMs);
+ this.loopTimers[key] = setTimeout(() => {
+ fn();
+ scheduleNext();
+ }, delay);
+ };
+ scheduleNext();
+ }
+
+ _stopLoop(key) {
+ if (this.loopTimers[key]) {
+ clearTimeout(this.loopTimers[key]);
+ delete this.loopTimers[key];
+ }
+ }
+
+ /**
+ * Cardassian Bulkhead Door Grind (Star Trek DS9, audio_gaps.md #21)
+ * Oppressive pneumatic bulkhead grinding open: resonant bandpass sweep
+ * across brown noise (120 Hz -> 650 Hz) layered with a scraping metallic
+ * saw undertone.
+ */
+ synthesizeCardassianDoor() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.4;
+
+ const noiseBuf = this.am.createNoiseBuffer('brown', duration + 0.2);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(120, now);
+ bp.frequency.exponentialRampToValueAtTime(650, now + 1.1);
+ bp.Q.setValueAtTime(3.5, now);
+
+ // Scraping metallic saw undertone through the same sweeping bandpass
+ const saw = ctx.createOscillator();
+ saw.type = 'sawtooth';
+ saw.frequency.setValueAtTime(110, now);
+ saw.frequency.exponentialRampToValueAtTime(70, now + duration);
+ const sawGain = ctx.createGain();
+ sawGain.gain.setValueAtTime(0.001, now);
+ sawGain.gain.linearRampToValueAtTime(0.09, now + 0.6);
+ sawGain.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.5, now + 0.4);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ saw.connect(sawGain);
+ sawGain.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ saw.start(now);
+ saw.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Turbolift Pass Whoosh (Star Trek TNG/VOY, audio_gaps.md #22)
+ * Bandpass-filtered white noise pitch sweep (180 -> 450 Hz and back) with
+ * resonant boost and soft stereo panning drift past the listener.
+ */
+ synthesizeTurboliftWhoosh() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.6;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.15);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(180, now);
+ bp.frequency.exponentialRampToValueAtTime(450, now + 0.55);
+ bp.frequency.exponentialRampToValueAtTime(180, now + 1.4);
+ bp.Q.setValueAtTime(2.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.42, now + 0.5);
+ env.gain.linearRampToValueAtTime(0.3, now + 0.8);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ let panOut;
+ if (ctx.createStereoPanner) {
+ const panner = ctx.createStereoPanner();
+ panner.pan.setValueAtTime(-0.45, now);
+ panner.pan.linearRampToValueAtTime(0.45, now + duration);
+ panOut = panner;
+ }
+ if (panOut) {
+ env.connect(panOut);
+ panOut.connect(this.gainNode);
+ } else {
+ env.connect(this.gainNode);
+ }
+
+ noise.connect(bp);
+ bp.connect(env);
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Replicator Materialization Shimmer (Star Trek TNG/VOY, audio_gaps.md #23)
+ * High-frequency white-noise shimmer (2.5-8 kHz) amplitude-modulated by a
+ * fast 30 Hz sine LFO with a soft ramp decay.
+ */
+ synthesizeReplicatorShimmer() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 2.0;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.15);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const masterEnv = ctx.createGain();
+ masterEnv.gain.setValueAtTime(0.001, now);
+ masterEnv.gain.linearRampToValueAtTime(0.3, now + 0.5);
+ masterEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
+ masterEnv.connect(this.gainNode);
+
+ // Two parallel shimmer bands (2.5-4 kHz and 5-8 kHz)
+ const bands = [
+ { center: 3200, q: 1.3, depth: 0.22 },
+ { center: 6400, q: 1.2, depth: 0.16 }
+ ];
+ bands.forEach((band) => {
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(band.center, now);
+ bp.Q.setValueAtTime(band.q, now);
+
+ const amEnv = ctx.createGain();
+ amEnv.gain.setValueAtTime(band.depth, now);
+
+ // 30 Hz shimmer AM
+ const lfo = ctx.createOscillator();
+ lfo.type = 'sine';
+ lfo.frequency.setValueAtTime(30, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(band.depth * 0.9, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(amEnv.gain);
+
+ noise.connect(bp);
+ bp.connect(amEnv);
+ amEnv.connect(masterEnv);
+
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+ });
+
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Vorlon Crystal "Singing" Resonance (Bioships, audio_gaps.md #26)
+ * Ethereal telepathic crystal harmonics: dual detuned sines (528/532 Hz)
+ * through a narrow bandpass with a slow 0.2 Hz undulating tremolo.
+ * One 8.5 s swell phrasing.
+ */
+ synthesizeVorlonSingingSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 8.5;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(530, now);
+ bp.Q.setValueAtTime(22, now);
+
+ const tremolo = ctx.createGain();
+ tremolo.gain.setValueAtTime(0.5, now);
+
+ const lfo = ctx.createOscillator();
+ lfo.type = 'sine';
+ lfo.frequency.setValueAtTime(0.2, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(0.24, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(tremolo.gain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.16, now + 3.0);
+ env.gain.linearRampToValueAtTime(0.13, now + 5.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ [528, 532].forEach((freq, idx) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, now);
+ if (idx === 1) osc.detune.setValueAtTime(3, now);
+ osc.connect(bp);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+
+ bp.connect(tremolo);
+ tremolo.connect(env);
+
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Vorlon singing resonance while the Vorlon cruiser is engaged
+ */
+ startVorlonSong(minIntervalMs = 11000, maxIntervalMs = 18000) {
+ this._scheduleLoop('vorlonSong', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeVorlonSingingSwell();
+ });
+ }
+
+ /**
+ * Neural-Bond Swell (Bioships, audio_gaps.md #27)
+ * Symbiotic neural link between pilot and bioship swelling with emotion:
+ * deep 55 Hz triangle sweeping into a resonant vowel-formant filter
+ * (350-850 Hz) over 2.5 seconds.
+ */
+ synthesizeNeuralBondSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 3.4;
+
+ // Sub-bass carrier
+ const osc = ctx.createOscillator();
+ osc.type = 'triangle';
+ osc.frequency.setValueAtTime(55, now);
+ osc.frequency.linearRampToValueAtTime(52, now + duration);
+
+ // Formant sweep adding vowel-like harmonics
+ const formant = ctx.createBiquadFilter();
+ formant.type = 'bandpass';
+ formant.frequency.setValueAtTime(350, now);
+ formant.frequency.linearRampToValueAtTime(850, now + 2.5);
+ formant.Q.setValueAtTime(4.5, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.34, now + 0.9);
+ env.gain.linearRampToValueAtTime(0.42, now + 1.9);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ osc.connect(formant);
+ formant.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Hull Self-Repair / Regeneration Texture (Bioships, audio_gaps.md #28)
+ * Microscopic organic tissue knit and chitin regrowth: granular
+ * amplitude-modulated pink noise (8-24 Hz modulation) with wet low
+ * regenerative pops. One ~1.8 s regeneration burst.
+ */
+ synthesizeHullRegenBurst() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.8;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', duration + 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(650, now);
+ bp.frequency.linearRampToValueAtTime(1600, now + duration);
+ bp.Q.setValueAtTime(2.0, now);
+
+ const modEnv = ctx.createGain();
+ modEnv.gain.setValueAtTime(0.16, now);
+
+ // Rapid scabbing modulation (~13 Hz) over the wet tissue bed
+ const lfo = ctx.createOscillator();
+ lfo.type = 'square';
+ lfo.frequency.setValueAtTime(9 + Math.random() * 8, now);
+ const lfoDepth = ctx.createGain();
+ lfoDepth.gain.setValueAtTime(0.12, now);
+ lfo.connect(lfoDepth);
+ lfoDepth.connect(modEnv.gain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.2, now + 0.25);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(modEnv);
+ modEnv.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ lfo.start(now);
+ lfo.stop(now + duration + 0.05);
+
+ // Wet bioplasmic pops (chitin knit)
+ const popCount = 2 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < popCount; i++) {
+ const popTime = now + 0.25 + Math.random() * (duration - 0.6);
+ const pop = ctx.createOscillator();
+ pop.type = 'sine';
+ pop.frequency.setValueAtTime(110 + Math.random() * 60, popTime);
+ pop.frequency.exponentialRampToValueAtTime(60, popTime + 0.06);
+ const popEnv = ctx.createGain();
+ popEnv.gain.setValueAtTime(0.001, popTime);
+ popEnv.gain.linearRampToValueAtTime(0.14, popTime + 0.008);
+ popEnv.gain.exponentialRampToValueAtTime(0.001, popTime + 0.07);
+ pop.connect(popEnv);
+ popEnv.connect(this.gainNode);
+ pop.start(popTime);
+ pop.stop(popTime + 0.08);
+ }
+ }
+
+ /**
+ * Ambient Loop: Periodic hull regeneration texture on Wraith hives
+ */
+ startHullRegen(minIntervalMs = 6000, maxIntervalMs = 13000) {
+ this._scheduleLoop('hullRegen', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeHullRegenBurst();
+ });
+ }
+
+ /**
+ * Belter Jury-Rigged Telemetry Jitter (Space Stations, audio_gaps.md #29)
+ * Rattle of loose relays, worn copper contactors and erratic voltage drops:
+ * noisy square relay pulses with micro-dropouts, contact-bounce double
+ * clicks and harsh metallic ticks on a jittered clock. One ~1.1 s cluster.
+ */
+ synthesizeBelterTelemetryJitter() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const eventCount = 3 + Math.floor(Math.random() * 5); // 3-7 events
+ let cursor = now + 0.05 + Math.random() * 0.2;
+
+ const relayPulse = (t) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ const baseFreq = 60 + Math.random() * 60;
+ osc.frequency.setValueAtTime(baseFreq, t);
+ osc.frequency.linearRampToValueAtTime(baseFreq * 0.8, t + 0.06);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(420, t);
+ const env = ctx.createGain();
+ // Erratic voltage: randomized micro-dropouts in the pulse body
+ env.gain.setValueAtTime(0.17, t);
+ env.gain.setValueAtTime(0.001, t + 0.012 + Math.random() * 0.015);
+ env.gain.setValueAtTime(0.14, t + 0.026 + Math.random() * 0.02);
+ env.gain.setValueAtTime(0.001, t + 0.05 + Math.random() * 0.025);
+ env.gain.setValueAtTime(0.1, t + 0.07);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
+ osc.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.11);
+ };
+
+ const contactBounce = (t) => {
+ // Worn contactor: double/triple click 1-3 ms apart
+ const clicks = 2 + (Math.random() < 0.35 ? 1 : 0);
+ for (let i = 0; i < clicks; i++) {
+ const ct = t + i * (0.001 + Math.random() * 0.002);
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(500 + Math.random() * 500, ct);
+ osc.frequency.exponentialRampToValueAtTime(120, ct + 0.008);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.14, ct);
+ env.gain.exponentialRampToValueAtTime(0.001, ct + 0.012);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(ct);
+ osc.stop(ct + 0.015);
+ }
+ };
+
+ const metallicTick = (t) => {
+ const buf = this.am.createNoiseBuffer('white', 0.05);
+ const noise = ctx.createBufferSource();
+ noise.buffer = buf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(400 + Math.random() * 1400, t);
+ bp.Q.setValueAtTime(6, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.09, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.02);
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(t);
+ noise.stop(t + 0.025);
+ };
+
+ for (let i = 0; i < eventCount; i++) {
+ const kind = Math.random();
+ if (kind < 0.4) relayPulse(cursor);
+ else if (kind < 0.75) contactBounce(cursor);
+ else metallicTick(cursor);
+ cursor += 0.08 + Math.random() * 0.22; // jittered clock drift
+ }
+ }
+
+ /**
+ * Ambient Loop: Intermittent jury-rigged telemetry on Belter stations
+ */
+ startBelterTelemetry(minIntervalMs = 1800, maxIntervalMs = 4500) {
+ this._scheduleLoop('belterTelemetry', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBelterTelemetryJitter();
+ });
+ }
+
+ /**
+ * True Hull Strain Moan (Deep Space, audio_gaps.md #30)
+ * Massive structural groan of stressed bulkheads flexing under gravitational
+ * shear: resonant bandpass slowly sweeping 40-220 Hz (high Q) over shaped
+ * brown noise with an asymmetric attack/decay. ~5.5 s.
+ */
+ synthesizeHullStrainMoan() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 5.5;
+
+ const noiseBuf = this.am.createNoiseBuffer('brown', duration + 0.3);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(48, now);
+ bp.frequency.exponentialRampToValueAtTime(210, now + 3.4);
+ bp.Q.setValueAtTime(10, now);
+
+ // Asymmetric envelope: slow menacing rise, long creaking decay
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.3, now + 0.9);
+ env.gain.linearRampToValueAtTime(0.22, now + 1.7);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Sporadic structural strain moans on haunted deep-space hulls
+ */
+ startHullStrainMoans(minIntervalMs = 16000, maxIntervalMs = 40000) {
+ this._scheduleLoop('hullStrainMoans', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeHullStrainMoan();
+ });
+ }
+
+ /**
+ * Icarus I Distress Beacon (Deep Space, audio_gaps.md #31)
+ * Eerie hypnotic modal arpeggio echoing from the ghost ship: a 4-note
+ * modal sine sequence through a long feedback delay with soft lowpass
+ * dampening. ~7 s including delay tail.
+ */
+ synthesizeIcarusBeacon() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const notes = [220.0, 261.63, 329.63, 440.0]; // A minor modal beacon
+
+ const delay = ctx.createDelay(1.2);
+ delay.delayTime.setValueAtTime(0.45, now);
+ const feedback = ctx.createGain();
+ feedback.gain.setValueAtTime(0.35, now);
+ const dampen = ctx.createBiquadFilter();
+ dampen.type = 'lowpass';
+ dampen.frequency.setValueAtTime(2200, now);
+ const wet = ctx.createGain();
+ wet.gain.setValueAtTime(0.5, now);
+
+ delay.connect(feedback);
+ feedback.connect(dampen);
+ dampen.connect(delay);
+ delay.connect(wet);
+ wet.connect(this.gainNode);
+
+ notes.forEach((freq, idx) => {
+ const t = now + idx * 0.95;
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.17, t + 0.05);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.85);
+ osc.connect(env);
+ env.connect(this.gainNode);
+ env.connect(delay);
+ osc.start(t);
+ osc.stop(t + 0.9);
+ });
+ }
+
+ /**
+ * Ambient Loop: Periodic distress beacon arpeggio while Icarus II is engaged
+ */
+ startIcarusBeacon(minIntervalMs = 22000, maxIntervalMs = 40000) {
+ this._scheduleLoop('icarusBeacon', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeIcarusBeacon();
+ });
+ }
+
+ /**
+ * Extradimensional Psychic Static Whisper (Deep Space, audio_gaps.md #32)
+ * Menacing psychoacoustic burst: multi-formant filtered white noise with
+ * randomized micro-envelopes evoking whispered vowels (800-2400 Hz).
+ */
+ synthesizeVoidWhisper() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 2.2;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', duration + 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.12, now + 0.4);
+ env.gain.linearRampToValueAtTime(0.09, now + 1.3);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // Three parallel whispered-vowel formants with independent wobbles
+ const formants = [
+ { center: 700, q: 7, wobbleHz: 0.8, wobbleAmt: 160 },
+ { center: 1250, q: 8, wobbleHz: 1.1, wobbleAmt: 280 },
+ { center: 2200, q: 10, wobbleHz: 1.6, wobbleAmt: 420 }
+ ];
+ formants.forEach((formant) => {
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(formant.center, now);
+ bp.Q.setValueAtTime(formant.q, now);
+
+ const wobble = ctx.createOscillator();
+ wobble.type = 'sine';
+ wobble.frequency.setValueAtTime(formant.wobbleHz, now);
+ const wobbleDepth = ctx.createGain();
+ wobbleDepth.gain.setValueAtTime(formant.wobbleAmt, now);
+ wobble.connect(wobbleDepth);
+ wobbleDepth.connect(bp.frequency);
+
+ const breath = ctx.createGain();
+ breath.gain.setValueAtTime(0.3, now);
+ // Randomized micro-envelopes: syllable-like gulps of the formant
+ const gulpCount = 3 + Math.floor(Math.random() * 3);
+ for (let i = 0; i < gulpCount; i++) {
+ const gt = now + 0.15 + Math.random() * (duration - 0.55);
+ breath.gain.setValueAtTime(0.12, gt);
+ breath.gain.linearRampToValueAtTime(0.3 + Math.random() * 0.18, gt + 0.04);
+ breath.gain.linearRampToValueAtTime(0.05, gt + 0.12 + Math.random() * 0.1);
+ }
+
+ noise.connect(bp);
+ bp.connect(breath);
+ breath.connect(env);
+
+ wobble.start(now);
+ wobble.stop(now + duration + 0.05);
+ });
+
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Ambient Loop: Psychic whisper bursts on haunted deep-space hulls
+ */
+ startVoidWhispers(minIntervalMs = 15000, maxIntervalMs = 32000) {
+ this._scheduleLoop('voidWhispers', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeVoidWhisper();
+ });
+ }
+
+ /**
+ * Beryllium Sphere Resonant Thrum (Comedy, audio_gaps.md #33)
+ * Deep glassy crystalline reactor hum: dual pure sines (65 + 195 Hz third
+ * harmonic) with slow beating detune and a subtle comb-filter ring.
+ * One ~10 s swell phrasing.
+ */
+ synthesizeBerylliumThrumSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 10.0;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.16, now + 4.0);
+ env.gain.linearRampToValueAtTime(0.14, now + 6.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // Comb ring: short 5.1 ms reflection
+ const comb = ctx.createDelay(0.05);
+ comb.delayTime.setValueAtTime(0.0051, now);
+ const combFb = ctx.createGain();
+ combFb.gain.setValueAtTime(0.45, now);
+ comb.connect(combFb);
+ combFb.connect(comb);
+ const combWet = ctx.createGain();
+ combWet.gain.setValueAtTime(0.6, now);
+ comb.connect(combWet);
+ combWet.connect(env);
+
+ const partials = [
+ { freq: 65, amp: 0.55 },
+ { freq: 65.3, amp: 0.3 }, // slow 0.3 Hz beating partner
+ { freq: 195, amp: 0.3 }
+ ];
+ partials.forEach((partial) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(partial.freq, now);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(partial.amp, now);
+ osc.connect(g);
+ g.connect(env);
+ g.connect(comb);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+ }
+
+ /**
+ * Ambient Loop: Beryllium sphere thrum while the NSEA Protector is engaged
+ */
+ startBerylliumThrum(minIntervalMs = 9000, maxIntervalMs = 16000) {
+ this._scheduleLoop('berylliumThrum', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBerylliumThrumSwell();
+ });
+ }
+
+ /**
+ * Omega-13 Temporal Capacitor Whine (Comedy, audio_gaps.md #34)
+ * Tremendous temporal capacitor power buildup: deep 40 Hz sub-bass swelling
+ * exponentially to 3200 Hz over 3 seconds, culminating in a wide
+ * white-noise discharge pop.
+ */
+ synthesizeOmega13Whine() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 3.1;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(40, now);
+ osc.frequency.exponentialRampToValueAtTime(3200, now + 3.0);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.exponentialRampToValueAtTime(0.5, now + 2.7);
+ env.gain.setValueAtTime(0.5, now + 2.95);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 3.2);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + duration + 0.1);
+
+ // Dimensional discharge pop (wide white-noise transient)
+ const popTime = now + 3.0;
+ const popBuf = this.am.createNoiseBuffer('white', 0.3);
+ const pop = ctx.createBufferSource();
+ pop.buffer = popBuf;
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(2000, popTime);
+ const popEnv = ctx.createGain();
+ popEnv.gain.setValueAtTime(0.001, popTime);
+ popEnv.gain.linearRampToValueAtTime(0.42, popTime + 0.008);
+ popEnv.gain.exponentialRampToValueAtTime(0.001, popTime + 0.16);
+ pop.connect(hp);
+ hp.connect(popEnv);
+ popEnv.connect(this.gainNode);
+ pop.start(popTime);
+ pop.stop(popTime + 0.18);
+ }
+
+ /**
+ * Repulsorlift Engine Drone (Outlaw, audio_gaps.md #35)
+ * Quintessential anti-gravity vehicle wash: dual detuned sines (68/72 Hz)
+ * through an asymmetric overdrive waveshaper, lowpass-filtered at 280 Hz.
+ * One ~10 s wash phrasing.
+ */
+ synthesizeRepulsorliftDrone() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 10.0;
+
+ let shaperOut = null;
+ if (ctx.createWaveShaper) {
+ const shaper = ctx.createWaveShaper();
+ shaper.oversample = '2x';
+ const curve = new Float32Array(1024);
+ for (let i = 0; i < 1024; i++) {
+ const x = (i / 512) - 1; // -1..1
+ curve[i] = Math.tanh(2.4 * x); // asymmetric-ish saturation
+ }
+ shaper.curve = curve;
+ shaperOut = shaper;
+ }
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(280, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.2, now + 2.6);
+ env.gain.linearRampToValueAtTime(0.17, now + 7.2);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ if (shaperOut) shaperOut.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+
+ [68, 72].forEach((freq) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(freq, now);
+ osc.connect(shaperOut || lp);
+ osc.start(now);
+ osc.stop(now + duration + 0.05);
+ });
+ }
+
+ /**
+ * Ambient Loop: Repulsorlift wash while the Marauder is engaged
+ */
+ startRepulsorliftDrone(intervalMs = 13000) {
+ this._scheduleLoop('repulsorliftDrone', intervalMs, intervalMs, () => {
+ this.synthesizeRepulsorliftDrone();
+ });
+ }
+
+ /**
+ * Converted Marine Trawler Engine Chug (Outlaw, audio_gaps.md #36)
+ * Rhythmic heavy diesel-like piston strokes: low-frequency square pulses
+ * (4-8 Hz cadence) through an 80 Hz resonant lowpass with mechanical
+ * piston wheeze. One cluster of 2-4 strokes.
+ */
+ synthesizeBebopChugStroke() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const strokes = 2 + Math.floor(Math.random() * 3); // 2-4 strokes
+
+ const stroke = (t, peak) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(45 + Math.random() * 25, t);
+ osc.frequency.linearRampToValueAtTime(38, t + 0.3);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(80, t);
+ lp.Q.setValueAtTime(3, t);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 0.02);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 0.42);
+ osc.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + 0.45);
+
+ // Mechanical piston wheeze
+ const wheezeBuf = this.am.createNoiseBuffer('pink', 0.3);
+ const wheeze = ctx.createBufferSource();
+ wheeze.buffer = wheezeBuf;
+ const wheezeBp = ctx.createBiquadFilter();
+ wheezeBp.type = 'bandpass';
+ wheezeBp.frequency.setValueAtTime(380, t);
+ wheezeBp.Q.setValueAtTime(1.6, t);
+ const wheezeEnv = ctx.createGain();
+ wheezeEnv.gain.setValueAtTime(0.001, t + 0.02);
+ wheezeEnv.gain.linearRampToValueAtTime(0.05, t + 0.08);
+ wheezeEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
+ wheeze.connect(wheezeBp);
+ wheezeBp.connect(wheezeEnv);
+ wheezeEnv.connect(this.gainNode);
+ wheeze.start(t);
+ wheeze.stop(t + 0.32);
+ };
+
+ for (let i = 0; i < strokes; i++) {
+ stroke(now + i * (0.34 + Math.random() * 0.2), 0.3 - i * 0.04);
+ }
+ }
+
+ /**
+ * Ambient Loop: Rhythmic diesel chug while the Bebop is engaged
+ */
+ startBebopChug(minIntervalMs = 2200, maxIntervalMs = 4200) {
+ this._scheduleLoop('bebopChug', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeBebopChugStroke();
+ });
+ }
+
+ /**
+ * Centrifugal Habitat Carousel Motor Groan (Military, audio_gaps.md #37)
+ * Deep rotational strain of a kilometer-long rotating drum: sub-audible
+ * 16 Hz rotational pair with bearing harmonics, modulated by a single
+ * ~1 RPM (0.0167 Hz) revolution LFO. One 58 s rotation pass.
+ */
+ synthesizeCarouselGroanCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 58.0;
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.55, now + 8.0);
+ env.gain.linearRampToValueAtTime(0.5, now + 50.0);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+ env.connect(this.gainNode);
+
+ // One rotation per ~60 s: slow strain swell and release
+ const rotation = ctx.createOscillator();
+ rotation.type = 'sine';
+ rotation.frequency.setValueAtTime(1 / 60, now);
+ const rotationDepth = ctx.createGain();
+ rotationDepth.gain.setValueAtTime(0.38, now);
+ rotation.connect(rotationDepth);
+ rotationDepth.connect(env.gain);
+
+ const partials = [
+ { freq: 16, amp: 0.55 },
+ { freq: 16.04, amp: 0.32 }, // rotational beating pair
+ { freq: 48, amp: 0.22 }, // structural harmonic
+ { freq: 96, amp: 0.07 } // bearing hum harmonic
+ ];
+ partials.forEach((partial) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(partial.freq, now);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(partial.amp, now);
+ osc.connect(g);
+ g.connect(env);
+ osc.start(now);
+ osc.stop(now + duration + 0.1);
+ });
+
+ rotation.start(now);
+ rotation.stop(now + duration + 0.1);
+ }
+
+ /**
+ * Ambient Loop: Carousel rotation groan while the Agamemnon is engaged
+ * (one 58 s pass every 60 s; the 2 s gap reads as a rotation splice)
+ */
+ startCarouselGroan(intervalMs = 60000) {
+ this._scheduleLoop('carouselGroan', intervalMs, intervalMs, () => {
+ this.synthesizeCarouselGroanCycle();
+ });
+ }
+
+ /**
+ * Slipstream Transition Surge (Military, audio_gaps.md #38)
+ * Ship tearing through an exotic slipstream: dual sweeping bandpass surges
+ * (200 -> 2400 Hz) with escalating resonance Q and a comb-filter shimmer
+ * tail. ~4.2 s.
+ */
+ synthesizeSlipstreamSurge() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Comb shimmer tail bus
+ const shimmer = ctx.createDelay(0.5);
+ shimmer.delayTime.setValueAtTime(0.12, now);
+ const shimmerFb = ctx.createGain();
+ shimmerFb.gain.setValueAtTime(0.3, now);
+ const shimmerLp = ctx.createBiquadFilter();
+ shimmerLp.type = 'lowpass';
+ shimmerLp.frequency.setValueAtTime(4000, now);
+ shimmer.connect(shimmerFb);
+ shimmerFb.connect(shimmerLp);
+ shimmerLp.connect(shimmer);
+ const shimmerWet = ctx.createGain();
+ shimmerWet.gain.setValueAtTime(0.5, now);
+ shimmer.connect(shimmerWet);
+ shimmerWet.connect(this.gainNode);
+
+ const surge = (offset, peak) => {
+ const t = now + offset;
+ const osc = ctx.createOscillator();
+ osc.type = 'sawtooth';
+ osc.frequency.setValueAtTime(200, t);
+ osc.frequency.exponentialRampToValueAtTime(2400, t + 2.6);
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(200, t);
+ bp.frequency.exponentialRampToValueAtTime(2400, t + 2.6);
+ bp.Q.setValueAtTime(2, t);
+ bp.Q.linearRampToValueAtTime(10, t + 2.6);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(peak, t + 1.4);
+ env.gain.setValueAtTime(peak, t + 2.7);
+ env.gain.exponentialRampToValueAtTime(0.001, t + 3.8);
+
+ osc.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ env.connect(shimmer);
+ osc.start(t);
+ osc.stop(t + 4.0);
+ };
+
+ surge(0, 0.22);
+ surge(0.3, 0.16);
+ }
+
+ /**
+ * Viper Pilot Oxygen Regulator Demand Valve (Military, audio_gaps.md #39)
+ * Pulsing rebreather of a Viper pilot mid-combat: a sharp 15 ms mechanical
+ * diaphragm click preceding pulsed highpass-filtered white noise breath
+ * (1.2-4.5 kHz, ~1.1 s). One breath cycle.
+ */
+ synthesizeOxygenRegulatorCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Sharp mechanical diaphragm click
+ const click = ctx.createOscillator();
+ click.type = 'triangle';
+ click.frequency.setValueAtTime(1800, now);
+ click.frequency.exponentialRampToValueAtTime(600, now + 0.015);
+ const clickEnv = ctx.createGain();
+ clickEnv.gain.setValueAtTime(0.3, now);
+ clickEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.02);
+ click.connect(clickEnv);
+ clickEnv.connect(this.gainNode);
+ click.start(now);
+ click.stop(now + 0.025);
+
+ // Regulated airflow breath
+ const noiseBuf = this.am.createNoiseBuffer('white', 1.3);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(1200, now + 0.02);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(4500, now + 0.02);
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now + 0.02);
+ env.gain.linearRampToValueAtTime(0.13, now + 0.45);
+ env.gain.linearRampToValueAtTime(0.09, now + 0.62);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 1.15);
+ noise.connect(hp);
+ hp.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now + 0.02);
+ noise.stop(now + 1.2);
+ }
+
+ /**
+ * Ambient Loop: Rhythmic oxygen demand breathing while a Viper cockpit
+ * is engaged
+ */
+ startOxygenRegulator(minIntervalMs = 3400, maxIntervalMs = 5200) {
+ this._scheduleLoop('oxygenRegulator', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeOxygenRegulatorCycle();
+ });
+ }
+
+ /**
+ * Condensation Pipe Drip & Expansion Tick (Industrial, audio_gaps.md #40)
+ * Lonely water droplet pinging inside a kilometres-long cargo hauler:
+ * high-Q sine pings (1400-2600 Hz) with fast exponential decay on a
+ * randomized clock. One drip.
+ */
+ synthesizePipeDrip() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const decay = 0.01 + Math.random() * 0.015;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ const freq = 1400 + Math.random() * 1200;
+ osc.frequency.setValueAtTime(freq, now);
+ osc.frequency.exponentialRampToValueAtTime(freq * 0.92, now + decay);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.1 + Math.random() * 0.2, now + 0.001);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + decay + 0.01);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(now);
+ osc.stop(now + decay + 0.03);
+ }
+
+ /**
+ * Ambient Loop: Sporadic condensation drips while the Nostromo is engaged
+ */
+ startPipeDrips(minIntervalMs = 500, maxIntervalMs = 3500) {
+ this._scheduleLoop('pipeDrips', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizePipeDrip();
+ });
+ }
+
+ /**
+ * High-Pressure Steam / Boiler Venting (Industrial, audio_gaps.md #41)
+ * Continuous hot-vent hiss of a steam-era mining vessel: bandpass-filtered
+ * white noise (800-2800 Hz) with a slow amplitude swell and random
+ * micro-flutter. One ~9 s vent pass.
+ */
+ synthesizeSteamVentSwell() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 9.0;
+
+ // Looping noise bed (short buffer, sustained vent)
+ const noiseBuf = this.am.createNoiseBuffer('white', 4.0);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ noise.loop = true;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(1500, now);
+ bp.Q.setValueAtTime(0.7, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.17, now + 3.0);
+ env.gain.linearRampToValueAtTime(0.15, now + 5.5);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ // Boiler pressure micro-flutter (~6-9 Hz jitter)
+ const flutter = ctx.createOscillator();
+ flutter.type = 'sine';
+ flutter.frequency.setValueAtTime(6 + Math.random() * 3, now);
+ const flutterDepth = ctx.createGain();
+ flutterDepth.gain.setValueAtTime(0.04, now);
+ flutter.connect(flutterDepth);
+ flutterDepth.connect(env.gain);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + duration + 0.1);
+ flutter.start(now);
+ flutter.stop(now + duration + 0.1);
+ }
+
+ /**
+ * Ambient Loop: Boiler vent passes while Serenity-class and Starbug
+ * presets are engaged
+ */
+ startSteamVent(minIntervalMs = 6000, maxIntervalMs = 10000) {
+ this._scheduleLoop('steamVent', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeSteamVentSwell();
+ });
+ }
+
+ /**
+ * Crash-Couch Hydraulic Gimbal Strain (Industrial, audio_gaps.md #42)
+ * Pilot's couch swinging into launch position: FM triangle carrier
+ * (140 Hz / 35 Hz modulator) through lowpass damping that tracks the
+ * acceleration strain. ~1.9 s.
+ */
+ synthesizeCrashCouchGimbal() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.9;
+
+ const layer = (offset, carrierFreq, modFreq) => {
+ const t = now + offset;
+ const carrier = ctx.createOscillator();
+ carrier.type = 'triangle';
+ carrier.frequency.setValueAtTime(carrierFreq, t);
+
+ const mod = ctx.createOscillator();
+ mod.type = 'sine';
+ mod.frequency.setValueAtTime(modFreq, t);
+ const modDepth = ctx.createGain();
+ modDepth.gain.setValueAtTime(carrierFreq * 4, t);
+ mod.connect(modDepth);
+ modDepth.connect(carrier.frequency);
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(300, t);
+ lp.frequency.linearRampToValueAtTime(900, t + 0.5);
+ lp.frequency.exponentialRampToValueAtTime(350, t + duration - offset);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.24, t + 0.55);
+ env.gain.linearRampToValueAtTime(0.2, t + 1.0);
+ env.gain.exponentialRampToValueAtTime(0.0001, t + duration - offset);
+
+ carrier.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+ carrier.start(t);
+ carrier.stop(t + (duration - offset) + 0.05);
+ mod.start(t);
+ mod.stop(t + (duration - offset) + 0.05);
+ };
+
+ layer(0, 140, 35);
+ layer(0.42, 152, 38);
+ }
+
+ /**
+ * HAL 9000 Breathing Loop (Retro Future, audio_gaps.md #43)
+ * Eerie slow respiration of a quiet, observant ship computer: rhythmic
+ * bandpass-filtered pink noise (450-1100 Hz) with a gentle 3.5 s
+ * inhalation/exhalation envelope. One breath.
+ */
+ synthesizeHalBreathCycle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const inhale = 1.3;
+ const exhale = 2.2;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', 4.0);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+ noise.loop = true;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(700, now);
+ bp.Q.setValueAtTime(1.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.085, now + inhale);
+ env.gain.exponentialRampToValueAtTime(0.001, now + inhale + exhale);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + inhale + exhale + 0.05);
+ }
+
+ /**
+ * Ambient Loop: Continuous respiration while the Discovery One is engaged
+ */
+ startHalBreathing(intervalMs = 3500) {
+ this._scheduleLoop('halBreathing', intervalMs, intervalMs, () => {
+ this.synthesizeHalBreathCycle();
+ });
+ }
+
+ /**
+ * Death Blossom Energy Surge Ramp (Retro Future, audio_gaps.md #44)
+ * Fearsome gunstar overdrive: cascaded sawtooth exponential ramp
+ * (200 Hz -> 8000 Hz over 2.5 s) with rising overdrive, cutting off
+ * abruptly at full bloom.
+ */
+ synthesizeDeathBlossomSurge() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const rampDur = 2.5;
+
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(400, now);
+ hp.frequency.exponentialRampToValueAtTime(4000, now + rampDur);
+
+ const hp2 = ctx.createBiquadFilter();
+ hp2.type = 'highpass';
+ hp2.frequency.setValueAtTime(200, now);
+ hp2.frequency.exponentialRampToValueAtTime(2000, now + rampDur);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.32, now + rampDur);
+ env.gain.setValueAtTime(0.32, now + rampDur + 0.03);
+ env.gain.exponentialRampToValueAtTime(0.001, now + rampDur + 0.4);
+
+ hp.connect(hp2);
+ hp2.connect(env);
+ env.connect(this.gainNode);
+
+ // Cascaded overdriven saws with staggered ignition
+ [0, 0.15, 0.3].forEach((offset, idx) => {
+ const t = now + offset;
+ const osc = ctx.createOscillator();
+ osc.type = 'sawtooth';
+ osc.frequency.setValueAtTime(200, t);
+ osc.frequency.exponentialRampToValueAtTime(8000, t + rampDur - offset);
+ const g = ctx.createGain();
+ g.gain.setValueAtTime(0.33 - idx * 0.08, t);
+ osc.connect(g);
+ g.connect(hp);
+ osc.start(t);
+ osc.stop(t + (rampDur - offset) + 0.45);
+ });
+
+ // Abrupt bloom transient
+ const burstTime = now + rampDur;
+ const burstBuf = this.am.createNoiseBuffer('white', 0.25);
+ const burst = ctx.createBufferSource();
+ burst.buffer = burstBuf;
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(3500, burstTime);
+ bp.Q.setValueAtTime(1.5, burstTime);
+ const burstEnv = ctx.createGain();
+ burstEnv.gain.setValueAtTime(0.001, burstTime);
+ burstEnv.gain.linearRampToValueAtTime(0.3, burstTime + 0.01);
+ burstEnv.gain.exponentialRampToValueAtTime(0.001, burstTime + 0.22);
+ burst.connect(bp);
+ bp.connect(burstEnv);
+ burstEnv.connect(this.gainNode);
+ burst.start(burstTime);
+ burst.stop(burstTime + 0.25);
+ }
+
+ /**
+ * Victorian Steam Piston Chug (Retro Future, audio_gaps.md #45)
+ * The steam-age skeleton of the Cygnus: lowpass-filtered noise bursts
+ * synchronized with heavy 40 Hz triangle thumps and a metallic slapback.
+ * One cluster of 2-3 stroke cycles.
+ */
+ synthesizeCygnusPistonChug() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const strokes = 2 + Math.floor(Math.random() * 2); // 2-3 strokes
+
+ const slap = ctx.createDelay(0.6);
+ slap.delayTime.setValueAtTime(0.35, now);
+ const slapFb = ctx.createGain();
+ slapFb.gain.setValueAtTime(0.3, now);
+ const slapWet = ctx.createGain();
+ slapWet.gain.setValueAtTime(0.4, now);
+ slap.connect(slapFb);
+ slapFb.connect(slap);
+ slap.connect(slapWet);
+ slapWet.connect(this.gainNode);
+
+ const stroke = (t, peak) => {
+ // Heavy piston body thump
+ const thump = ctx.createOscillator();
+ thump.type = 'triangle';
+ thump.frequency.setValueAtTime(40, t);
+ thump.frequency.linearRampToValueAtTime(32, t + 0.4);
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(120, t);
+ const thumpEnv = ctx.createGain();
+ thumpEnv.gain.setValueAtTime(0.001, t);
+ thumpEnv.gain.linearRampToValueAtTime(peak, t + 0.02);
+ thumpEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.45);
+ thump.connect(lp);
+ lp.connect(thumpEnv);
+ thumpEnv.connect(this.gainNode);
+ thumpEnv.connect(slap);
+ thump.start(t);
+ thump.stop(t + 0.5);
+
+ // Synchronized steam exhaust puff
+ const puffBuf = this.am.createNoiseBuffer('white', 0.16);
+ const puff = ctx.createBufferSource();
+ puff.buffer = puffBuf;
+ const puffLp = ctx.createBiquadFilter();
+ puffLp.type = 'lowpass';
+ puffLp.frequency.setValueAtTime(500, t);
+ const puffEnv = ctx.createGain();
+ puffEnv.gain.setValueAtTime(0.001, t + 0.01);
+ puffEnv.gain.linearRampToValueAtTime(0.13, t + 0.03);
+ puffEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
+ puff.connect(puffLp);
+ puffLp.connect(puffEnv);
+ puffEnv.connect(this.gainNode);
+ puff.start(t);
+ puff.stop(t + 0.17);
+ };
+
+ for (let i = 0; i < strokes; i++) {
+ stroke(now + i * (0.5 + Math.random() * 0.25), 0.26 - i * 0.03);
+ }
+ }
+
+ /**
+ * Ambient Loop: Steam piston chug while the Cygnus is engaged
+ */
+ startCygnusChug(minIntervalMs = 1800, maxIntervalMs = 3200) {
+ this._scheduleLoop('cygnusChug', minIntervalMs, maxIntervalMs, () => {
+ this.synthesizeCygnusPistonChug();
+ });
+ }
+
stopAllLoops() {
this.stopMedicalMonitor();
this.stopStationSparks();
+ Object.keys(this.loopTimers).forEach((key) => {
+ this._stopLoop(key);
+ });
}
}
diff --git a/js/config.js b/js/config.js
index a467daa..78cfbe7 100644
--- a/js/config.js
+++ b/js/config.js
@@ -166,8 +166,10 @@ const UniverseRegistry = {
{ id: 'btn-door-swish', label: 'DOOR PNEUMATIC SWISH' },
{ id: 'btn-bosun-whistle', label: "BOSUN'S PIPE WHISTLE" },
{ id: 'btn-medical-monitor', label: 'SICKBAY ECG MONITOR' },
- { id: 'btn-chirp-single', label: 'LCARS SINGLE CHIRP' },
- { id: 'btn-chirp-ack', label: 'LCARS DATA ACK' }
+ { id: 'btn-cardassian-door', label: 'CARDASSIAN BULKHEAD DOOR' },
+ { id: 'btn-turbolift', label: 'TURBOLIFT WHOOSH' },
+ { id: 'btn-replicator', label: 'REPLICATOR SHIMMER' },
+ { id: 'btn-chirp-single', label: 'LCARS SINGLE CHIRP' }
],
presets: StarshipPresets
},
@@ -194,7 +196,9 @@ const UniverseRegistry = {
{ id: 'btn-tardis-door', label: 'POLICE BOX WOODEN DOOR' },
{ id: 'btn-fast-return', label: 'FAST-RETURN LEVER' },
{ id: 'btn-demat-switch', label: 'DEMAT SWITCH' },
- { id: 'btn-telepathic', label: 'TELEPATHIC CHIME' }
+ { id: 'btn-telepathic', label: 'TELEPATHIC CHIME' },
+ { id: 'btn-scanner-activate', label: 'SCANNER ACTIVATION' },
+ { id: 'btn-type-clatter', label: 'CONSOLE TYPE CLATTER' }
],
presets: {
'tardis-1963': {
@@ -303,9 +307,9 @@ const UniverseRegistry = {
soundboard: [
{ id: 'btn-geiger', label: 'GEIGER COUNTER' },
{ id: 'btn-dock-clamp', label: 'HYDRAULIC CLAMPS' },
- { id: 'btn-relay-click', label: 'M/TH/UR TELETYPE' },
- { id: 'btn-switch-pop', label: 'HEAVY SOLENOID' },
- { id: 'btn-pneumatic-door', label: 'PNEUMATIC SEAL PURGE' },
+ { id: 'btn-pipe-drips', label: 'CONDENSATION DRIPS' },
+ { id: 'btn-steam-vent', label: 'BOILER VENT HISS' },
+ { id: 'btn-crash-couch', label: 'CRASH-COUCH GIMBAL' },
{ id: 'btn-air-handler', label: 'HVAC AIR HANDLER' }
],
presets: {
@@ -409,12 +413,12 @@ const UniverseRegistry = {
{ id: 'btn-bio-defense', label: 'BIO-DEFENSE BURST', type: 'biodefense' }
],
soundboard: [
- { id: 'btn-telepathic-chime', label: 'NEURAL TWITCH' },
- { id: 'btn-fast-return', label: 'VASCULAR PUMP' },
- { id: 'btn-demat-switch', label: 'CHITIN CREAK' },
- { id: 'btn-chirp-sweep', label: 'BIOPLASMIC HISS' },
- { id: 'btn-sonic', label: 'SYMBIOTE VOCALIZATION' },
- { id: 'btn-relay-click', label: 'MEMBRANE FLUTTER' }
+ { id: 'btn-neural-bond', label: 'NEURAL-BOND SWELL' },
+ { id: 'btn-fast-return', label: 'VASCULAR PUMP' },
+ { id: 'btn-demat-switch', label: 'CHITIN CREAK' },
+ { id: 'btn-chirp-sweep', label: 'BIOPLASMIC HISS' },
+ { id: 'btn-vorlon-song', label: 'VORLON SINGING RESONANCE' },
+ { id: 'btn-hull-regen', label: 'HULL REGEN TEXTURE' }
],
presets: {
'bio-moya': {
@@ -497,12 +501,12 @@ const UniverseRegistry = {
{ id: 'btn-hal-override', label: 'HAL 9000 OVERRIDE', type: 'hal' }
],
soundboard: [
- { id: 'btn-hal-chime', label: 'HAL LOGIC CONFIRM' },
- { id: 'btn-relay-click', label: 'TAPE REEL SPOOL' },
+ { id: 'btn-hal-breath', label: 'HAL RESPIRATION' },
+ { id: 'btn-death-blossom',label: 'DEATH BLOSSOM SURGE' },
+ { id: 'btn-cygnus-chug', label: 'STEAM PISTON CHUG' },
{ id: 'btn-chirp-single', label: 'ASTROGATOR BEAT' },
{ id: 'btn-chirp-sweep', label: 'ANALOG GLISSANDO' },
- { id: 'btn-fast-return', label: 'MAGNETIC TAPE LATCH' },
- { id: 'btn-dock-clamp', label: 'PNEUMATIC SEAL PURGE' }
+ { id: 'btn-relay-click', label: 'TAPE REEL SPOOL' }
],
presets: {
'ret-jupiter-2': {
@@ -605,12 +609,12 @@ const UniverseRegistry = {
{ id: 'btn-flak', label: 'FLAK BARRAGE', type: 'flak' }
],
soundboard: [
- { id: 'btn-dradis-ping', label: 'DRADIS SCAN PING' },
- { id: 'btn-switch-pop', label: 'WEAPONS ARMED' },
- { id: 'btn-dock-clamp', label: 'BULKHEAD SEAL' },
- { id: 'btn-chirp-sweep', label: 'RADAR TARGET LOCK' },
- { id: 'btn-fast-return', label: 'TURBO-THRUSTER SWELL' },
- { id: 'btn-air-handler', label: 'RCS THRUSTER BURST' }
+ { id: 'btn-dradis-ping', label: 'DRADIS SCAN PING' },
+ { id: 'btn-switch-pop', label: 'WEAPONS ARMED' },
+ { id: 'btn-dock-clamp', label: 'BULKHEAD SEAL' },
+ { id: 'btn-carousel-groan', label: 'CAROUSEL MOTOR GROAN' },
+ { id: 'btn-slipstream', label: 'SLIPSTREAM SURGE' },
+ { id: 'btn-oxygen-reg', label: 'O2 DEMAND VALVE' }
],
presets: {
'mil-sulaco': {
@@ -703,12 +707,12 @@ const UniverseRegistry = {
{ id: 'btn-solar-swell', label: 'SOLAR SHIELD SWELL', type: 'solar' }
],
soundboard: [
- { id: 'btn-solar-roar', label: 'HEAT SHIELD ROAR' },
- { id: 'btn-cryo-sigh', label: 'CRYO POD SIGH' },
- { id: 'btn-orion-thump', label: 'ORION PULSE THUMP' },
- { id: 'btn-dock-clamp', label: 'HULL STRAIN MOAN' },
- { id: 'btn-telepathic-chime', label: 'VOID RESONANCE' },
- { id: 'btn-chirp-sweep', label: 'TACHYON SENSOR PING' }
+ { id: 'btn-solar-roar', label: 'HEAT SHIELD ROAR' },
+ { id: 'btn-cryo-sigh', label: 'CRYO POD SIGH' },
+ { id: 'btn-orion-thump', label: 'ORION PULSE THUMP' },
+ { id: 'btn-hull-strain', label: 'TRUE HULL STRAIN MOAN' },
+ { id: 'btn-void-whisper',label: 'PSYCHIC VOID WHISPER' },
+ { id: 'btn-icarus-beacon', label: 'ICARUS I DISTRESS BEACON' }
],
presets: {
'deep-avalon': {
@@ -804,9 +808,9 @@ const UniverseRegistry = {
{ id: 'btn-tape-clunk', label: 'CASSETTE CLUNK & WHIRR' },
{ id: 'btn-grappler-servo', label: 'GRAPPLER SERVO MOTOR' },
{ id: 'btn-radio-static', label: 'RF SQUELCH STATIC BURST' },
- { id: 'btn-dock-clamp', label: 'FUEL LINE VALVE' },
- { id: 'btn-afterburner-snd',label: 'AFTERBURNER BOOST' },
- { id: 'btn-fast-return', label: 'TURBO JET BURN' }
+ { id: 'btn-repulsor-drone', label: 'REPULSORLIFT DRONE' },
+ { id: 'btn-bebop-chug', label: 'MARINE DIESEL CHUG' },
+ { id: 'btn-afterburner-snd',label: 'AFTERBURNER BOOST' }
],
presets: {
'out-betty': {
@@ -894,7 +898,7 @@ const UniverseRegistry = {
{ id: 'btn-commlock', label: 'COMMLOCK CALLING TONE' },
{ id: 'btn-sevas-spark', label: 'CONDUIT SPARK ARC' },
{ id: 'btn-station-ecg', label: 'CLINICAL VITAL (ECG)' },
- { id: 'btn-dock-clamp', label: 'DOCKING CLAMP LATCH' }
+ { id: 'btn-belter-telemetry', label: 'BELTER JURY-RIG TELEMETRY', span: 2 }
],
presets: {
'sta-babylon-5': {
@@ -981,8 +985,8 @@ const UniverseRegistry = {
{ id: 'btn-tea-machine', label: 'TEA DISPENSER GURGLE' },
{ id: 'btn-kettle-whistle', label: 'KETTLE STEAM WHISTLE' },
{ id: 'btn-plaid-alarm', label: 'LUDICROUS PLAID ALARM' },
- { id: 'btn-beryllium-pulse',label: 'BERYLLIUM CORE PULSE' },
- { id: 'btn-chirp-ack', label: 'ORVILLE ACK CHIRP' }
+ { id: 'btn-beryllium-thrum',label: 'BERYLLIUM SPHERE THRUM' },
+ { id: 'btn-omega-13', label: 'OMEGA-13 CAPACITOR WHINE' }
],
presets: {
'com-orville': {
diff --git a/sound_reference.md b/sound_reference.md
index c8eb392..9c18d7a 100644
--- a/sound_reference.md
+++ b/sound_reference.md
@@ -294,6 +294,18 @@ Every Starfleet preset in `StarshipPresets` is audited against the audio engine:
---
+## Star Trek Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #21–#23).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Cardassian bulkhead door | `synthesizeCardassianDoor` | `btn-cardassian-door` | — |
+| Turbolift whoosh | `synthesizeTurboliftWhoosh` | `btn-turbolift` | — |
+| Replicator shimmer | `synthesizeReplicatorShimmer` | `btn-replicator` | — |
+
+---
+
## Star Trek open questions
Deliberately unresolved rather than guessed:
@@ -486,6 +498,18 @@ Every TARDIS console room preset is audited against the audio engine:
---
+## Whoniverse Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #24–#25). Both remain standalone
+routines; `TelemetrySynth` `era: 'who'` routing is Phase 3 scope.
+
+| Element | Method (WhoniverseAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Scanner / monitor activation | `synthesizeScannerActivate` | `btn-scanner-activate` | — (flyback partial at 10.6 kHz) |
+| Console "type" input telemetry | `synthesizeTypeClatter` + `startConsoleTypeTelemetry` | `btn-type-clatter` | All `tardis-*` rooms, 2.8–6.5 s clatter clusters |
+
+---
+
## Whoniverse open questions
- **[GAP]** The cloister bell's real strike interval and who created the sound.
@@ -721,6 +745,18 @@ Item 1 dwarfs the rest in the same way the Whoniverse telemetry gap did: a
purpose-built organic discrete vocabulary — creaks, pulses, membrane flutters —
would do more for this universe's authenticity than any single new one-shot.
+## Bioships Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #26–#28).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Vorlon "singing" resonance | `synthesizeVorlonSingingSwell` + `startVorlonSong` | `btn-vorlon-song` | `bio-vorlon`, 11–18 s swell cadence |
+| Neural-bond swell | `synthesizeNeuralBondSwell` | `btn-neural-bond` | Also fires from the NEURAL PULSE universe event |
+| Hull regeneration texture | `synthesizeHullRegenBurst` + `startHullRegen` | `btn-hull-regen` | `bio-wraith`, 6–13 s cadence |
+
+---
+
## Bioships open questions
- **[GAP]** No sound design production information was found for The Lexx,
@@ -1194,6 +1230,17 @@ Ranked by impact and feasibility across the six station profiles:
---
+## Space Stations Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (row #29). Delivered as a standalone
+routine; `TelemetrySynth` `era: 'belter'` routing is Phase 3 scope.
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Belter jury-rigged telemetry | `synthesizeBelterTelemetryJitter` + `startBelterTelemetry` | `btn-belter-telemetry` (full-width) | `sta-ceres`, `sta-tycho`, 1.8–4.5 s jitter clusters |
+
+---
+
## Space Stations open questions
- **[GAP]** Whether *Space: 1999*’s transition from Main Mission (Season 1) to
@@ -1710,6 +1757,18 @@ Ranked by impact and feasibility across the seven vessel profiles:
---
+## Deep Space Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #30–#32).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| True hull strain moan | `synthesizeHullStrainMoan` + `startHullStrainMoans` | `btn-hull-strain` | `deep-event-horizon`, `deep-icarus-ii`, 16–40 s cadence |
+| Icarus I distress beacon | `synthesizeIcarusBeacon` + `startIcarusBeacon` | `btn-icarus-beacon` | `deep-icarus-ii`, 22–40 s cadence |
+| Psychic void whisper | `synthesizeVoidWhisper` + `startVoidWhispers` | `btn-void-whisper` | `deep-event-horizon`, `deep-nightflyer`, 15–32 s cadence |
+
+---
+
## Deep Space open questions
- **[GAP]** The specific synthesizer patches and filtering used by Glenn
@@ -2126,6 +2185,17 @@ Ranked by impact and feasibility across the five starship profiles:
---
+## Comedy & Adventure Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #33–#34).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Beryllium sphere resonant thrum | `synthesizeBerylliumThrumSwell` + `startBerylliumThrum` | `btn-beryllium-thrum` | `com-protector`, 9–16 s swell cadence |
+| Omega-13 temporal capacitor whine | `synthesizeOmega13Whine` | `btn-omega-13` | — |
+
+---
+
## Comedy & Adventure open questions
- **[GAP]** The specific synthesizer models and patch parameters used by Gary
@@ -2582,6 +2652,17 @@ Ranked by impact and feasibility across the six starship profiles:
---
+## Outlaw & Frontier Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #35–#36).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Repulsorlift engine drone | `synthesizeRepulsorliftDrone` + `startRepulsorliftDrone` | `btn-repulsor-drone` | `out-marauder`, 13 s wash cadence |
+| Marine trawler engine chug | `synthesizeBebopChugStroke` + `startBebopChug` | `btn-bebop-chug` | `out-bebop`, 2.2–4.2 s stroke clusters |
+
+---
+
## Outlaw & Frontier open questions
- **[GAP]** The specific mechanical source recorded by Christopher Boyes for
@@ -3175,6 +3256,18 @@ that are currently simulated by existing approximations:
---
+## Military Space Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #37–#39).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Carousel motor groan | `synthesizeCarouselGroanCycle` + `startCarouselGroan` | `btn-carousel-groan` | `mil-agamemnon`, 58 s rotation pass / 60 s |
+| Slipstream transition surge | `synthesizeSlipstreamSurge` | `btn-slipstream` | — |
+| O₂ demand valve | `synthesizeOxygenRegulatorCycle` + `startOxygenRegulator` | `btn-oxygen-reg` | `mil-viper`, 3.4–5.2 s breath cycles |
+
+---
+
## Military Space open questions
- **[GAP]** The exact acoustic recording sources used by Don Sharpe for the
@@ -3810,6 +3903,18 @@ production that are currently simulated by approximations:
---
+## Industrial Space Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #40–#42).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| Condensation pipe drips | `synthesizePipeDrip` + `startPipeDrips` | `btn-pipe-drips` | `ind-nostromo`, 0.5–3.5 s drip clock |
+| Steam / boiler venting | `synthesizeSteamVentSwell` + `startSteamVent` | `btn-steam-vent` | `ind-serenity`, `ind-starbug`, 6–10 s vent passes |
+| Crash-couch hydraulic gimbal | `synthesizeCrashCouchGimbal` | `btn-crash-couch` | — |
+
+---
+
## Industrial Space open questions
- **[GAP]** The exact mechanical typewriter and electromechanical relay sources
@@ -4434,6 +4539,18 @@ that are currently simulated by existing approximations:
---
+## Retro Future Phase 2 additions
+
+Implemented under audio_gaps.md Phase 2 (rows #43–#45).
+
+| Element | Method (ExpandedSciFiAudioSynth) | Soundboard | Ambient hook |
+| --- | --- | --- | --- |
+| HAL 9000 breathing loop | `synthesizeHalBreathCycle` + `startHalBreathing` | `btn-hal-breath` | `ret-discovery-one`, continuous 3.5 s breath cycle |
+| Death Blossom energy surge | `synthesizeDeathBlossomSurge` | `btn-death-blossom` | — |
+| Victorian steam piston chug | `synthesizeCygnusPistonChug` + `startCygnusChug` | `btn-cygnus-chug` | `ret-cygnus`, 1.8–3.2 s stroke clusters |
+
+---
+
## Retro Future open questions
- **[GAP]** The exact electronic audio generators and tape echo units used by