Implement Phase 3 audio gaps: complex multi-stage one-shots (transporter, flak, breech, docking, tape spooler, teletype)

This commit is contained in:
Claude
2026-09-04 09:40:23 -07:00
parent 42d6007445
commit 9d1bc7b743
3 changed files with 519 additions and 4 deletions
+20 -1
View File
@@ -669,10 +669,12 @@ document.addEventListener('DOMContentLoaded', () => {
expandedAudio.synthesizeHalChime();
break;
case 'ftl-jump':
case 'flak':
expandedAudio.synthesizeFtlJump();
visualizer.spawnWarpPulses();
break;
case 'flak':
expandedAudio.synthesizeFlakBarrageBurst();
break;
case 'singularity':
case 'gravity':
case 'rotation':
@@ -917,6 +919,23 @@ document.addEventListener('DOMContentLoaded', () => {
expandedAudio.synthesizeCygnusPistonChug();
break;
// Phase 3 (audio_gaps.md #48-#53)
case 'btn-transporter':
expandedAudio.synthesizeTransporterCycle();
break;
case 'btn-breech-clank':
expandedAudio.synthesizeBreechClank();
break;
case 'btn-dock-groan':
expandedAudio.synthesizeDockingGroan();
break;
case 'btn-tape-spooler':
expandedAudio.synthesizeTapeSpooler();
break;
case 'btn-teletype':
expandedAudio.synthesizeTeletypeChatterPhrase();
break;
default:
telemetry.synthesizeLCARSSingleChirp();
break;
+491
View File
@@ -5228,6 +5228,497 @@ class ExpandedSciFiAudioSynth {
});
}
/**
* Transporter Dematerialization / Rematerialization Cycle (audio_gaps.md #48)
* The classic shimmering beam-up wash: an additive array of 5 detuned sines
* (330-990 Hz over a 660 Hz base) each interlocked with its own 5-11 Hz AM
* LFO, plus a high bandpass noise shimmer gated by a 30 Hz LFO, all under a
* slow rise / mid dip / release master envelope. ~3.0 s.
*/
synthesizeTransporterCycle() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 3.0;
// Tonal shimmer master envelope (dip mid-cycle reads as the demat/remat split)
const masterEnv = ctx.createGain();
masterEnv.gain.setValueAtTime(0.001, now);
masterEnv.gain.linearRampToValueAtTime(0.15, now + 0.9);
masterEnv.gain.setValueAtTime(0.1, now + 1.35);
masterEnv.gain.linearRampToValueAtTime(0.17, now + 1.95);
masterEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
masterEnv.connect(this.gainNode);
const ratios = [0.5, 0.75, 1.0, 1.25, 1.5];
ratios.forEach((ratio, idx) => {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(660 * ratio, now);
osc.detune.setValueAtTime((idx % 2 ? 1 : -1) * (1.5 + Math.random() * 4.5), now);
// Per-voice interlocking amplitude modulation (staggered 5-11 Hz LFOs)
const amBase = 0.5 + Math.random() * 0.2;
const voiceEnv = ctx.createGain();
voiceEnv.gain.setValueAtTime(amBase, now);
const lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.setValueAtTime(5 + idx * 1.5 + Math.random(), now);
const amDepth = ctx.createGain();
amDepth.gain.setValueAtTime(amBase * 0.7, now);
lfo.connect(amDepth);
amDepth.connect(voiceEnv.gain);
osc.connect(voiceEnv);
voiceEnv.connect(masterEnv);
osc.start(now);
osc.stop(now + duration + 0.1);
lfo.start(now);
lfo.stop(now + duration + 0.1);
});
// High-frequency energy shimmer (4-9 kHz) following a parallel envelope
const shimmerEnv = ctx.createGain();
shimmerEnv.gain.setValueAtTime(0.001, now);
shimmerEnv.gain.linearRampToValueAtTime(0.05, now + 0.9);
shimmerEnv.gain.setValueAtTime(0.035, now + 1.35);
shimmerEnv.gain.linearRampToValueAtTime(0.055, now + 1.95);
shimmerEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
shimmerEnv.connect(this.gainNode);
const shimmerBuf = this.am.createNoiseBuffer('white', duration + 0.2);
const shimmer = ctx.createBufferSource();
shimmer.buffer = shimmerBuf;
[4500, 7800].forEach((center) => {
const bp = ctx.createBiquadFilter();
bp.type = 'bandpass';
bp.frequency.setValueAtTime(center, now);
bp.Q.setValueAtTime(1.2, now);
const amEnv = ctx.createGain();
amEnv.gain.setValueAtTime(center === 4500 ? 0.6 : 0.4, now);
const lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.setValueAtTime(30, now);
const lfoDepth = ctx.createGain();
lfoDepth.gain.setValueAtTime(amEnv.gain.value * 0.9, now);
lfo.connect(lfoDepth);
lfoDepth.connect(amEnv.gain);
shimmer.connect(bp);
bp.connect(amEnv);
amEnv.connect(shimmerEnv);
lfo.start(now);
lfo.stop(now + duration + 0.1);
});
shimmer.start(now);
shimmer.stop(now + duration + 0.05);
}
/**
* Point-Defense Flak Barrage Burst (audio_gaps.md #49)
* Staggered concussive clusters of 4-8 noise transients 10-30 ms apart over
* a 40 Hz sub-bass punch, with a couple of high shell-crack transients per
* volley. Each hit self-releases; whole burst ~0.9-1.4 s.
*/
synthesizeFlakBarrageBurst() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.9 + Math.random() * 0.5;
// 40 Hz sub-bass punch at volley start
const sub = ctx.createOscillator();
sub.type = 'triangle';
sub.frequency.setValueAtTime(40, now);
sub.frequency.linearRampToValueAtTime(32, now + 0.25);
const subEnv = ctx.createGain();
subEnv.gain.setValueAtTime(0.001, now);
subEnv.gain.linearRampToValueAtTime(0.3, now + 0.008);
subEnv.gain.exponentialRampToValueAtTime(0.0001, now + 0.3);
sub.connect(subEnv);
subEnv.connect(this.gainNode);
sub.start(now);
sub.stop(now + 0.32);
const hitCount = 4 + Math.floor(Math.random() * 5); // 4-8 hits
let cursor = 0.05;
for (let i = 0; i < hitCount; i++) {
const t = now + cursor;
const alternate = i % 2 ? 0.75 : 1.0; // mono proxy for spread
const isCrack = i % 3 === 1;
const buf = this.am.createNoiseBuffer('white', 0.12);
const noise = ctx.createBufferSource();
noise.buffer = buf;
const bp = ctx.createBiquadFilter();
bp.type = 'bandpass';
if (isCrack) {
bp.frequency.setValueAtTime(2500 + Math.random() * 1500, t);
bp.Q.setValueAtTime(8 + Math.random() * 4, t);
} else {
bp.frequency.setValueAtTime(300 + Math.random() * 1300, t);
bp.Q.setValueAtTime(2 + Math.random() * 3, t);
}
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, t);
env.gain.linearRampToValueAtTime((isCrack ? 0.1 : 0.17 + Math.random() * 0.12) * alternate, t + 0.004);
env.gain.exponentialRampToValueAtTime(0.001, t + (isCrack ? 0.015 : 0.03 + Math.random() * 0.04));
noise.connect(bp);
bp.connect(env);
env.connect(this.gainNode);
noise.start(t);
noise.stop(t + 0.13);
cursor += 0.01 + Math.random() * 0.02; // 10-30 ms stagger
}
// Second soft sub thump mid-volley (distant batteries)
const sub2 = ctx.createOscillator();
sub2.type = 'triangle';
sub2.frequency.setValueAtTime(38, now + 0.42);
sub2.frequency.linearRampToValueAtTime(30, now + 0.6);
const sub2Env = ctx.createGain();
sub2Env.gain.setValueAtTime(0.001, now + 0.42);
sub2Env.gain.linearRampToValueAtTime(0.16, now + 0.43);
sub2Env.gain.exponentialRampToValueAtTime(0.0001, now + 0.72);
sub2.connect(sub2Env);
sub2Env.connect(this.gainNode);
sub2.start(now + 0.42);
sub2.stop(now + 0.74);
}
/**
* Heavy Weapons Breech Locking Clank (audio_gags.md #50)
* Three-stage loading cycle: 80 ms pre-charge pneumatic hiss, 60 Hz square
* breech slam into a lowpass body, and a high-Q steel ringoff with damped
* inharmonic partials. ~1.6 s.
*/
synthesizeBreechClank() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
// Stage 1: pneumatic pre-charge hiss (80 ms, highpass 2.5 kHz)
const hissBuf = this.am.createNoiseBuffer('white', 0.14);
const hiss = ctx.createBufferSource();
hiss.buffer = hissBuf;
const hp = ctx.createBiquadFilter();
hp.type = 'highpass';
hp.frequency.setValueAtTime(2500, now);
hp.Q.setValueAtTime(0.7, now);
const hissEnv = ctx.createGain();
hissEnv.gain.setValueAtTime(0.001, now);
hissEnv.gain.linearRampToValueAtTime(0.14, now + 0.005);
hissEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
hiss.connect(hp);
hp.connect(hissEnv);
hissEnv.connect(this.gainNode);
hiss.start(now);
hiss.stop(now + 0.1);
// Stage 2: breech slam - 60 Hz square impact into a lowpass body
const slam = ctx.createOscillator();
slam.type = 'square';
slam.frequency.setValueAtTime(60, now + 0.08);
slam.frequency.linearRampToValueAtTime(48, now + 0.2);
const slamLp = ctx.createBiquadFilter();
slamLp.type = 'lowpass';
slamLp.frequency.setValueAtTime(300, now + 0.08);
const slamEnv = ctx.createGain();
slamEnv.gain.setValueAtTime(0.001, now + 0.08);
slamEnv.gain.linearRampToValueAtTime(0.4, now + 0.086);
slamEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.28);
slam.connect(slamLp);
slamLp.connect(slamEnv);
slamEnv.connect(this.gainNode);
slam.start(now + 0.08);
slam.stop(now + 0.3);
// Stage 3: high-Q steel ringoff with damped inharmonic partials
const ringBuf = this.am.createNoiseBuffer('white', 0.45);
const ring = ctx.createBufferSource();
ring.buffer = ringBuf;
const ringBp = ctx.createBiquadFilter();
ringBp.type = 'bandpass';
ringBp.frequency.setValueAtTime(1800 + Math.random() * 1600, now + 0.09);
ringBp.Q.setValueAtTime(18 + Math.random() * 7, now + 0.09);
const ringEnv = ctx.createGain();
ringEnv.gain.setValueAtTime(0.001, now + 0.09);
ringEnv.gain.linearRampToValueAtTime(0.16, now + 0.095);
ringEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.5);
ring.connect(ringBp);
ringBp.connect(ringEnv);
ringEnv.connect(this.gainNode);
ring.start(now + 0.09);
ring.stop(now + 0.52);
[240, 630, 1120].forEach((freq, idx) => {
const partial = ctx.createOscillator();
partial.type = 'sine';
partial.frequency.setValueAtTime(freq, now + 0.09);
const pEnv = ctx.createGain();
pEnv.gain.setValueAtTime(0.001, now + 0.09);
pEnv.gain.linearRampToValueAtTime([0.11, 0.07, 0.045][idx], now + 0.095);
pEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.09 + [0.9, 0.6, 0.35][idx]);
partial.connect(pEnv);
pEnv.connect(this.gainNode);
partial.start(now + 0.09);
partial.stop(now + [1.0, 0.7, 0.45][idx]);
});
}
/**
* Heavy Hydraulic Docking Groan & Mechanical Clang (audio_gaps.md #52)
* A slow ~2.4 s structural stress groan: brown noise through two parallel
* resonant bandpasses sweeping 38->62 Hz and 55->85 Hz under an asymmetric
* groaning envelope, resolving into a metallic anvil transient with an
* inharmonic sine stack at the moment the clamp seats. ~4.0 s.
*/
synthesizeDockingGroan() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const groanDur = 3.9;
// Brown-noise stress bed through two sweeping resonant bandpasses
const bedBuf = this.am.createNoiseBuffer('brown', groanDur + 0.2);
const bed = ctx.createBufferSource();
bed.buffer = bedBuf;
const groanEnv = ctx.createGain();
groanEnv.gain.setValueAtTime(0.0001, now);
groanEnv.gain.linearRampToValueAtTime(0.001, now + 0.01);
groanEnv.gain.linearRampToValueAtTime(0.22, now + 1.1); // groan up
groanEnv.gain.setValueAtTime(0.1, now + 1.6); // stress dip
groanEnv.gain.linearRampToValueAtTime(0.26, now + 2.3); // groan peaks
groanEnv.gain.exponentialRampToValueAtTime(0.0001, now + groanDur);
groanEnv.connect(this.gainNode);
[38, 55].forEach((startFreq, idx) => {
const bp = ctx.createBiquadFilter();
bp.type = 'bandpass';
bp.frequency.setValueAtTime(startFreq, now);
bp.frequency.linearRampToValueAtTime(idx === 0 ? 62 : 85, now + 2.4);
bp.Q.setValueAtTime(idx === 0 ? 5.5 : 4.5, now);
bed.connect(bp);
bp.connect(groanEnv);
});
bed.start(now);
bed.stop(now + groanDur + 0.05);
// Metallic anvil clang as the groan resolves (~2.7 s in)
const clangT = now + 2.7;
const ringBuf = this.am.createNoiseBuffer('white', 0.4);
const ring = ctx.createBufferSource();
ring.buffer = ringBuf;
const ringBp = ctx.createBiquadFilter();
ringBp.type = 'bandpass';
ringBp.frequency.setValueAtTime(2000, clangT);
ringBp.Q.setValueAtTime(20, clangT);
const ringEnv = ctx.createGain();
ringEnv.gain.setValueAtTime(0.001, clangT);
ringEnv.gain.linearRampToValueAtTime(0.16, clangT + 0.004);
ringEnv.gain.exponentialRampToValueAtTime(0.001, clangT + 0.35);
ring.connect(ringBp);
ringBp.connect(ringEnv);
ringEnv.connect(this.gainNode);
ring.start(clangT);
ring.stop(clangT + 0.4);
[420, 880, 1600].forEach((freq, idx) => {
const partial = ctx.createOscillator();
partial.type = 'sine';
partial.frequency.setValueAtTime(freq, clangT);
const pEnv = ctx.createGain();
pEnv.gain.setValueAtTime(0.001, clangT);
pEnv.gain.linearRampToValueAtTime([0.08, 0.06, 0.04][idx], clangT + 0.005);
pEnv.gain.exponentialRampToValueAtTime(0.001, clangT + [0.8, 0.55, 0.3][idx]);
partial.connect(pEnv);
pEnv.connect(this.gainNode);
partial.start(clangT);
partial.stop(clangT + [0.85, 0.6, 0.35][idx]);
});
}
/**
* Dual-Reel Magnetic Tape Spooler & Pinch Roller Clank (audio_gaps.md #53)
* Solenoid pinch-roller engagement (110 Hz impulse), a fluttering pink tape
* bed (15 Hz AM on a bandpass noise loop) and a faint 3200 Hz capstan hum
* with spool-up glide, ending in an optional fast-forward whine tail.
* ~2.6-3.5 s.
*/
synthesizeTapeSpooler() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 2.6 + Math.random() * 0.9;
// Solenoid clamp impulse (110 Hz) with a tiny mechanical tick
const solenoid = ctx.createOscillator();
solenoid.type = 'square';
solenoid.frequency.setValueAtTime(110, now);
solenoid.frequency.linearRampToValueAtTime(95, now + 0.05);
const solEnv = ctx.createGain();
solEnv.gain.setValueAtTime(0.001, now);
solEnv.gain.linearRampToValueAtTime(0.22, now + 0.004);
solEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.07);
solenoid.connect(solEnv);
solEnv.connect(this.gainNode);
solenoid.start(now);
solenoid.stop(now + 0.09);
const tickBuf = this.am.createNoiseBuffer('white', 0.03);
const tick = ctx.createBufferSource();
tick.buffer = tickBuf;
const tickBp = ctx.createBiquadFilter();
tickBp.type = 'bandpass';
tickBp.frequency.setValueAtTime(1800, now + 0.02);
tickBp.Q.setValueAtTime(10, now + 0.02);
const tickEnv = ctx.createGain();
tickEnv.gain.setValueAtTime(0.08, now + 0.02);
tickEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.045);
tick.connect(tickBp);
tickBp.connect(tickEnv);
tickEnv.connect(this.gainNode);
tick.start(now + 0.02);
tick.stop(now + 0.06);
// Fluttering pink-noise tape bed (15 Hz flutter AM)
const tapeBuf = this.am.createNoiseBuffer('pink', duration + 0.3);
const tape = ctx.createBufferSource();
tape.buffer = tapeBuf;
tape.loop = true;
const tapeBp = ctx.createBiquadFilter();
tapeBp.type = 'bandpass';
tapeBp.frequency.setValueAtTime(500 + Math.random() * 1000, now);
tapeBp.Q.setValueAtTime(1, now);
const tapeEnv = ctx.createGain();
tapeEnv.gain.setValueAtTime(0.0001, now);
tapeEnv.gain.linearRampToValueAtTime(0.05, now + 0.15);
tapeEnv.gain.setValueAtTime(0.03, now + duration * 0.45);
tapeEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
const flutter = ctx.createOscillator();
flutter.type = 'sine';
flutter.frequency.setValueAtTime(15, now);
const flutterDepth = ctx.createGain();
flutterDepth.gain.setValueAtTime(0.035, now);
flutter.connect(flutterDepth);
flutterDepth.connect(tapeEnv.gain);
tape.connect(tapeBp);
tapeBp.connect(tapeEnv);
tapeEnv.connect(this.gainNode);
tape.start(now);
tape.stop(now + duration + 0.05);
flutter.start(now);
flutter.stop(now + duration + 0.05);
// Capstan hum: faint 3200 Hz sine with a slow spool-up glide and wow
const capstan = ctx.createOscillator();
capstan.type = 'sine';
capstan.frequency.setValueAtTime(3136, now);
capstan.frequency.exponentialRampToValueAtTime(3264, now + 1.5);
const capstanEnv = ctx.createGain();
capstanEnv.gain.setValueAtTime(0.0001, now);
capstanEnv.gain.linearRampToValueAtTime(0.016, now + 0.3);
capstanEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
const wow = ctx.createOscillator();
wow.type = 'sine';
wow.frequency.setValueAtTime(8, now);
const wowDepth = ctx.createGain();
wowDepth.gain.setValueAtTime(12, now);
wow.connect(wowDepth);
wowDepth.connect(capstan.frequency);
capstan.connect(capstanEnv);
capstanEnv.connect(this.gainNode);
capstan.start(now);
capstan.stop(now + duration + 0.1);
wow.start(now);
wow.stop(now + duration + 0.1);
// Occasional fast-forward whine tail
if (Math.random() < 0.3) {
const whineT = now + duration - 0.55;
const whine = ctx.createOscillator();
whine.type = 'sine';
whine.frequency.setValueAtTime(1400, whineT);
whine.frequency.exponentialRampToValueAtTime(2300, whineT + 0.5);
const whineEnv = ctx.createGain();
whineEnv.gain.setValueAtTime(0.0001, whineT);
whineEnv.gain.linearRampToValueAtTime(0.02, whineT + 0.08);
whineEnv.gain.exponentialRampToValueAtTime(0.0001, whineT + 0.5);
whine.connect(whineEnv);
whineEnv.connect(this.gainNode);
whine.start(whineT);
whine.stop(whineT + 0.55);
}
}
/**
* Dot-Matrix / Teletype Chatter Phrase (audio_gaps.md #51, one phrase unit)
* A burst of 6-16 pseudo-random high-Q ~4 kHz impact spikes with periodic
* 12 Hz stepper clicks, phrased with a soft attack/release. The continuous
* Nostromo terminal loop is startTeletypeChatter() below.
*/
synthesizeTeletypeChatterPhrase() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const phraseDur = 0.6 + Math.random() * 0.8;
const masterEnv = ctx.createGain();
masterEnv.gain.setValueAtTime(0.0001, now);
masterEnv.gain.linearRampToValueAtTime(1, now + 0.03); // soft phrase in
masterEnv.gain.setValueAtTime(1, now + phraseDur - 0.06);
masterEnv.gain.exponentialRampToValueAtTime(0.0001, now + phraseDur + 0.05);
masterEnv.connect(this.gainNode);
const strikes = 6 + Math.floor(Math.random() * 11); // 6-16 spikes
let cursor = 0.02;
for (let i = 0; i < strikes; i++) {
if (cursor >= phraseDur - 0.1) break; // never schedule past the phrase out
const t = now + cursor;
const buf = this.am.createNoiseBuffer('white', 0.03);
const noise = ctx.createBufferSource();
noise.buffer = buf;
const bp = ctx.createBiquadFilter();
bp.type = 'bandpass';
bp.frequency.setValueAtTime(3400 + Math.random() * 1200, t);
bp.Q.setValueAtTime(10 + Math.random() * 6, t);
const env = ctx.createGain();
env.gain.setValueAtTime(0.1 + Math.random() * 0.1, t);
env.gain.exponentialRampToValueAtTime(0.001, t + 0.012 + Math.random() * 0.01);
noise.connect(bp);
bp.connect(env);
env.connect(masterEnv);
noise.start(t);
noise.stop(t + 0.035);
// Periodic 12 Hz carriage stepper click
if (i % 3 === 2) {
const stepper = ctx.createOscillator();
stepper.type = 'square';
stepper.frequency.setValueAtTime(90, t);
stepper.frequency.linearRampToValueAtTime(70, t + 0.02);
const sEnv = ctx.createGain();
sEnv.gain.setValueAtTime(0.06, t);
sEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.025);
stepper.connect(sEnv);
sEnv.connect(masterEnv);
stepper.start(t);
stepper.stop(t + 0.03);
}
cursor += 0.045 + Math.random() * 0.095; // jittered 45-140 ms strikes
}
}
stopAllLoops() {
this.stopMedicalMonitor();
this.stopStationSparks();
+8 -3
View File
@@ -169,6 +169,7 @@ const UniverseRegistry = {
{ id: 'btn-cardassian-door', label: 'CARDASSIAN BULKHEAD DOOR' },
{ id: 'btn-turbolift', label: 'TURBOLIFT WHOOSH' },
{ id: 'btn-replicator', label: 'REPLICATOR SHIMMER' },
{ id: 'btn-transporter', label: 'TRANSPORTER CYCLE' },
{ id: 'btn-chirp-single', label: 'LCARS SINGLE CHIRP' }
],
presets: StarshipPresets
@@ -310,7 +311,9 @@ const UniverseRegistry = {
{ 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' }
{ id: 'btn-air-handler', label: 'HVAC AIR HANDLER' },
{ id: 'btn-dock-groan', label: 'DOCKING GROAN & CLANG' },
{ id: 'btn-teletype', label: 'DOT-MATRIX TELETYPE' }
],
presets: {
'ind-nostromo': {
@@ -506,7 +509,8 @@ const UniverseRegistry = {
{ id: 'btn-cygnus-chug', label: 'STEAM PISTON CHUG' },
{ id: 'btn-chirp-single', label: 'ASTROGATOR BEAT' },
{ id: 'btn-chirp-sweep', label: 'ANALOG GLISSANDO' },
{ id: 'btn-relay-click', label: 'TAPE REEL SPOOL' }
{ id: 'btn-relay-click', label: 'TAPE REEL SPOOL' },
{ id: 'btn-tape-spooler', label: 'DUAL-REEL TAPE SPOOLER' }
],
presets: {
'ret-jupiter-2': {
@@ -614,7 +618,8 @@ const UniverseRegistry = {
{ 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' }
{ id: 'btn-oxygen-reg', label: 'O2 DEMAND VALVE' },
{ id: 'btn-breech-clank', label: 'WEAPONS BREECH CLANK' }
],
presets: {
'mil-sulaco': {