diff --git a/dist/SciFiAmbientDisplay_v4.html b/dist/SciFiAmbientDisplay_v4.html
index 0a549b0..1712b56 100644
--- a/dist/SciFiAmbientDisplay_v4.html
+++ b/dist/SciFiAmbientDisplay_v4.html
@@ -360,6 +360,62 @@ body {
color: #fff;
}
+/* Master Engage / Full Stop Play Button */
+#btn-master-play {
+ background: #00cc66;
+ color: #000000;
+ box-shadow: 0 0 12px rgba(0, 204, 102, 0.45);
+ transition: transform 0.1s, filter 0.15s, background-color 0.2s, box-shadow 0.2s;
+}
+
+#btn-master-play:hover {
+ filter: brightness(1.2);
+}
+
+#btn-master-play.playing {
+ background: #ff3333;
+ color: #ffffff;
+ box-shadow: 0 0 14px rgba(255, 51, 51, 0.5);
+}
+
+#btn-master-play.engaging {
+ animation: lcars-engage-flash 0.35s infinite alternate ease-in-out;
+}
+
+#btn-master-play.stopping {
+ animation: lcars-stop-flash 0.35s infinite alternate ease-in-out;
+}
+
+@keyframes lcars-engage-flash {
+ 0% {
+ background: #00cc66;
+ color: #000000;
+ box-shadow: 0 0 6px rgba(0, 204, 102, 0.4);
+ filter: brightness(0.95);
+ }
+ 100% {
+ background: #33ff99;
+ color: #000000;
+ box-shadow: 0 0 24px rgba(0, 255, 136, 0.95);
+ filter: brightness(1.35);
+ }
+}
+
+@keyframes lcars-stop-flash {
+ 0% {
+ background: #ff3333;
+ color: #ffffff;
+ box-shadow: 0 0 6px rgba(255, 51, 51, 0.4);
+ filter: brightness(0.95);
+ }
+ 100% {
+ background: #ff6666;
+ color: #ffffff;
+ box-shadow: 0 0 24px rgba(255, 51, 51, 0.95);
+ filter: brightness(1.35);
+ }
+}
+
/* Action Pill Buttons */
.pill-button-group {
display: flex;
@@ -2396,7 +2452,7 @@ body.theme-comedy .observation-overlay { background: #07061a; }
@@ -2886,14 +2942,20 @@ class HullDroneSynth {
};
}
- start() {
+ start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
// Channel Gain Node
this.gainNode = ctx.createGain();
- this.gainNode.gain.setValueAtTime(this.isMuted ? 0 : this.params.volume, ctx.currentTime);
+ const targetVol = this.isMuted ? 0 : this.params.volume;
+ if (rampDuration > 0) {
+ this.gainNode.gain.setValueAtTime(0.0001, ctx.currentTime);
+ this.gainNode.gain.linearRampToValueAtTime(targetVol, ctx.currentTime + rampDuration);
+ } else {
+ this.gainNode.gain.setValueAtTime(targetVol, ctx.currentTime);
+ }
// Steep Lowpass Filter (24dB/oct via 2 cascading biquads)
this.filterNode = ctx.createBiquadFilter();
@@ -2961,7 +3023,22 @@ class HullDroneSynth {
this.nodes = [this.subOsc1, this.subOsc2, this.noiseSource, lfo, osc1Gain, osc2Gain, noiseGain, lfoGain, this.filterNode, filterStage2, this.gainNode];
}
- stop() {
+ stop(fadeDuration = 0) {
+ if (fadeDuration > 0 && this.nodes.length > 0 && this.gainNode && this.am.ctx) {
+ const now = this.am.ctx.currentTime;
+ this.gainNode.gain.cancelScheduledValues(now);
+ this.gainNode.gain.setValueAtTime(Math.max(0.0001, this.gainNode.gain.value), now);
+ this.gainNode.gain.linearRampToValueAtTime(0.0001, now + fadeDuration);
+ if (this.stoppingTimeout) clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = setTimeout(() => {
+ this.stop(0);
+ }, fadeDuration * 1000);
+ return;
+ }
+ if (this.stoppingTimeout) {
+ clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = null;
+ }
if (this.nodes.length > 0) {
try {
if (this.subOsc1) this.subOsc1.stop();
@@ -2974,6 +3051,10 @@ class HullDroneSynth {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
+ this.subOsc1 = null;
+ this.subOsc2 = null;
+ this.noiseSource = null;
+ this.gainNode = null;
}
}
@@ -3047,13 +3128,19 @@ class WarpCoreSynth {
this.onPulse = null; // Callback for UI visualizer pulse animation!
}
- start() {
+ start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
this.gainNode = ctx.createGain();
- this.gainNode.gain.setValueAtTime(this.isMuted ? 0 : this.params.volume, ctx.currentTime);
+ const targetVol = this.isMuted ? 0 : this.params.volume;
+ if (rampDuration > 0) {
+ this.gainNode.gain.setValueAtTime(0.0001, ctx.currentTime);
+ this.gainNode.gain.linearRampToValueAtTime(targetVol, ctx.currentTime + rampDuration);
+ } else {
+ this.gainNode.gain.setValueAtTime(targetVol, ctx.currentTime);
+ }
// Filter Node
this.filterNode = ctx.createBiquadFilter();
@@ -3067,12 +3154,22 @@ class WarpCoreSynth {
// Carrier & Modulator Oscillators (FM Engine)
this.carrier = ctx.createOscillator();
this.carrier.type = this.params.pulseShape === 'tos' ? 'sawtooth' : (this.params.pulseShape === 'nx' ? 'triangle' : 'sine');
- this.carrier.frequency.setValueAtTime(this.params.carrierFreq, ctx.currentTime);
+ if (rampDuration > 0) {
+ this.carrier.frequency.setValueAtTime(Math.max(20, this.params.carrierFreq * 0.45), ctx.currentTime);
+ this.carrier.frequency.exponentialRampToValueAtTime(this.params.carrierFreq, ctx.currentTime + rampDuration);
+ } else {
+ this.carrier.frequency.setValueAtTime(this.params.carrierFreq, ctx.currentTime);
+ }
// Sub-harmonic oscillator for massive bottom end
this.subOsc = ctx.createOscillator();
this.subOsc.type = 'sine';
- this.subOsc.frequency.setValueAtTime(this.params.carrierFreq * 0.5, ctx.currentTime);
+ if (rampDuration > 0) {
+ this.subOsc.frequency.setValueAtTime(Math.max(12, this.params.carrierFreq * 0.22), ctx.currentTime);
+ this.subOsc.frequency.exponentialRampToValueAtTime(this.params.carrierFreq * 0.5, ctx.currentTime + rampDuration);
+ } else {
+ this.subOsc.frequency.setValueAtTime(this.params.carrierFreq * 0.5, ctx.currentTime);
+ }
const subGain = ctx.createGain();
subGain.gain.setValueAtTime(0.6, ctx.currentTime);
@@ -3220,7 +3317,29 @@ class WarpCoreSynth {
}
}
- stop() {
+ stop(fadeDuration = 0) {
+ if (fadeDuration > 0 && this.nodes.length > 0 && this.gainNode && this.am.ctx) {
+ const now = this.am.ctx.currentTime;
+ this.gainNode.gain.cancelScheduledValues(now);
+ this.gainNode.gain.setValueAtTime(Math.max(0.0001, this.gainNode.gain.value), now);
+ this.gainNode.gain.linearRampToValueAtTime(0.0001, now + fadeDuration);
+ if (this.carrier) {
+ try {
+ this.carrier.frequency.cancelScheduledValues(now);
+ this.carrier.frequency.setValueAtTime(this.carrier.frequency.value, now);
+ this.carrier.frequency.linearRampToValueAtTime(Math.max(20, this.params.carrierFreq * 0.35), now + fadeDuration);
+ } catch (e) {}
+ }
+ if (this.stoppingTimeout) clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = setTimeout(() => {
+ this.stop(0);
+ }, fadeDuration * 1000);
+ return;
+ }
+ if (this.stoppingTimeout) {
+ clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = null;
+ }
if (this.pulseInterval) {
clearInterval(this.pulseInterval);
this.pulseInterval = null;
@@ -3234,6 +3353,9 @@ class WarpCoreSynth {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
+ this.carrier = null;
+ this.subOsc = null;
+ this.gainNode = null;
}
}
@@ -3274,13 +3396,19 @@ class LifeSupportSynth {
};
}
- start() {
+ start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
this.gainNode = ctx.createGain();
- this.gainNode.gain.setValueAtTime(this.isMuted ? 0 : this.params.volume, ctx.currentTime);
+ const targetVol = this.isMuted ? 0 : this.params.volume;
+ if (rampDuration > 0) {
+ this.gainNode.gain.setValueAtTime(0.0001, ctx.currentTime);
+ this.gainNode.gain.linearRampToValueAtTime(targetVol, ctx.currentTime + rampDuration);
+ } else {
+ this.gainNode.gain.setValueAtTime(targetVol, ctx.currentTime);
+ }
// Highpass filter (cuts muddy lows)
const hpFilter = ctx.createBiquadFilter();
@@ -3341,7 +3469,22 @@ class LifeSupportSynth {
];
}
- stop() {
+ stop(fadeDuration = 0) {
+ if (fadeDuration > 0 && this.nodes.length > 0 && this.gainNode && this.am.ctx) {
+ const now = this.am.ctx.currentTime;
+ this.gainNode.gain.cancelScheduledValues(now);
+ this.gainNode.gain.setValueAtTime(Math.max(0.0001, this.gainNode.gain.value), now);
+ this.gainNode.gain.linearRampToValueAtTime(0.0001, now + fadeDuration);
+ if (this.stoppingTimeout) clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = setTimeout(() => {
+ this.stop(0);
+ }, fadeDuration * 1000);
+ return;
+ }
+ if (this.stoppingTimeout) {
+ clearTimeout(this.stoppingTimeout);
+ this.stoppingTimeout = null;
+ }
if (this.nodes.length > 0) {
try {
if (this.noiseSource) this.noiseSource.stop();
@@ -3350,6 +3493,8 @@ class LifeSupportSynth {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
+ this.noiseSource = null;
+ this.gainNode = null;
}
}
@@ -3472,20 +3617,26 @@ class TelemetrySynth {
case 'ds9':
Math.random() > 0.5 ? this.synthesizeCardassianSensor() : this.synthesizeLCARSSingleChirp();
break;
- case 'voyager':
- Math.random() > 0.4 ? this.synthesizeLCARSDoubleChirp() : this.synthesizeSensorSweep();
+ case 'voyager': {
+ const r = Math.random();
+ if (r < 0.12) this.synthesizeCommBadge();
+ else if (r < 0.55) this.synthesizeLCARSDoubleChirp();
+ else this.synthesizeSensorSweep();
break;
+ }
case 'nx':
Math.random() > 0.5 ? this.synthesizeNXRelay() : this.synthesizeNXIndicatorBeep();
break;
case 'tng':
- default:
+ default: {
const r = Math.random();
- if (r < 0.45) this.synthesizeLCARSSingleChirp();
- else if (r < 0.75) this.synthesizeLCARSDoubleChirp();
- else if (r < 0.90) this.synthesizeLCARSSequence();
+ if (r < 0.10) this.synthesizeCommBadge();
+ else if (r < 0.45) this.synthesizeLCARSSingleChirp();
+ else if (r < 0.72) this.synthesizeLCARSDoubleChirp();
+ else if (r < 0.88) this.synthesizeLCARSSequence();
else this.synthesizeSensorSweep();
break;
+ }
}
// OBSERVATION intentionally treats telemetry as an abstract activity pulse,
@@ -3499,6 +3650,33 @@ class TelemetrySynth {
}));
}
+ /**
+ * Star Trek Comm Badge Confirmation Chirp
+ */
+ synthesizeCommBadge() {
+ if (window.expandedAudio) {
+ window.expandedAudio.synthesizeCommBadge();
+ return;
+ }
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+ const now = ctx.currentTime;
+ [784, 1396.91].forEach((freq, idx) => {
+ const t = now + idx * 0.038;
+ 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.35, t + 0.005);
+ env.gain.exponentialRampToValueAtTime(0.001, t + (idx === 0 ? 0.045 : 0.09));
+ osc.connect(env);
+ env.connect(this.gainNode);
+ osc.start(t);
+ osc.stop(t + (idx === 0 ? 0.05 : 0.095));
+ });
+ }
+
/**
* TNG/Voyager Single LCARS Touch Tone (Soft sine with gentle attack and rapid exponential decay)
*/
@@ -4421,6 +4599,105 @@ class WhoniverseAudioSynth {
osc.stop(now + duration + 0.05);
});
}
+
+ /**
+ * TARDIS Lever-Throw Clunk (Tactile mechanical switch)
+ * Hard square transient click coupled to a dull highpass noise thump
+ */
+ synthesizeLeverClunk() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Hard square transient click
+ const clickOsc = ctx.createOscillator();
+ clickOsc.type = 'square';
+ clickOsc.frequency.setValueAtTime(80, now);
+ clickOsc.frequency.exponentialRampToValueAtTime(30, now + 0.035);
+
+ const clickEnv = ctx.createGain();
+ clickEnv.gain.setValueAtTime(0.42, now);
+ clickEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.04);
+
+ clickOsc.connect(clickEnv);
+ clickEnv.connect(this.gainNode);
+ clickOsc.start(now);
+ clickOsc.stop(now + 0.042);
+
+ // Dull highpass noise thump
+ const noiseBuf = this.am.createNoiseBuffer('pink', 0.15);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const filter = ctx.createBiquadFilter();
+ filter.type = 'highpass';
+ filter.frequency.setValueAtTime(320, now);
+
+ const noiseEnv = ctx.createGain();
+ noiseEnv.gain.setValueAtTime(0.3, now);
+ noiseEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.06);
+
+ noise.connect(filter);
+ filter.connect(noiseEnv);
+ noiseEnv.connect(this.gainNode);
+ noise.start(now);
+ noise.stop(now + 0.065);
+ }
+
+ /**
+ * TARDIS Police Box Exterior Door Open / Close (Wood creak + mortise latch)
+ * Modulated bandpass friction noise layered over a sharp metallic dual-click transient
+ */
+ synthesizeTardisDoor() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.75;
+
+ // 1. Modulated bandpass friction noise (wood creak)
+ const creakBuf = this.am.createNoiseBuffer('brown', duration);
+ const creak = ctx.createBufferSource();
+ creak.buffer = creakBuf;
+
+ const creakFilter = ctx.createBiquadFilter();
+ creakFilter.type = 'bandpass';
+ creakFilter.frequency.setValueAtTime(260, now);
+ creakFilter.frequency.linearRampToValueAtTime(540, now + 0.35);
+ creakFilter.frequency.exponentialRampToValueAtTime(310, now + 0.65);
+ creakFilter.Q.setValueAtTime(6.0, now);
+
+ const creakEnv = ctx.createGain();
+ creakEnv.gain.setValueAtTime(0.001, now);
+ creakEnv.gain.linearRampToValueAtTime(0.38, now + 0.15);
+ creakEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.68);
+
+ creak.connect(creakFilter);
+ creakFilter.connect(creakEnv);
+ creakEnv.connect(this.gainNode);
+ creak.start(now);
+ creak.stop(now + 0.7);
+
+ // 2. Iron mortise latch dual-click transient
+ [0.54, 0.62].forEach((offset, idx) => {
+ const click = ctx.createOscillator();
+ click.type = 'triangle';
+ click.frequency.setValueAtTime(idx === 0 ? 1100 : 750, now + offset);
+ click.frequency.exponentialRampToValueAtTime(180, now + offset + 0.03);
+
+ const cEnv = ctx.createGain();
+ cEnv.gain.setValueAtTime(0.28, now + offset);
+ cEnv.gain.exponentialRampToValueAtTime(0.001, now + offset + 0.035);
+
+ click.connect(cEnv);
+ cEnv.connect(this.gainNode);
+ click.start(now + offset);
+ click.stop(now + offset + 0.04);
+ });
+ }
}
window.WhoniverseAudioSynth = WhoniverseAudioSynth;
@@ -4436,6 +4713,8 @@ class ExpandedSciFiAudioSynth {
constructor(audioManager) {
this.am = audioManager;
this.gainNode = null;
+ this.medicalTimer = null;
+ this.sparkTimer = null;
}
init() {
@@ -4809,6 +5088,81 @@ class ExpandedSciFiAudioSynth {
}, 180);
}
+ /**
+ * Space Station Air Handler Thud (large HVAC unit cycling on)
+ * Distinct from the docking clamp latch above: a dull low-frequency thump
+ * (no bright metallic impact) followed by a slow-building airflow whoosh
+ * rather than a short pneumatic hiss. A clamp is a single hard mechanical
+ * event; an air handler is a big soft one that keeps breathing after it.
+ */
+ synthesizeAirHandlerThud() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx) return;
+
+ const now = ctx.currentTime;
+
+ // Dull low-frequency thump -- triangle, not square, and no bright impact
+ // transient, so it reads as a heavy fan housing rather than a latch.
+ const thump = ctx.createOscillator();
+ thump.type = 'triangle';
+ thump.frequency.setValueAtTime(95, now);
+ thump.frequency.exponentialRampToValueAtTime(38, now + 0.22);
+
+ const thumpEnv = ctx.createGain();
+ thumpEnv.gain.setValueAtTime(0.001, now);
+ thumpEnv.gain.linearRampToValueAtTime(0.42, now + 0.02);
+ thumpEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
+
+ // Sub-octave body for HVAC housing weight
+ const sub = ctx.createOscillator();
+ sub.type = 'sine';
+ sub.frequency.setValueAtTime(46, now);
+
+ const subEnv = ctx.createGain();
+ subEnv.gain.setValueAtTime(0.001, now);
+ subEnv.gain.linearRampToValueAtTime(0.25, now + 0.03);
+ subEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.45);
+
+ thump.connect(thumpEnv);
+ thumpEnv.connect(this.gainNode);
+ sub.connect(subEnv);
+ subEnv.connect(this.gainNode);
+
+ thump.start(now);
+ thump.stop(now + 0.4);
+ sub.start(now);
+ sub.stop(now + 0.45);
+
+ // Slow-building airflow whoosh -- lowpass rather than the clamp's
+ // bandpass, and a much slower attack, so it reads as a fan spinning up
+ // rather than a sharp seal-release hiss.
+ setTimeout(() => {
+ if (!this.am.ctx) return;
+ const t = this.am.ctx.currentTime;
+ const airBuf = this.am.createNoiseBuffer('pink', 1.8);
+ const air = this.am.ctx.createBufferSource();
+ air.buffer = airBuf;
+
+ const airFilter = this.am.ctx.createBiquadFilter();
+ airFilter.type = 'lowpass';
+ airFilter.frequency.setValueAtTime(420, t);
+ airFilter.Q.setValueAtTime(0.7, t);
+
+ const airEnv = this.am.ctx.createGain();
+ airEnv.gain.setValueAtTime(0.001, t);
+ airEnv.gain.linearRampToValueAtTime(0.22, t + 0.35);
+ airEnv.gain.exponentialRampToValueAtTime(0.001, t + 1.6);
+
+ air.connect(airFilter);
+ airFilter.connect(airEnv);
+ airEnv.connect(this.gainNode);
+
+ air.start(t);
+ air.stop(t + 1.6);
+ }, 140);
+ }
+
/**
* Ludicrous Speed Accelerator (Spaceball One / Comedy)
*/
@@ -4924,15 +5278,1267 @@ class ExpandedSciFiAudioSynth {
osc.start(now);
osc.stop(now + duration);
}
+
+ /**
+ * Star Trek Comm Badge Confirmation Chirp
+ * Iconic bright two-tone pulse in rapid sequence (784 Hz to 1397 Hz)
+ */
+ synthesizeCommBadge() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const tone1Freq = 784; // G5
+ const tone2Freq = 1396.91; // F6
+
+ // Pulse 1
+ const osc1 = ctx.createOscillator();
+ osc1.type = 'sine';
+ osc1.frequency.setValueAtTime(tone1Freq, now);
+
+ const env1 = ctx.createGain();
+ env1.gain.setValueAtTime(0.001, now);
+ env1.gain.linearRampToValueAtTime(0.38, now + 0.005);
+ env1.gain.exponentialRampToValueAtTime(0.001, now + 0.045);
+
+ osc1.connect(env1);
+ env1.connect(this.gainNode);
+ osc1.start(now);
+ osc1.stop(now + 0.048);
+
+ // Pulse 2
+ const t2 = now + 0.038;
+ const osc2 = ctx.createOscillator();
+ osc2.type = 'sine';
+ osc2.frequency.setValueAtTime(tone2Freq, t2);
+
+ const env2 = ctx.createGain();
+ env2.gain.setValueAtTime(0.001, t2);
+ env2.gain.linearRampToValueAtTime(0.42, t2 + 0.006);
+ env2.gain.exponentialRampToValueAtTime(0.001, t2 + 0.095);
+
+ osc2.connect(env2);
+ env2.connect(this.gainNode);
+ osc2.start(t2);
+ osc2.stop(t2 + 0.1);
+ }
+
+ /**
+ * Starfleet Pneumatic Door Swish (Four Era Variants)
+ * Bandpass-filtered white noise burst shaped per era
+ */
+ synthesizeDoorSwish(era = 'tng') {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ let centerFreq = 950;
+ let qVal = 1.8;
+ let duration = 0.45;
+ let noiseType = 'white';
+
+ switch (era) {
+ case 'tos':
+ centerFreq = 720;
+ qVal = 1.4;
+ duration = 0.38;
+ break;
+ case 'voyager':
+ centerFreq = 1250;
+ qVal = 2.4;
+ duration = 0.40;
+ break;
+ case 'nx':
+ centerFreq = 650;
+ qVal = 2.0;
+ duration = 0.55;
+ noiseType = 'pink';
+ break;
+ case 'tng':
+ default:
+ centerFreq = 950;
+ qVal = 1.8;
+ duration = 0.45;
+ break;
+ }
+
+ const noiseBuf = this.am.createNoiseBuffer(noiseType, duration + 0.1);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(centerFreq * 0.8, now);
+ bp.frequency.linearRampToValueAtTime(centerFreq * 1.15, now + duration * 0.4);
+ bp.frequency.exponentialRampToValueAtTime(centerFreq * 0.7, now + duration);
+ bp.Q.setValueAtTime(qVal, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.45, now + 0.03);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Starfleet Bosun's Pipe Whistle (TOS Command Announce)
+ * Two-tone sine glide (1800 Hz -> 2400 Hz -> 1850 Hz) with gentle tremolo
+ */
+ synthesizeBosunWhistle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.85;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(1800, now);
+ osc.frequency.linearRampToValueAtTime(2400, now + 0.22);
+ osc.frequency.setValueAtTime(2400, now + 0.48);
+ osc.frequency.linearRampToValueAtTime(1850, now + 0.75);
+
+ const tremolo = ctx.createOscillator();
+ tremolo.type = 'sine';
+ tremolo.frequency.setValueAtTime(6.5, now);
+
+ const tremGain = ctx.createGain();
+ tremGain.gain.setValueAtTime(0.12, now);
+ tremolo.connect(tremGain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.28, now + 0.05);
+ env.gain.setValueAtTime(0.28, now + 0.65);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ tremGain.connect(env.gain);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ tremolo.start(now);
+ osc.stop(now + duration);
+ tremolo.stop(now + duration);
+ }
+
+ /**
+ * Starfleet Sickbay Medical Monitor ECG Ping
+ * Calm, periodic rhythmic vital signs pulse (1080 Hz sine, 40 ms decay)
+ */
+ synthesizeMedicalMonitor() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.045;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(1080, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.32, now + 0.003);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ osc.stop(now + duration + 0.005);
+ }
+
+ /**
+ * Sevastopol Electrical Conduit Spark Transient
+ * High-voltage electrical discharge arcs across damaged station bulkheads
+ */
+ synthesizeSevastopolSpark() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const bursts = 2 + Math.floor(Math.random() * 2);
+
+ for (let i = 0; i < bursts; i++) {
+ const offset = i * (0.015 + Math.random() * 0.02);
+ const t = now + offset;
+ const dur = 0.008 + Math.random() * 0.008;
+
+ const noiseBuf = this.am.createNoiseBuffer('white', 0.05);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(3600 + Math.random() * 800, t);
+ hp.Q.setValueAtTime(4.5, t);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.45, t);
+ env.gain.exponentialRampToValueAtTime(0.001, t + dur);
+
+ noise.connect(hp);
+ hp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(t);
+ noise.stop(t + dur + 0.005);
+ }
+ }
+
+ /**
+ * Moonbase Alpha Commlock Calling Tone (Space: 1999)
+ * Dual square wave pulse sequence (1100 Hz / 1500 Hz)
+ */
+ synthesizeCommlockTone() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Beep 1: 1100 Hz, 60 ms
+ const osc1 = ctx.createOscillator();
+ osc1.type = 'square';
+ osc1.frequency.setValueAtTime(1100, now);
+
+ const env1 = ctx.createGain();
+ env1.gain.setValueAtTime(0.001, now);
+ env1.gain.linearRampToValueAtTime(0.24, now + 0.004);
+ env1.gain.setValueAtTime(0.24, now + 0.055);
+ env1.gain.exponentialRampToValueAtTime(0.001, now + 0.062);
+
+ osc1.connect(env1);
+ env1.connect(this.gainNode);
+ osc1.start(now);
+ osc1.stop(now + 0.065);
+
+ // Beep 2: 1500 Hz, 75 ms starting at +0.082s
+ const t2 = now + 0.082;
+ const osc2 = ctx.createOscillator();
+ osc2.type = 'square';
+ osc2.frequency.setValueAtTime(1500, t2);
+
+ const env2 = ctx.createGain();
+ env2.gain.setValueAtTime(0.001, t2);
+ env2.gain.linearRampToValueAtTime(0.24, t2 + 0.004);
+ env2.gain.setValueAtTime(0.24, t2 + 0.07);
+ env2.gain.exponentialRampToValueAtTime(0.001, t2 + 0.078);
+
+ osc2.connect(env2);
+ env2.connect(this.gainNode);
+ osc2.start(t2);
+ osc2.stop(t2 + 0.082);
+ }
+
+ /**
+ * Gateway Station Medical Vital Telemetry Pip (Aliens)
+ * Clinical sterile 950 Hz pure sine pip (35 ms decay)
+ */
+ synthesizeStationMedicalPing() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.038;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(950, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.3, now + 0.003);
+ env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ osc.stop(now + duration + 0.005);
+ }
+
+ /**
+ * Solar Radiation / Heat Shield Roar (Icarus II / Sunshine)
+ * Terrifying lowpass brown noise roar with amplitude swell and crackle impulses
+ */
+ synthesizeSolarRoar() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 3.6;
+
+ // Lowpass brown noise body
+ const noiseBuf = this.am.createNoiseBuffer('brown', duration + 0.2);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(120, now);
+ lp.frequency.linearRampToValueAtTime(180, now + 1.5);
+ lp.frequency.exponentialRampToValueAtTime(90, now + duration);
+ lp.Q.setValueAtTime(2.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.72, now + 1.4);
+ env.gain.setValueAtTime(0.70, now + 2.2);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + duration + 0.05);
+
+ // Highpass solar crackle layer
+ const crackleBuf = this.am.createNoiseBuffer('pink', duration);
+ const crackle = ctx.createBufferSource();
+ crackle.buffer = crackleBuf;
+
+ const hp = ctx.createBiquadFilter();
+ hp.type = 'highpass';
+ hp.frequency.setValueAtTime(3200, now);
+ hp.Q.setValueAtTime(4.0, now);
+
+ const crackleEnv = ctx.createGain();
+ crackleEnv.gain.setValueAtTime(0.001, now);
+ crackleEnv.gain.linearRampToValueAtTime(0.08, now + 1.2);
+ crackleEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ crackle.connect(hp);
+ hp.connect(crackleEnv);
+ crackleEnv.connect(this.gainNode);
+
+ crackle.start(now);
+ crackle.stop(now + duration);
+ }
+
+ /**
+ * Cryogenic Pod Depressurization Sigh (Avalon / Ark One)
+ * Gas pressure relief with exponential filter decay followed by 205 Hz seal hum
+ */
+ synthesizeCryoDepressurize() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.35;
+
+ // Pneumatic gas depressurization sigh
+ const noiseBuf = this.am.createNoiseBuffer('white', 1.4);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(1800, now);
+ lp.frequency.exponentialRampToValueAtTime(280, now + 1.1);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.38, now + 0.06);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 1.15);
+
+ noise.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + 1.2);
+
+ // Quiet motorized seal hum
+ const hum = ctx.createOscillator();
+ hum.type = 'sine';
+ hum.frequency.setValueAtTime(205, now + 0.35);
+
+ const humEnv = ctx.createGain();
+ humEnv.gain.setValueAtTime(0.001, now + 0.35);
+ humEnv.gain.linearRampToValueAtTime(0.18, now + 0.55);
+ humEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ hum.connect(humEnv);
+ humEnv.connect(this.gainNode);
+
+ hum.start(now + 0.35);
+ hum.stop(now + duration + 0.02);
+ }
+
+ /**
+ * Mid-Century Orion Nuclear Pulse Thump (USS Ascension)
+ * Heavy 45 Hz lowpass square impulse with sub-bass body resonance
+ */
+ synthesizeOrionPulseThump() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.22;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(45, now);
+ osc.frequency.exponentialRampToValueAtTime(24, now + 0.09);
+
+ const lp = ctx.createBiquadFilter();
+ lp.type = 'lowpass';
+ lp.frequency.setValueAtTime(140, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.65, now);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 0.085);
+
+ osc.connect(lp);
+ lp.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ osc.stop(now + 0.09);
+
+ const sub = ctx.createOscillator();
+ sub.type = 'sine';
+ sub.frequency.setValueAtTime(36, now);
+
+ const subEnv = ctx.createGain();
+ subEnv.gain.setValueAtTime(0.001, now);
+ subEnv.gain.linearRampToValueAtTime(0.55, now + 0.01);
+ subEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
+
+ sub.connect(subEnv);
+ subEnv.connect(this.gainNode);
+
+ sub.start(now);
+ sub.stop(now + duration);
+ }
+
+ /**
+ * Nutri-Matic Dedicated Tea Dispenser Gurgle (Heart of Gold)
+ * Bandpass bubbling noise hops (500–1000 Hz) terminating in a short steam hiss
+ */
+ synthesizeTeaDispenser() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Bubbling hops
+ const bubbleFreqs = [520, 840, 610, 960, 480, 730];
+ bubbleFreqs.forEach((freq, i) => {
+ const t = now + i * 0.11;
+ const bDur = 0.09;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', 0.12);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(freq, t);
+ bp.Q.setValueAtTime(6.0, t);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.35, t + 0.015);
+ env.gain.exponentialRampToValueAtTime(0.001, t + bDur);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(t);
+ noise.stop(t + bDur + 0.01);
+ });
+
+ // Steam hiss
+ const steamTime = now + 0.65;
+ const steamBuf = this.am.createNoiseBuffer('white', 0.45);
+ const steam = ctx.createBufferSource();
+ steam.buffer = steamBuf;
+
+ const steamFilter = ctx.createBiquadFilter();
+ steamFilter.type = 'bandpass';
+ steamFilter.frequency.setValueAtTime(2400, steamTime);
+ steamFilter.Q.setValueAtTime(3.0, steamTime);
+
+ const steamEnv = ctx.createGain();
+ steamEnv.gain.setValueAtTime(0.001, steamTime);
+ steamEnv.gain.linearRampToValueAtTime(0.28, steamTime + 0.04);
+ steamEnv.gain.exponentialRampToValueAtTime(0.001, steamTime + 0.42);
+
+ steam.connect(steamFilter);
+ steamFilter.connect(steamEnv);
+ steamEnv.connect(this.gainNode);
+
+ steam.start(steamTime);
+ steam.stop(steamTime + 0.45);
+ }
+
+ /**
+ * British Electric Kettle Steam Whistle (HMS Camden Lock / Hyperdrive)
+ * Narrow bandpass sine sweeping 1820 Hz to 2380 Hz with steady tremolo
+ */
+ synthesizeKettleWhistle() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 1.6;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(1820, now);
+ osc.frequency.exponentialRampToValueAtTime(2380, now + duration * 0.7);
+ osc.frequency.linearRampToValueAtTime(2320, now + duration);
+
+ const tremolo = ctx.createOscillator();
+ tremolo.type = 'sine';
+ tremolo.frequency.setValueAtTime(5.2, now);
+
+ const tremGain = ctx.createGain();
+ tremGain.gain.setValueAtTime(0.15, now);
+ tremolo.connect(tremGain);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.26, now + 0.25);
+ env.gain.setValueAtTime(0.26, now + 1.2);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ tremGain.connect(env.gain);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ tremolo.start(now);
+ osc.stop(now + duration);
+ tremolo.stop(now + duration);
+ }
+
+ /**
+ * Ludicrous Speed Plaid Alarm (Spaceball One)
+ * Two-tone alternating square wave siren (440 Hz / 880 Hz)
+ */
+ synthesizePlaidAlarm() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const steps = 6;
+ const stepDur = 0.12;
+
+ for (let i = 0; i < steps; i++) {
+ const t = now + i * stepDur;
+ const freq = (i % 2 === 0) ? 440 : 880;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(freq, t);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, t);
+ env.gain.linearRampToValueAtTime(0.28, t + 0.006);
+ env.gain.setValueAtTime(0.28, t + stepDur - 0.01);
+ env.gain.exponentialRampToValueAtTime(0.001, t + stepDur);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(t);
+ osc.stop(t + stepDur + 0.005);
+ }
+ }
+
+ /**
+ * Dedicated Cassette Transport Clunk & Whirr (Milano / Bebop)
+ * Dual square transient click (120 Hz / 360 Hz) followed by 2200 Hz capstan tone with flutter
+ */
+ synthesizeCassetteTransport() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Dual square mechanical button clunk
+ [120, 360].forEach((freq) => {
+ const osc = ctx.createOscillator();
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(freq, now);
+ osc.frequency.exponentialRampToValueAtTime(40, now + 0.028);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.35, now);
+ env.gain.exponentialRampToValueAtTime(0.001, now + 0.03);
+
+ osc.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ osc.stop(now + 0.032);
+ });
+
+ // 12V capstan motor tone + tape flutter
+ const motorTime = now + 0.04;
+ const motorDur = 0.95;
+
+ const motor = ctx.createOscillator();
+ motor.type = 'sine';
+ motor.frequency.setValueAtTime(2200, motorTime);
+
+ const flutter = ctx.createOscillator();
+ flutter.type = 'sine';
+ flutter.frequency.setValueAtTime(8.2, motorTime);
+
+ const flutGain = ctx.createGain();
+ flutGain.gain.setValueAtTime(35, motorTime);
+ flutter.connect(flutGain);
+ flutGain.connect(motor.frequency);
+
+ const motorEnv = ctx.createGain();
+ motorEnv.gain.setValueAtTime(0.001, motorTime);
+ motorEnv.gain.linearRampToValueAtTime(0.12, motorTime + 0.08);
+ motorEnv.gain.exponentialRampToValueAtTime(0.001, motorTime + motorDur);
+
+ motor.connect(motorEnv);
+ motorEnv.connect(this.gainNode);
+
+ motor.start(motorTime);
+ flutter.start(motorTime);
+ motor.stop(motorTime + motorDur);
+ flutter.stop(motorTime + motorDur);
+
+ // Tape noise bed
+ const tapeBuf = this.am.createNoiseBuffer('pink', motorDur);
+ const tape = ctx.createBufferSource();
+ tape.buffer = tapeBuf;
+
+ const tapeFilter = ctx.createBiquadFilter();
+ tapeFilter.type = 'bandpass';
+ tapeFilter.frequency.setValueAtTime(3200, motorTime);
+ tapeFilter.Q.setValueAtTime(2.0, motorTime);
+
+ const tapeEnv = ctx.createGain();
+ tapeEnv.gain.setValueAtTime(0.001, motorTime);
+ tapeEnv.gain.linearRampToValueAtTime(0.08, motorTime + 0.05);
+ tapeEnv.gain.exponentialRampToValueAtTime(0.001, motorTime + motorDur);
+
+ tape.connect(tapeFilter);
+ tapeFilter.connect(tapeEnv);
+ tapeEnv.connect(this.gainNode);
+
+ tape.start(motorTime);
+ tape.stop(motorTime + motorDur);
+ }
+
+ /**
+ * Grappler Arm Servo Motor Whine (Outlaw Star / Bebop)
+ * Swept triangle wave (220 Hz -> 680 Hz) through resonant bandpass filter
+ */
+ synthesizeGrapplerServo() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.44;
+
+ const osc = ctx.createOscillator();
+ osc.type = 'triangle';
+ osc.frequency.setValueAtTime(220, now);
+ osc.frequency.exponentialRampToValueAtTime(680, now + duration * 0.7);
+ osc.frequency.linearRampToValueAtTime(540, now + duration);
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(320, now);
+ bp.frequency.exponentialRampToValueAtTime(820, now + duration * 0.7);
+ bp.Q.setValueAtTime(3.2, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.001, now);
+ env.gain.linearRampToValueAtTime(0.38, now + 0.04);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ osc.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(now);
+ osc.stop(now + duration + 0.01);
+ }
+
+ /**
+ * True RF Radio Static Burst (The Betty / The Marauder)
+ * Bandpass-filtered pink/white noise burst (300–3200 Hz) with hard-knee square gate
+ */
+ synthesizeRadioStatic() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+ const duration = 0.18;
+
+ const noiseBuf = this.am.createNoiseBuffer('pink', 0.25);
+ const noise = ctx.createBufferSource();
+ noise.buffer = noiseBuf;
+
+ const bp = ctx.createBiquadFilter();
+ bp.type = 'bandpass';
+ bp.frequency.setValueAtTime(1400, now);
+ bp.Q.setValueAtTime(1.1, now);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.42, now);
+ env.gain.setValueAtTime(0.40, now + duration - 0.02);
+ env.gain.exponentialRampToValueAtTime(0.001, now + duration);
+
+ noise.connect(bp);
+ bp.connect(env);
+ env.connect(this.gainNode);
+
+ noise.start(now);
+ noise.stop(now + duration + 0.01);
+ }
+
+ /**
+ * Magnetic Boot ("Mag-Boot") Latch (Ceres / Tycho / The Expanse)
+ * Low-frequency impact thump (80 Hz click) followed by 120 Hz inductive clamp buzz
+ */
+ synthesizeMagBootLatch() {
+ this.init();
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return;
+
+ const now = ctx.currentTime;
+
+ // Stage 1: Impact thump
+ const thump = ctx.createOscillator();
+ thump.type = 'triangle';
+ thump.frequency.setValueAtTime(80, now);
+ thump.frequency.exponentialRampToValueAtTime(32, now + 0.03);
+
+ const thumpEnv = ctx.createGain();
+ thumpEnv.gain.setValueAtTime(0.5, now);
+ thumpEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.035);
+
+ thump.connect(thumpEnv);
+ thumpEnv.connect(this.gainNode);
+ thump.start(now);
+ thump.stop(now + 0.038);
+
+ // Stage 2: 120 Hz inductive clamping buzz
+ const buzzTime = now + 0.025;
+ const buzz = ctx.createOscillator();
+ buzz.type = 'square';
+ buzz.frequency.setValueAtTime(120, buzzTime);
+
+ const buzzFilter = ctx.createBiquadFilter();
+ buzzFilter.type = 'lowpass';
+ buzzFilter.frequency.setValueAtTime(420, buzzTime);
+
+ const buzzEnv = ctx.createGain();
+ buzzEnv.gain.setValueAtTime(0.001, buzzTime);
+ buzzEnv.gain.linearRampToValueAtTime(0.32, buzzTime + 0.01);
+ buzzEnv.gain.exponentialRampToValueAtTime(0.001, buzzTime + 0.16);
+
+ buzz.connect(buzzFilter);
+ buzzFilter.connect(buzzEnv);
+ buzzEnv.connect(this.gainNode);
+
+ buzz.start(buzzTime);
+ buzz.stop(buzzTime + 0.17);
+ }
+
+ /**
+ * Background Loop: Periodic Medical Vital Signs Monitor (ECG)
+ */
+ startMedicalMonitor(intervalSeconds = 1.1) {
+ this.stopMedicalMonitor();
+ const tick = () => {
+ this.synthesizeMedicalMonitor();
+ this.medicalTimer = setTimeout(tick, intervalSeconds * 1000);
+ };
+ tick();
+ }
+
+ stopMedicalMonitor() {
+ if (this.medicalTimer) {
+ clearTimeout(this.medicalTimer);
+ this.medicalTimer = null;
+ }
+ }
+
+ /**
+ * Background Loop: Random Electrical Sparks on Damaged Stations
+ */
+ startStationSparks(minIntervalMs = 4000, maxIntervalMs = 12000) {
+ this.stopStationSparks();
+ const scheduleNext = () => {
+ const delay = minIntervalMs + Math.random() * (maxIntervalMs - minIntervalMs);
+ this.sparkTimer = setTimeout(() => {
+ this.synthesizeSevastopolSpark();
+ scheduleNext();
+ }, delay);
+ };
+ scheduleNext();
+ }
+
+ stopStationSparks() {
+ if (this.sparkTimer) {
+ clearTimeout(this.sparkTimer);
+ this.sparkTimer = null;
+ }
+ }
+
+ stopAllLoops() {
+ this.stopMedicalMonitor();
+ this.stopStationSparks();
+ }
}
window.ExpandedSciFiAudioSynth = ExpandedSciFiAudioSynth;
/**
- * Canonical Starship & Location Sound Profiles Matrix
- * Each preset defines the exact parameters for Hull Drone, Warp Core, Life Support, and Telemetry.
+ * Layered & Staged Engine Startup and Shutdown Synthesizer
+ * Synthesizes chronological multi-phase acoustic scripts for 15 distinct starship archetypes.
*/
+class EngineTransitionSynth {
+ constructor(audioManager) {
+ this.am = audioManager;
+ this.gainNode = null;
+ this.activeNodes = [];
+ this.activeTimeouts = [];
+ }
+
+ init() {
+ if (!this.am.ctx) return;
+ if (!this.gainNode) {
+ this.gainNode = this.am.ctx.createGain();
+ this.gainNode.gain.setValueAtTime(0.85, this.am.ctx.currentTime);
+ this.gainNode.connect(this.am.compressor);
+ }
+ }
+
+ stopTransitions() {
+ this.activeTimeouts.forEach(t => clearTimeout(t));
+ this.activeTimeouts = [];
+ this.activeNodes.forEach(node => {
+ try { if (node.stop) node.stop(); } catch (e) {}
+ try { node.disconnect(); } catch (e) {}
+ });
+ this.activeNodes = [];
+ }
+
+ _tone({ type = 'sine', startFreq, endFreq, startTime, duration, startVol = 0.001, peakVol = 0.4, endVol = 0.0001, filterType = null, filterFreq = 1000, filterQ = 1.0 }) {
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return null;
+ const osc = ctx.createOscillator();
+ osc.type = type;
+ osc.frequency.setValueAtTime(Math.max(10, startFreq), startTime);
+ if (endFreq && endFreq !== startFreq) {
+ osc.frequency.exponentialRampToValueAtTime(Math.max(10, endFreq), startTime + duration);
+ }
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(Math.max(0.0001, startVol), startTime);
+ const attack = Math.min(0.08, duration * 0.25);
+ env.gain.linearRampToValueAtTime(peakVol, startTime + attack);
+ env.gain.exponentialRampToValueAtTime(Math.max(0.0001, endVol), startTime + duration);
+
+ let lastNode = osc;
+ if (filterType) {
+ const filter = ctx.createBiquadFilter();
+ filter.type = filterType;
+ filter.frequency.setValueAtTime(filterFreq, startTime);
+ filter.Q.setValueAtTime(filterQ, startTime);
+ lastNode.connect(filter);
+ lastNode = filter;
+ this.activeNodes.push(filter);
+ }
+
+ lastNode.connect(env);
+ env.connect(this.gainNode);
+
+ osc.start(startTime);
+ osc.stop(startTime + duration);
+
+ this.activeNodes.push(osc, env);
+ return osc;
+ }
+
+ _noise({ noiseType = 'pink', filterType = 'bandpass', startFreq = 800, endFreq = 800, Q = 1.5, startTime, duration, peakVol = 0.35 }) {
+ const ctx = this.am.ctx;
+ if (!ctx || !this.gainNode) return null;
+ const buf = this.am.createNoiseBuffer(noiseType, Math.max(3, Math.ceil(duration + 1)));
+ const src = ctx.createBufferSource();
+ src.buffer = buf;
+
+ const filter = ctx.createBiquadFilter();
+ filter.type = filterType;
+ filter.frequency.setValueAtTime(Math.max(20, startFreq), startTime);
+ if (endFreq && endFreq !== startFreq) {
+ filter.frequency.exponentialRampToValueAtTime(Math.max(20, endFreq), startTime + duration);
+ }
+ filter.Q.setValueAtTime(Q, startTime);
+
+ const env = ctx.createGain();
+ env.gain.setValueAtTime(0.0001, startTime);
+ const attack = Math.min(0.12, duration * 0.3);
+ env.gain.linearRampToValueAtTime(peakVol, startTime + attack);
+ env.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);
+
+ src.connect(filter);
+ filter.connect(env);
+ env.connect(this.gainNode);
+
+ src.start(startTime);
+ src.stop(startTime + duration);
+
+ this.activeNodes.push(src, filter, env);
+ return src;
+ }
+
+ _click(startTime, freq = 140, duration = 0.035, vol = 0.4, type = 'square') {
+ return this._tone({ type, startFreq: freq, endFreq: 25, startTime, duration, peakVol: vol, endVol: 0.0001 });
+ }
+
+ _sub(startTime, startFreq = 70, endFreq = 30, duration = 0.8, vol = 0.6) {
+ return this._tone({ type: 'sine', startFreq, endFreq, startTime, duration, peakVol: vol, endVol: 0.0001 });
+ }
+
+ _chime(startTime, pitches = [880, 1174, 1567], step = 0.05, duration = 0.35, vol = 0.25) {
+ pitches.forEach((freq, idx) => {
+ const t = startTime + idx * step;
+ this._tone({ type: 'sine', startFreq: freq, endFreq: freq * 1.01, startTime: t, duration, peakVol: vol, endVol: 0.0001 });
+ });
+ }
+
+ playStartup(profileKey = 'galaxy', duration = 2.5) {
+ this.init();
+ if (!this.am.ctx) return;
+ this.stopTransitions();
+ const now = this.am.ctx.currentTime;
+ const s = duration / 2.5;
+
+ switch (profileKey) {
+ case 'intrepid': this._startupIntrepid(now, s); break;
+ case 'defiant': this._startupDefiant(now, s); break;
+ case 'cardassian': this._startupCardassian(now, s); break;
+ case 'tos': this._startupTOS(now, s); break;
+ case 'nx': this._startupNX(now, s); break;
+ case 'tardis': this._startupTardis(now, s); break;
+ case 'industrial': this._startupIndustrial(now, s); break;
+ case 'bioship': this._startupBioship(now, s); break;
+ case 'retrofuture': this._startupRetrofuture(now, s); break;
+ case 'military': this._startupMilitary(now, s); break;
+ case 'deepspace': this._startupDeepSpace(now, s); break;
+ case 'outlaw': this._startupOutlaw(now, s); break;
+ case 'station': this._startupStation(now, s); break;
+ case 'comedy': this._startupComedy(now, s); break;
+ case 'galaxy':
+ default:
+ this._startupGalaxy(now, s);
+ break;
+ }
+ }
+
+ playShutdown(profileKey = 'galaxy', duration = 2.5) {
+ this.init();
+ if (!this.am.ctx) return;
+ this.stopTransitions();
+ const now = this.am.ctx.currentTime;
+ const s = duration / 2.5;
+
+ switch (profileKey) {
+ case 'intrepid': this._shutdownIntrepid(now, s); break;
+ case 'defiant': this._shutdownDefiant(now, s); break;
+ case 'cardassian': this._shutdownCardassian(now, s); break;
+ case 'tos': this._shutdownTOS(now, s); break;
+ case 'nx': this._shutdownNX(now, s); break;
+ case 'tardis': this._shutdownTardis(now, s); break;
+ case 'industrial': this._shutdownIndustrial(now, s); break;
+ case 'bioship': this._shutdownBioship(now, s); break;
+ case 'retrofuture': this._shutdownRetrofuture(now, s); break;
+ case 'military': this._shutdownMilitary(now, s); break;
+ case 'deepspace': this._shutdownDeepSpace(now, s); break;
+ case 'outlaw': this._shutdownOutlaw(now, s); break;
+ case 'station': this._shutdownStation(now, s); break;
+ case 'comedy': this._shutdownComedy(now, s); break;
+ case 'galaxy':
+ default:
+ this._shutdownGalaxy(now, s);
+ break;
+ }
+ }
+
+ // 1. TNG GALAXY CLASS
+ _startupGalaxy(now, s) {
+ this._click(now, 150, 0.04, 0.45);
+ this._click(now + 0.14 * s, 110, 0.03, 0.35);
+ this._tone({ type: 'sine', startFreq: 60, endFreq: 140, startTime: now + 0.05 * s, duration: 0.65 * s, peakVol: 0.35 });
+ this._tone({ type: 'sawtooth', startFreq: 120, endFreq: 480, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.28, filterType: 'lowpass', filterFreq: 750, filterQ: 3.5 });
+ this._sub(now + 0.85 * s, 65, 80, 0.95 * s, 0.55);
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 200, endFreq: 600, startTime: now + 0.9 * s, duration: 0.9 * s, peakVol: 0.25 });
+ this._chime(now + 1.8 * s, [784, 1046, 1318], 0.06 * s, 0.5 * s, 0.3);
+ this._sub(now + 1.8 * s, 80, 58, 0.65 * s, 0.45);
+ }
+ _shutdownGalaxy(now, s) {
+ this._click(now, 160, 0.05, 0.5);
+ this._sub(now, 85, 40, 0.65 * s, 0.6);
+ this._tone({ type: 'sawtooth', startFreq: 460, endFreq: 75, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.25, filterType: 'lowpass', filterFreq: 600, filterQ: 2.0 });
+ this._noise({ noiseType: 'pink', filterType: 'bandpass', startFreq: 650, endFreq: 180, startTime: now + 0.75 * s, duration: 1.0 * s, peakVol: 0.28 });
+ this._tone({ type: 'sine', startFreq: 75, endFreq: 24, startTime: now + 1.7 * s, duration: 0.75 * s, peakVol: 0.35 });
+ }
+
+ // 2. VOYAGER INTREPID CLASS
+ _startupIntrepid(now, s) {
+ this._click(now, 450, 0.025, 0.35);
+ this._click(now + 0.08 * s, 680, 0.025, 0.35);
+ this._tone({ type: 'triangle', startFreq: 240, endFreq: 580, startTime: now + 0.1 * s, duration: 0.6 * s, peakVol: 0.3 });
+ this._tone({ type: 'sawtooth', startFreq: 320, endFreq: 1650, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.35, filterType: 'bandpass', filterFreq: 1400, filterQ: 4.5 });
+ this._sub(now + 0.8 * s, 70, 95, 0.9 * s, 0.45);
+ this._noise({ noiseType: 'white', filterType: 'bandpass', startFreq: 1200, endFreq: 2400, startTime: now + 0.85 * s, duration: 0.9 * s, peakVol: 0.22 });
+ this._chime(now + 1.8 * s, [1174, 1567, 2093], 0.05 * s, 0.45 * s, 0.28);
+ this._tone({ type: 'sine', startFreq: 95, endFreq: 70, startTime: now + 1.85 * s, duration: 0.65 * s, peakVol: 0.4 });
+ }
+ _shutdownIntrepid(now, s) {
+ this._click(now, 520, 0.03, 0.4);
+ this._sub(now, 95, 50, 0.5 * s, 0.5);
+ this._tone({ type: 'sawtooth', startFreq: 1600, endFreq: 140, startTime: now + 0.5 * s, duration: 1.2 * s, peakVol: 0.28, filterType: 'lowpass', filterFreq: 1200 });
+ this._noise({ noiseType: 'white', filterType: 'highpass', startFreq: 1400, endFreq: 400, startTime: now + 0.6 * s, duration: 0.8 * s, peakVol: 0.25 });
+ this._tone({ type: 'sine', startFreq: 140, endFreq: 30, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.25 });
+ }
+
+ // 3. DEFIANT ESCORT
+ _startupDefiant(now, s) {
+ this._click(now, 90, 0.06, 0.6, 'square');
+ this._tone({ type: 'sawtooth', startFreq: 50, endFreq: 120, startTime: now + 0.05 * s, duration: 0.6 * s, peakVol: 0.4, filterType: 'lowpass', filterFreq: 300 });
+ this._sub(now + 0.65 * s, 35, 95, 1.1 * s, 0.7);
+ this._tone({ type: 'sawtooth', startFreq: 180, endFreq: 740, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.32, filterType: 'bandpass', filterFreq: 550, filterQ: 3.0 });
+ this._chime(now + 1.8 * s, [660, 880], 0.08 * s, 0.4 * s, 0.35);
+ this._sub(now + 1.8 * s, 95, 68, 0.7 * s, 0.5);
+ }
+ _shutdownDefiant(now, s) {
+ this._click(now, 110, 0.05, 0.6);
+ this._sub(now, 90, 35, 0.6 * s, 0.65);
+ this._tone({ type: 'sawtooth', startFreq: 720, endFreq: 60, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.3, filterType: 'lowpass', filterFreq: 450 });
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 500, endFreq: 120, startTime: now + 0.7 * s, duration: 1.0 * s, peakVol: 0.3 });
+ this._tone({ type: 'sine', startFreq: 60, endFreq: 22, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.35 });
+ }
+
+ // 4. CARDASSIAN / DS9
+ _startupCardassian(now, s) {
+ this._click(now, 85, 0.08, 0.55);
+ this._tone({ type: 'triangle', startFreq: 165, endFreq: 110, startTime: now, duration: 0.7 * s, peakVol: 0.4 });
+ this._sub(now + 0.7 * s, 32, 68, 1.1 * s, 0.65);
+ this._tone({ type: 'sawtooth', startFreq: 80, endFreq: 260, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.3, filterType: 'lowpass', filterFreq: 320 });
+ this._noise({ noiseType: 'brown', filterType: 'lowpass', startFreq: 180, endFreq: 350, startTime: now + 0.8 * s, duration: 1.0 * s, peakVol: 0.3 });
+ this._chime(now + 1.8 * s, [330, 440], 0.1 * s, 0.5 * s, 0.35);
+ }
+ _shutdownCardassian(now, s) {
+ this._click(now, 95, 0.06, 0.5);
+ this._sub(now, 68, 30, 0.6 * s, 0.6);
+ this._tone({ type: 'sawtooth', startFreq: 250, endFreq: 40, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.25, filterType: 'lowpass', filterFreq: 220 });
+ this._noise({ noiseType: 'brown', filterType: 'bandpass', startFreq: 240, endFreq: 80, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.25 });
+ this._tone({ type: 'sine', startFreq: 40, endFreq: 20, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.3 });
+ }
+
+ // 5. TOS 1960s
+ _startupTOS(now, s) {
+ this._click(now, 220, 0.03, 0.45);
+ this._tone({ type: 'sine', startFreq: 80, endFreq: 180, startTime: now + 0.04 * s, duration: 0.6 * s, peakVol: 0.35 });
+ this._tone({ type: 'sine', startFreq: 220, endFreq: 980, startTime: now + 0.65 * s, duration: 1.15 * s, peakVol: 0.32 });
+ this._tone({ type: 'triangle', startFreq: 225, endFreq: 990, startTime: now + 0.65 * s, duration: 1.15 * s, peakVol: 0.2 });
+ this._chime(now + 1.8 * s, [880, 1108], 0.08 * s, 0.35 * s, 0.3);
+ }
+ _shutdownTOS(now, s) {
+ this._click(now, 260, 0.035, 0.45);
+ this._tone({ type: 'sine', startFreq: 950, endFreq: 90, startTime: now + 0.5 * s, duration: 1.2 * s, peakVol: 0.3 });
+ this._tone({ type: 'sine', startFreq: 2400, endFreq: 400, startTime: now + 0.6 * s, duration: 0.8 * s, peakVol: 0.15 });
+ this._tone({ type: 'sine', startFreq: 90, endFreq: 25, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.25 });
+ }
+
+ // 6. NX-01 PROTOTYPE
+ _startupNX(now, s) {
+ [0, 0.12, 0.24, 0.36, 0.48].forEach(dt => this._click(now + dt * s, 110, 0.04, 0.45));
+ this._tone({ type: 'sawtooth', startFreq: 95, endFreq: 420, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.32, filterType: 'lowpass', filterFreq: 600 });
+ this._sub(now + 0.8 * s, 45, 75, 1.0 * s, 0.6);
+ this._noise({ noiseType: 'brown', filterType: 'lowpass', startFreq: 220, endFreq: 480, startTime: now + 0.85 * s, duration: 0.95 * s, peakVol: 0.3 });
+ this._click(now + 1.8 * s, 180, 0.03, 0.4);
+ }
+ _shutdownNX(now, s) {
+ this._click(now, 95, 0.06, 0.55);
+ this._sub(now, 75, 35, 0.6 * s, 0.6);
+ [0.6, 0.85, 1.15, 1.5].forEach(dt => this._click(now + dt * s, 85, 0.04, 0.35));
+ this._noise({ noiseType: 'pink', filterType: 'highpass', startFreq: 900, endFreq: 300, startTime: now + 0.7 * s, duration: 1.0 * s, peakVol: 0.25 });
+ this._tone({ type: 'sine', startFreq: 40, endFreq: 18, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.3 });
+ }
+
+ // 7. TARDIS DIMENSIONAL
+ _startupTardis(now, s) {
+ this._click(now, 80, 0.06, 0.55);
+ this._tone({ type: 'sawtooth', startFreq: 140, endFreq: 680, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.32, filterType: 'bandpass', filterFreq: 550, filterQ: 3.0 });
+ this._tone({ type: 'sine', startFreq: 70, endFreq: 220, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.35 });
+ this._chime(now + 1.8 * s, [1046, 1318, 1567], 0.06 * s, 0.45 * s, 0.3);
+ }
+ _shutdownTardis(now, s) {
+ this._click(now, 90, 0.05, 0.5);
+ this._tone({ type: 'sawtooth', startFreq: 620, endFreq: 75, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.28, filterType: 'bandpass', filterFreq: 350, filterQ: 2.0 });
+ this._chime(now + 0.8 * s, [440], 0.1, 0.7 * s, 0.3);
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 350, endFreq: 80, startTime: now + 1.6 * s, duration: 0.9 * s, peakVol: 0.2 });
+ }
+
+ // 8. INDUSTRIAL FREIGHTER
+ _startupIndustrial(now, s) {
+ this._click(now, 75, 0.08, 0.6);
+ this._noise({ noiseType: 'white', filterType: 'highpass', startFreq: 1800, endFreq: 800, startTime: now + 0.05 * s, duration: 0.4 * s, peakVol: 0.35 });
+ this._tone({ type: 'sawtooth', startFreq: 55, endFreq: 290, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.35, filterType: 'lowpass', filterFreq: 400 });
+ this._sub(now + 0.7 * s, 35, 65, 1.1 * s, 0.7);
+ this._noise({ noiseType: 'brown', filterType: 'bandpass', startFreq: 400, endFreq: 900, startTime: now + 0.8 * s, duration: 1.0 * s, peakVol: 0.3 });
+ this._sub(now + 1.8 * s, 65, 40, 0.7 * s, 0.6);
+ }
+ _shutdownIndustrial(now, s) {
+ this._click(now, 85, 0.07, 0.65);
+ this._sub(now, 65, 28, 0.6 * s, 0.6);
+ this._noise({ noiseType: 'white', filterType: 'lowpass', startFreq: 2400, endFreq: 400, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.38 });
+ this._tone({ type: 'sawtooth', startFreq: 280, endFreq: 40, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.25, filterType: 'lowpass', filterFreq: 300 });
+ [1.7, 1.95, 2.2].forEach(dt => this._click(now + dt * s, 320, 0.02, 0.25));
+ }
+
+ // 9. BIOSHIP
+ _startupBioship(now, s) {
+ this._chime(now, [784, 1174, 1567], 0.04 * s, 0.3 * s, 0.25);
+ this._sub(now + 0.6 * s, 42, 28, 0.4 * s, 0.65);
+ this._sub(now + 0.95 * s, 44, 28, 0.4 * s, 0.7);
+ this._tone({ type: 'sine', startFreq: 260, endFreq: 540, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.32 });
+ this._noise({ noiseType: 'pink', filterType: 'bandpass', startFreq: 250, endFreq: 600, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.3 });
+ }
+ _shutdownBioship(now, s) {
+ this._noise({ noiseType: 'pink', filterType: 'bandpass', startFreq: 550, endFreq: 180, startTime: now, duration: 1.1 * s, peakVol: 0.35 });
+ this._sub(now + 0.5 * s, 38, 25, 0.4 * s, 0.55);
+ this._sub(now + 1.1 * s, 34, 22, 0.4 * s, 0.45);
+ this._tone({ type: 'sine', startFreq: 480, endFreq: 180, startTime: now + 0.6 * s, duration: 1.1 * s, peakVol: 0.22 });
+ this._tone({ type: 'sine', startFreq: 35, endFreq: 18, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.25 });
+ }
+
+ // 10. RETROFUTURE
+ _startupRetrofuture(now, s) {
+ [0, 0.07, 0.14, 0.21].forEach(dt => this._click(now + dt * s, 380, 0.02, 0.3));
+ this._tone({ type: 'triangle', startFreq: 95, endFreq: 580, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.35, filterType: 'lowpass', filterFreq: 650 });
+ this._tone({ type: 'sine', startFreq: 100, endFreq: 590, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.25 });
+ this._chime(now + 1.8 * s, [920], 0.1, 0.5 * s, 0.35);
+ }
+ _shutdownRetrofuture(now, s) {
+ this._click(now, 140, 0.05, 0.55);
+ this._tone({ type: 'triangle', startFreq: 550, endFreq: 65, startTime: now + 0.5 * s, duration: 1.2 * s, peakVol: 0.28 });
+ this._noise({ noiseType: 'pink', filterType: 'bandpass', startFreq: 450, endFreq: 150, startTime: now + 0.6 * s, duration: 1.1 * s, peakVol: 0.22 });
+ this._tone({ type: 'sine', startFreq: 3200, endFreq: 120, startTime: now + 1.7 * s, duration: 0.7 * s, peakVol: 0.2 });
+ }
+
+ // 11. MILITARY
+ _startupMilitary(now, s) {
+ this._click(now, 120, 0.06, 0.6);
+ this._tone({ type: 'square', startFreq: 60, endFreq: 120, startTime: now, duration: 0.6 * s, peakVol: 0.35, filterType: 'lowpass', filterFreq: 250 });
+ this._tone({ type: 'sawtooth', startFreq: 140, endFreq: 880, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.35, filterType: 'bandpass', filterFreq: 750, filterQ: 3.0 });
+ this._sub(now + 0.7 * s, 40, 85, 1.1 * s, 0.65);
+ this._chime(now + 1.8 * s, [750, 750], 0.08 * s, 0.35 * s, 0.35);
+ }
+ _shutdownMilitary(now, s) {
+ this._click(now, 150, 0.04, 0.5);
+ this._chime(now + 0.05 * s, [880], 0.1, 0.3 * s, 0.35);
+ this._tone({ type: 'sawtooth', startFreq: 850, endFreq: 70, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.28, filterType: 'lowpass', filterFreq: 500 });
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 600, endFreq: 140, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.28 });
+ this._sub(now + 1.7 * s, 70, 25, 0.8 * s, 0.4);
+ }
+
+ // 12. DEEP SPACE
+ _startupDeepSpace(now, s) {
+ this._noise({ noiseType: 'white', filterType: 'bandpass', startFreq: 2400, endFreq: 1200, startTime: now, duration: 0.65 * s, peakVol: 0.32 });
+ this._sub(now + 0.7 * s, 28, 68, 1.1 * s, 0.7);
+ this._tone({ type: 'sine', startFreq: 110, endFreq: 280, startTime: now + 0.75 * s, duration: 1.1 * s, peakVol: 0.3 });
+ this._chime(now + 1.8 * s, [440, 554], 0.1 * s, 0.5 * s, 0.25);
+ }
+ _shutdownDeepSpace(now, s) {
+ this._sub(now, 68, 25, 0.7 * s, 0.65);
+ this._tone({ type: 'sine', startFreq: 260, endFreq: 50, startTime: now + 0.6 * s, duration: 1.2 * s, peakVol: 0.25 });
+ this._noise({ noiseType: 'brown', filterType: 'lowpass', startFreq: 200, endFreq: 40, startTime: now + 0.7 * s, duration: 1.1 * s, peakVol: 0.3 });
+ this._sub(now + 1.7 * s, 35, 15, 0.8 * s, 0.25);
+ }
+
+ // 13. OUTLAW
+ _startupOutlaw(now, s) {
+ [0, 0.1, 0.22, 0.32, 0.44].forEach(dt => this._click(now + dt * s, 130 + Math.random() * 40, 0.035, 0.4));
+ this._tone({ type: 'sawtooth', startFreq: 70, endFreq: 130, startTime: now + 0.1 * s, duration: 0.5 * s, peakVol: 0.3, filterType: 'lowpass', filterFreq: 280 });
+ this._tone({ type: 'sawtooth', startFreq: 180, endFreq: 1150, startTime: now + 0.65 * s, duration: 1.15 * s, peakVol: 0.35, filterType: 'bandpass', filterFreq: 900, filterQ: 4.0 });
+ this._sub(now + 0.7 * s, 45, 80, 1.1 * s, 0.65);
+ this._noise({ noiseType: 'white', filterType: 'highpass', startFreq: 2800, endFreq: 1500, startTime: now + 1.8 * s, duration: 0.3 * s, peakVol: 0.35 });
+ }
+ _shutdownOutlaw(now, s) {
+ this._click(now, 70, 0.08, 0.7, 'sawtooth');
+ this._sub(now, 90, 40, 0.4 * s, 0.7);
+ [0.5, 0.8, 1.15, 1.55].forEach(dt => this._click(now + dt * s, 90, 0.04, 0.35));
+ this._tone({ type: 'sawtooth', startFreq: 750, endFreq: 55, startTime: now + 0.5 * s, duration: 1.3 * s, peakVol: 0.25, filterType: 'lowpass', filterFreq: 350 });
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 350, endFreq: 80, startTime: now + 1.7 * s, duration: 0.8 * s, peakVol: 0.25 });
+ }
+
+ // 14. STATION
+ _startupStation(now, s) {
+ this._chime(now, [587, 880], 0.08 * s, 0.35 * s, 0.3);
+ this._click(now + 0.15 * s, 110, 0.05, 0.45);
+ this._sub(now + 0.6 * s, 30, 65, 1.2 * s, 0.7);
+ this._tone({ type: 'sawtooth', startFreq: 70, endFreq: 240, startTime: now + 0.65 * s, duration: 1.15 * s, peakVol: 0.28, filterType: 'lowpass', filterFreq: 280 });
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 200, endFreq: 500, startTime: now + 0.8 * s, duration: 1.0 * s, peakVol: 0.25 });
+ this._click(now + 1.8 * s, 85, 0.06, 0.5);
+ }
+ _shutdownStation(now, s) {
+ this._click(now, 105, 0.05, 0.5);
+ this._sub(now + 0.5 * s, 65, 25, 1.4 * s, 0.55);
+ this._tone({ type: 'sawtooth', startFreq: 220, endFreq: 45, startTime: now + 0.55 * s, duration: 1.3 * s, peakVol: 0.22, filterType: 'lowpass', filterFreq: 200 });
+ this._noise({ noiseType: 'pink', filterType: 'lowpass', startFreq: 280, endFreq: 90, startTime: now + 1.6 * s, duration: 0.9 * s, peakVol: 0.2 });
+ }
+
+ // 15. COMEDY
+ _startupComedy(now, s) {
+ this._chime(now, [330, 440, 554, 659], 0.07 * s, 0.35 * s, 0.32);
+ this._tone({ type: 'sawtooth', startFreq: 110, endFreq: 1400, startTime: now + 0.55 * s, duration: 1.25 * s, peakVol: 0.3, filterType: 'bandpass', filterFreq: 800, filterQ: 3.5 });
+ this._sub(now + 0.7 * s, 40, 75, 1.1 * s, 0.5);
+ this._chime(now + 1.8 * s, [1200], 0.1, 0.5 * s, 0.4);
+ }
+ _shutdownComedy(now, s) {
+ this._tone({ type: 'sine', startFreq: 920, endFreq: 140, startTime: now, duration: 0.85 * s, peakVol: 0.32 });
+ [0.7, 0.82, 0.96, 1.12].forEach(dt => this._click(now + dt * s, 420 + Math.random() * 200, 0.025, 0.3));
+ this._noise({ noiseType: 'white', filterType: 'bandpass', startFreq: 300, endFreq: 100, startTime: now + 1.6 * s, duration: 0.6 * s, peakVol: 0.25 });
+ }
+}
+
+window.EngineTransitionSynth = EngineTransitionSynth;
+
+