Files
SciFi-XZBT/js/audio.js
T
Claude f2776885c9 Implement Phase 1 audio gaps: 20 quick-win procedural sounds and ambient loops
- 18 low + 2 low-medium discrete elements (comm badge, door swishes, bosun
  whistle, sickbay ECG, TARDIS lever/door, Sevastopol sparks, commlock tone,
  station ECG, solar roar, cryo sigh, Orion thump, tea dispenser, kettle
  whistle, plaid alarm, cassette transport, grappler servo, radio static,
  mag-boot latch) with soundboard buttons, preset tuning, and automated
  background loops
- bio-wraith misty life-support tuning; sound_reference.md and audio_gaps.md
  implementation tracking
- gitignore audition scratch and local artifacts
2026-09-04 08:32:36 -07:00

3809 lines
126 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class AudioManager {
constructor() {
this.ctx = null;
this.isInitialized = false;
this.isPlaying = false;
this.masterGain = null;
this.compressor = null;
this.analyser = null;
this.currentVolume = 0.75;
this.isMuted = false;
// Sleep Timer
this.timerId = null;
this.timerRemainingSeconds = 0;
this.onTimerTick = null;
this.onTimerComplete = null;
}
init() {
if (this.isInitialized) return;
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
this.ctx = new AudioContextClass();
// Master Dynamics Compressor / Limiter for studio-quality mastering & anti-clipping
this.compressor = this.ctx.createDynamicsCompressor();
this.compressor.threshold.setValueAtTime(-12, this.ctx.currentTime);
this.compressor.knee.setValueAtTime(8, this.ctx.currentTime);
this.compressor.ratio.setValueAtTime(4, this.ctx.currentTime);
this.compressor.attack.setValueAtTime(0.003, this.ctx.currentTime);
this.compressor.release.setValueAtTime(0.25, this.ctx.currentTime);
// Master Gain
this.masterGain = this.ctx.createGain();
this.masterGain.gain.setValueAtTime(this.isMuted ? 0 : this.currentVolume, this.ctx.currentTime);
// Master Analyser Node for Visualizers
this.analyser = this.ctx.createAnalyser();
this.analyser.fftSize = 512;
this.analyser.smoothingTimeConstant = 0.82;
// Route: Nodes -> Compressor -> MasterGain -> Analyser -> Destination
this.compressor.connect(this.masterGain);
this.masterGain.connect(this.analyser);
this.analyser.connect(this.ctx.destination);
this.isInitialized = true;
}
async resume() {
if (!this.isInitialized) this.init();
if (this.ctx.state === 'suspended') {
await this.ctx.resume();
}
}
setMasterVolume(val, smoothTime = 0.05) {
const clamped = Math.max(0, Math.min(1, val));
this.currentVolume = clamped;
if (this.masterGain && this.ctx) {
const target = this.isMuted ? 0 : clamped;
const now = this.ctx.currentTime;
this.masterGain.gain.cancelScheduledValues(now);
this.masterGain.gain.linearRampToValueAtTime(target, now + smoothTime);
}
}
getMasterVolume() {
return this.currentVolume;
}
toggleMute() {
return this.setMute(!this.isMuted);
}
setMute(muted) {
this.isMuted = !!muted;
if (this.masterGain && this.ctx) {
const target = this.isMuted ? 0 : this.currentVolume;
const now = this.ctx.currentTime;
this.masterGain.gain.cancelScheduledValues(now);
this.masterGain.gain.linearRampToValueAtTime(target, now + 0.05);
}
return this.isMuted;
}
// Noise Buffer Helper (White, Pink, Brown)
createNoiseBuffer(type = 'pink', durationSeconds = 5) {
if (!this.ctx) this.init();
const sampleRate = this.ctx.sampleRate;
const bufferSize = sampleRate * durationSeconds;
const buffer = this.ctx.createBuffer(2, bufferSize, sampleRate);
const left = buffer.getChannelData(0);
const right = buffer.getChannelData(1);
if (type === 'white') {
for (let i = 0; i < bufferSize; i++) {
left[i] = Math.random() * 2 - 1;
right[i] = Math.random() * 2 - 1;
}
} else if (type === 'pink') {
let b0L = 0, b1L = 0, b2L = 0, b3L = 0, b4L = 0, b5L = 0, b6L = 0;
let b0R = 0, b1R = 0, b2R = 0, b3R = 0, b4R = 0, b5R = 0, b6R = 0;
for (let i = 0; i < bufferSize; i++) {
const whiteL = Math.random() * 2 - 1;
b0L = 0.99886 * b0L + whiteL * 0.0555179;
b1L = 0.99332 * b1L + whiteL * 0.0750759;
b2L = 0.96900 * b2L + whiteL * 0.1538520;
b3L = 0.86650 * b3L + whiteL * 0.3104856;
b4L = 0.55000 * b4L + whiteL * 0.5329522;
b5L = -0.7616 * b5L - whiteL * 0.0168980;
left[i] = (b0L + b1L + b2L + b3L + b4L + b5L + b6L + whiteL * 0.5362) * 0.11;
b6L = whiteL * 0.115926;
const whiteR = Math.random() * 2 - 1;
b0R = 0.99886 * b0R + whiteR * 0.0555179;
b1R = 0.99332 * b1R + whiteR * 0.0750759;
b2R = 0.96900 * b2R + whiteR * 0.1538520;
b3R = 0.86650 * b3R + whiteR * 0.3104856;
b4R = 0.55000 * b4R + whiteR * 0.5329522;
b5R = -0.7616 * b5R - whiteR * 0.0168980;
right[i] = (b0R + b1R + b2R + b3R + b4R + b5R + b6R + whiteR * 0.5362) * 0.11;
b6R = whiteR * 0.115926;
}
} else if (type === 'brown') {
let lastOutL = 0.0;
let lastOutR = 0.0;
for (let i = 0; i < bufferSize; i++) {
const whiteL = Math.random() * 2 - 1;
lastOutL = (lastOutL + 0.02 * whiteL) / 1.02;
left[i] = lastOutL * 3.5;
const whiteR = Math.random() * 2 - 1;
lastOutR = (lastOutR + 0.02 * whiteR) / 1.02;
right[i] = lastOutR * 3.5;
}
}
return buffer;
}
// Sleep Timer System
startSleepTimer(minutes, onTick, onComplete) {
this.stopSleepTimer();
this.timerRemainingSeconds = Math.round(minutes * 60);
this.onTimerTick = onTick;
this.onTimerComplete = onComplete;
if (this.onTimerTick) this.onTimerTick(this.timerRemainingSeconds);
this.timerId = setInterval(() => {
this.timerRemainingSeconds--;
if (this.onTimerTick) this.onTimerTick(this.timerRemainingSeconds);
// Begin exponential smooth fadeout during final 30 seconds
if (this.timerRemainingSeconds <= 30 && this.timerRemainingSeconds > 0) {
const factor = this.timerRemainingSeconds / 30;
if (this.masterGain && this.ctx) {
const targetVol = this.getMasterVolume() * factor;
this.masterGain.gain.setValueAtTime(Math.max(0, targetVol), this.ctx.currentTime);
}
}
if (this.timerRemainingSeconds <= 0) {
this.stopSleepTimer();
if (this.onTimerComplete) this.onTimerComplete();
}
}, 1000);
}
stopSleepTimer() {
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = null;
this.timerRemainingSeconds = 0;
}
}
}
window.AudioManager = AudioManager;
/**
* Hull Drone & Environmental Sub-Bass Synthesizer
* Generates organic, continuous low-frequency starship structural vibration & room tone.
*/
class HullDroneSynth {
constructor(audioManager) {
this.am = audioManager;
this.nodes = [];
this.gainNode = null;
this.filterNode = null;
this.subOsc1 = null;
this.subOsc2 = null;
this.noiseSource = null;
this.isMuted = false;
// Default configuration parameters
this.params = {
volume: 0.7,
baseFreq: 50, // Fundamental frequency (e.g. 50Hz for TNG bridge)
filterCutoff: 110, // Lowpass filter cutoff
resonance: 2.5, // Filter Q / resonance peak
noiseMix: 0.45, // Brown noise texture mix
harmonicSpread: 1.02 // Slight frequency detune between sub-oscillators for phasing
};
}
start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
// Channel Gain Node
this.gainNode = ctx.createGain();
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();
this.filterNode.type = 'lowpass';
this.filterNode.frequency.setValueAtTime(this.params.filterCutoff, ctx.currentTime);
this.filterNode.Q.setValueAtTime(this.params.resonance, ctx.currentTime);
const filterStage2 = ctx.createBiquadFilter();
filterStage2.type = 'lowpass';
filterStage2.frequency.setValueAtTime(this.params.filterCutoff * 1.5, ctx.currentTime);
filterStage2.Q.setValueAtTime(1.0, ctx.currentTime);
// Sub-bass Oscillator 1 (Sine)
this.subOsc1 = ctx.createOscillator();
this.subOsc1.type = 'sine';
this.subOsc1.frequency.setValueAtTime(this.params.baseFreq, ctx.currentTime);
const osc1Gain = ctx.createGain();
osc1Gain.gain.setValueAtTime(0.5, ctx.currentTime);
this.subOsc1.connect(osc1Gain);
osc1Gain.connect(this.filterNode);
// Sub-bass Oscillator 2 (Triangle/Sine detuned for slow, natural phase beating)
this.subOsc2 = ctx.createOscillator();
this.subOsc2.type = 'triangle';
this.subOsc2.frequency.setValueAtTime(this.params.baseFreq * this.params.harmonicSpread, ctx.currentTime);
const osc2Gain = ctx.createGain();
osc2Gain.gain.setValueAtTime(0.3, ctx.currentTime);
this.subOsc2.connect(osc2Gain);
osc2Gain.connect(this.filterNode);
// Brown Noise Structural Rumble Layer
const brownBuffer = this.am.createNoiseBuffer('brown', 6);
this.noiseSource = ctx.createBufferSource();
this.noiseSource.buffer = brownBuffer;
this.noiseSource.loop = true;
const noiseGain = ctx.createGain();
noiseGain.gain.setValueAtTime(this.params.noiseMix * 0.7, ctx.currentTime);
this.noiseSource.connect(noiseGain);
noiseGain.connect(this.filterNode);
// Slow LFO for organic drifting movement
const lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.setValueAtTime(0.1, ctx.currentTime); // 10 second cycle
const lfoGain = ctx.createGain();
lfoGain.gain.setValueAtTime(12, ctx.currentTime); // Modulate cutoff by ±12Hz
lfo.connect(lfoGain);
lfoGain.connect(this.filterNode.frequency);
// Connect Graph
this.filterNode.connect(filterStage2);
filterStage2.connect(this.gainNode);
this.gainNode.connect(this.am.compressor);
// Start Sources
this.subOsc1.start();
this.subOsc2.start();
this.noiseSource.start();
lfo.start();
this.nodes = [this.subOsc1, this.subOsc2, this.noiseSource, lfo, osc1Gain, osc2Gain, noiseGain, lfoGain, this.filterNode, filterStage2, this.gainNode];
}
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();
if (this.subOsc2) this.subOsc2.stop();
if (this.noiseSource) this.noiseSource.stop();
} catch (e) {
// Ignore if already stopped
}
this.nodes.forEach(node => {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
this.subOsc1 = null;
this.subOsc2 = null;
this.noiseSource = null;
this.gainNode = null;
}
}
setVolume(val) {
this.params.volume = Math.max(0, Math.min(1, val));
if (this.gainNode && this.am.ctx && !this.isMuted) {
const now = this.am.ctx.currentTime;
this.gainNode.gain.cancelScheduledValues(now);
this.gainNode.gain.linearRampToValueAtTime(this.params.volume, now + 0.05);
}
}
setBaseFreq(freq) {
this.params.baseFreq = freq;
if (this.subOsc1 && this.subOsc2 && this.am.ctx) {
const now = this.am.ctx.currentTime;
this.subOsc1.frequency.linearRampToValueAtTime(freq, now + 0.1);
this.subOsc2.frequency.linearRampToValueAtTime(freq * this.params.harmonicSpread, now + 0.1);
}
}
setFilterCutoff(cutoff) {
this.params.filterCutoff = cutoff;
if (this.filterNode && this.am.ctx) {
const now = this.am.ctx.currentTime;
this.filterNode.frequency.linearRampToValueAtTime(cutoff, now + 0.1);
}
}
applyPreset(config) {
if (config.volume !== undefined) this.params.volume = config.volume;
if (config.baseFreq !== undefined) this.setBaseFreq(config.baseFreq);
if (config.filterCutoff !== undefined) this.setFilterCutoff(config.filterCutoff);
if (config.resonance !== undefined) this.params.resonance = config.resonance;
if (config.noiseMix !== undefined) this.params.noiseMix = config.noiseMix;
if (config.harmonicSpread !== undefined) this.params.harmonicSpread = config.harmonicSpread;
this.setVolume(this.params.volume);
}
}
window.HullDroneSynth = HullDroneSynth;
/**
* Warp Core & Reactor Pulse Synthesizer
* Generates the iconic pulsating magnetic intermix thrum of Star Trek warp cores.
*/
class WarpCoreSynth {
constructor(audioManager) {
this.am = audioManager;
this.nodes = [];
this.gainNode = null;
this.isMuted = false;
// Pulse parameters
this.params = {
volume: 0.8,
bpm: 48, // Pulse rate (TNG is ~46-52 BPM, Voyager is ~68-75 BPM)
carrierFreq: 58, // Fundamental carrier pitch (Hz)
modFreqRatio: 2.0, // FM modulation frequency multiplier
modIndex: 40, // FM modulation depth
filterCutoff: 180, // Lowpass filter cutoff
pulseShape: 'tng', // 'tng', 'voyager', 'tos', 'defiant', 'nx'
resonance: 3.0,
swirlMix: 0.35 // Stereo phase swirl
};
this.pulseInterval = null;
this.pulsePhase = 0;
this.onPulse = null; // Callback for UI visualizer pulse animation!
}
start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
this.gainNode = ctx.createGain();
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();
this.filterNode.type = 'lowpass';
this.filterNode.frequency.setValueAtTime(this.params.filterCutoff, ctx.currentTime);
this.filterNode.Q.setValueAtTime(this.params.resonance, ctx.currentTime);
// Stereo Panner for magnetic swirl
this.panner = ctx.createStereoPanner ? ctx.createStereoPanner() : null;
// Carrier & Modulator Oscillators (FM Engine)
this.carrier = ctx.createOscillator();
this.carrier.type = this.params.pulseShape === 'tos' ? 'sawtooth' : (this.params.pulseShape === 'nx' ? 'triangle' : 'sine');
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';
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);
this.subOsc.connect(subGain);
subGain.connect(this.filterNode);
// Pulse Envelope Modulator Gain Node
this.pulseGain = ctx.createGain();
this.pulseGain.gain.setValueAtTime(0.2, ctx.currentTime);
// Connect Carrier -> PulseGain -> Filter -> Panner -> ChannelGain -> Master
this.carrier.connect(this.pulseGain);
this.pulseGain.connect(this.filterNode);
if (this.panner) {
this.filterNode.connect(this.panner);
this.panner.connect(this.gainNode);
} else {
this.filterNode.connect(this.gainNode);
}
this.gainNode.connect(this.am.compressor);
this.carrier.start();
this.subOsc.start();
this.nodes = [this.carrier, this.subOsc, subGain, this.pulseGain, this.filterNode, this.gainNode];
if (this.panner) this.nodes.push(this.panner);
// Start precision pulse scheduler
this.startPulseLoop();
}
startPulseLoop() {
if (this.pulseInterval) clearInterval(this.pulseInterval);
const intervalMs = (60 / this.params.bpm) * 1000;
this.scheduleNextPulse();
this.pulseInterval = setInterval(() => {
this.scheduleNextPulse();
}, intervalMs);
}
scheduleNextPulse() {
const ctx = this.am.ctx;
if (!ctx || !this.pulseGain || !this.filterNode) return;
const now = ctx.currentTime;
const pulseDuration = (60 / this.params.bpm);
this.pulsePhase = (this.pulsePhase + 1) % 4;
// Trigger visualizer callback
if (this.onPulse) {
this.onPulse(this.pulsePhase, pulseDuration);
}
// Dynamic envelope shaping based on ship era
if (this.params.pulseShape === 'tng') {
// Iconic 4-stage Galaxy-class magnetic warp pulse
// Soft attack, deep swelling peak, secondary reverberant harmonic bloom, smooth decay
const peakTime = now + pulseDuration * 0.28;
const secondPeak = now + pulseDuration * 0.58;
this.pulseGain.gain.cancelScheduledValues(now);
this.pulseGain.gain.setValueAtTime(0.18, now);
this.pulseGain.gain.linearRampToValueAtTime(0.95, peakTime);
this.pulseGain.gain.exponentialRampToValueAtTime(0.45, now + pulseDuration * 0.42);
this.pulseGain.gain.linearRampToValueAtTime(0.65, secondPeak);
this.pulseGain.gain.exponentialRampToValueAtTime(0.18, now + pulseDuration * 0.95);
// Modulate filter cutoff in sync with the pulse
this.filterNode.frequency.cancelScheduledValues(now);
this.filterNode.frequency.setValueAtTime(this.params.filterCutoff * 0.7, now);
this.filterNode.frequency.exponentialRampToValueAtTime(this.params.filterCutoff * 1.6, peakTime);
this.filterNode.frequency.exponentialRampToValueAtTime(this.params.filterCutoff * 0.7, now + pulseDuration * 0.95);
} else if (this.params.pulseShape === 'voyager') {
// Faster, sharper, higher-resonance Class 9 warp core
const peakTime = now + pulseDuration * 0.2;
this.pulseGain.gain.cancelScheduledValues(now);
this.pulseGain.gain.setValueAtTime(0.25, now);
this.pulseGain.gain.linearRampToValueAtTime(1.0, peakTime);
this.pulseGain.gain.exponentialRampToValueAtTime(0.25, now + pulseDuration * 0.85);
this.filterNode.frequency.cancelScheduledValues(now);
this.filterNode.frequency.linearRampToValueAtTime(this.params.filterCutoff * 1.8, peakTime);
this.filterNode.frequency.linearRampToValueAtTime(this.params.filterCutoff * 0.8, now + pulseDuration * 0.85);
} else if (this.params.pulseShape === 'tos') {
// TOS Electromechanical oscillating engine thrum
const halfTime = now + pulseDuration * 0.5;
this.pulseGain.gain.cancelScheduledValues(now);
this.pulseGain.gain.setValueAtTime(0.4, now);
this.pulseGain.gain.linearRampToValueAtTime(0.9, halfTime);
this.pulseGain.gain.linearRampToValueAtTime(0.4, now + pulseDuration);
} else if (this.params.pulseShape === 'defiant') {
// Defiant: tight, aggressive pulse with rapid decay
const peakTime = now + pulseDuration * 0.15;
this.pulseGain.gain.cancelScheduledValues(now);
this.pulseGain.gain.setValueAtTime(0.3, now);
this.pulseGain.gain.linearRampToValueAtTime(1.0, peakTime);
this.pulseGain.gain.exponentialRampToValueAtTime(0.3, now + pulseDuration * 0.75);
} else {
// NX / Industrial reactor chug
const peakTime = now + pulseDuration * 0.35;
this.pulseGain.gain.cancelScheduledValues(now);
this.pulseGain.gain.setValueAtTime(0.2, now);
this.pulseGain.gain.linearRampToValueAtTime(0.85, peakTime);
this.pulseGain.gain.linearRampToValueAtTime(0.2, now + pulseDuration);
}
// Subtle stereo panning drift
if (this.panner && this.params.swirlMix > 0) {
const panTarget = Math.sin(this.pulsePhase * Math.PI * 0.5) * this.params.swirlMix;
this.panner.pan.linearRampToValueAtTime(panTarget, now + pulseDuration * 0.5);
}
}
setBpm(bpm) {
this.params.bpm = Math.max(20, Math.min(160, bpm));
if (this.nodes.length > 0) {
this.startPulseLoop();
}
}
setVolume(val) {
this.params.volume = Math.max(0, Math.min(1, val));
if (this.gainNode && this.am.ctx && !this.isMuted) {
const now = this.am.ctx.currentTime;
this.gainNode.gain.cancelScheduledValues(now);
this.gainNode.gain.linearRampToValueAtTime(this.params.volume, now + 0.05);
}
}
setCarrierFreq(freq) {
this.params.carrierFreq = freq;
if (this.carrier && this.subOsc && this.am.ctx) {
const now = this.am.ctx.currentTime;
this.carrier.frequency.linearRampToValueAtTime(freq, now + 0.1);
this.subOsc.frequency.linearRampToValueAtTime(freq * 0.5, now + 0.1);
}
}
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;
}
if (this.nodes.length > 0) {
try {
if (this.carrier) this.carrier.stop();
if (this.subOsc) this.subOsc.stop();
} catch (e) {}
this.nodes.forEach(node => {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
this.carrier = null;
this.subOsc = null;
this.gainNode = null;
}
}
applyPreset(config) {
if (config.volume !== undefined) this.params.volume = config.volume;
if (config.bpm !== undefined) this.setBpm(config.bpm);
if (config.carrierFreq !== undefined) this.setCarrierFreq(config.carrierFreq);
if (config.filterCutoff !== undefined) this.params.filterCutoff = config.filterCutoff;
if (config.pulseShape !== undefined) this.params.pulseShape = config.pulseShape;
if (config.resonance !== undefined) this.params.resonance = config.resonance;
if (config.swirlMix !== undefined) this.params.swirlMix = config.swirlMix;
this.setVolume(this.params.volume);
}
}
window.WarpCoreSynth = WarpCoreSynth;
/**
* Environmental Life Support & Airflow Synthesizer
* Generates continuous ventilation airflow, atmospheric hiss, and room acoustic damping.
*/
class LifeSupportSynth {
constructor(audioManager) {
this.am = audioManager;
this.nodes = [];
this.gainNode = null;
this.isMuted = false;
this.params = {
volume: 0.5,
noiseType: 'pink', // 'pink' (warm TNG), 'white' (crisp Voyager), 'brown' (heavy NX-01)
highpassFreq: 180, // Cuts extreme sub rumble to isolate air movement
lowpassFreq: 1800, // Gentle top-end rolloff
airflowModSpeed: 0.15, // Subtle breathing movement of the environmental airflow
airflowModDepth: 0.12 // Depth of airflow intensity modulation
};
}
start(rampDuration = 0) {
this.stop();
const ctx = this.am.ctx;
if (!ctx) return;
this.gainNode = ctx.createGain();
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();
hpFilter.type = 'highpass';
hpFilter.frequency.setValueAtTime(this.params.highpassFreq, ctx.currentTime);
// Lowpass filter (shapes the crispness vs warmth of the air)
this.lpFilter = ctx.createBiquadFilter();
this.lpFilter.type = 'lowpass';
this.lpFilter.frequency.setValueAtTime(this.params.lowpassFreq, ctx.currentTime);
this.lpFilter.Q.setValueAtTime(0.7, ctx.currentTime);
// Noise buffer source
const noiseBuffer = this.am.createNoiseBuffer(this.params.noiseType, 6);
this.noiseSource = ctx.createBufferSource();
this.noiseSource.buffer = noiseBuffer;
this.noiseSource.loop = true;
// Slow airflow modulation LFO for organic breath
const airflowLfo = ctx.createOscillator();
airflowLfo.type = 'sine';
airflowLfo.frequency.setValueAtTime(this.params.airflowModSpeed, ctx.currentTime);
const lfoGain = ctx.createGain();
lfoGain.gain.setValueAtTime(this.params.airflowModDepth, ctx.currentTime);
const modGain = ctx.createGain();
modGain.gain.setValueAtTime(0.8, ctx.currentTime);
airflowLfo.connect(lfoGain);
lfoGain.connect(modGain.gain);
// Stereo widener using delay
const splitter = ctx.createChannelSplitter(2);
const merger = ctx.createChannelMerger(2);
const delayRight = ctx.createDelay();
delayRight.delayTime.setValueAtTime(0.018, ctx.currentTime); // 18ms Haas effect widening
// Graph: Noise -> ModGain -> HP -> LP -> Splitter -> (Left direct, Right delay) -> Merger -> Gain -> Compressor
this.noiseSource.connect(modGain);
modGain.connect(hpFilter);
hpFilter.connect(this.lpFilter);
this.lpFilter.connect(splitter);
splitter.connect(merger, 0, 0); // Left channel
splitter.connect(delayRight, 1);
delayRight.connect(merger, 0, 1); // Right delayed channel
merger.connect(this.gainNode);
this.gainNode.connect(this.am.compressor);
this.noiseSource.start();
airflowLfo.start();
this.nodes = [
this.noiseSource, airflowLfo, lfoGain, modGain,
hpFilter, this.lpFilter, splitter, delayRight, merger, this.gainNode
];
}
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();
} catch (e) {}
this.nodes.forEach(node => {
try { node.disconnect(); } catch (e) {}
});
this.nodes = [];
this.noiseSource = null;
this.gainNode = null;
}
}
setVolume(val) {
this.params.volume = Math.max(0, Math.min(1, val));
if (this.gainNode && this.am.ctx && !this.isMuted) {
const now = this.am.ctx.currentTime;
this.gainNode.gain.cancelScheduledValues(now);
this.gainNode.gain.linearRampToValueAtTime(this.params.volume, now + 0.05);
}
}
setFilterCutoff(freq) {
this.params.lowpassFreq = freq;
if (this.lpFilter && this.am.ctx) {
const now = this.am.ctx.currentTime;
this.lpFilter.frequency.linearRampToValueAtTime(freq, now + 0.1);
}
}
applyPreset(config) {
if (config.volume !== undefined) this.params.volume = config.volume;
if (config.noiseType !== undefined) this.params.noiseType = config.noiseType;
if (config.highpassFreq !== undefined) this.params.highpassFreq = config.highpassFreq;
if (config.lowpassFreq !== undefined) this.setFilterCutoff(config.lowpassFreq);
if (config.airflowModSpeed !== undefined) this.params.airflowModSpeed = config.airflowModSpeed;
if (config.airflowModDepth !== undefined) this.params.airflowModDepth = config.airflowModDepth;
this.setVolume(this.params.volume);
}
}
window.LifeSupportSynth = LifeSupportSynth;
/**
* Procedural Starship Telemetry, LCARS Chirps, Beeps & Console Synthesizer
* 100% synthesized programmatically via Web Audio API oscillators, FM synthesis, and envelopes.
* Zero stored audio samples.
*/
class TelemetrySynth {
constructor(audioManager) {
this.am = audioManager;
this.gainNode = null;
this.isMuted = false;
this.schedulerTimer = null;
this.params = {
volume: 0.4,
density: 0.5, // How often background telemetry chirps occur (0 = off, 1 = busy bridge)
era: 'tng', // 'tng', 'voyager', 'tos', 'ds9', 'nx'
reverbMix: 0.25
};
// Musical pitch frequencies for authentic LCARS musical intervals (major/minor pentatonic & perfect 4ths/5ths)
this.lcarsPitches = [
880, 987.77, 1046.50, 1174.66, 1318.51, 1396.91, 1567.98, 1760, 1975.53, 2093.00, 2349.32, 2637.02
];
// TOS Bridge oscillator warble frequencies
this.tosFrequencies = [
440, 554.37, 659.25, 830.61, 880, 1108.73, 1318.51, 1661.22, 2217.46
];
}
start() {
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);
this.gainNode.connect(this.am.compressor);
this.startAutoTelemetryScheduler();
}
stop() {
if (this.schedulerTimer) {
clearTimeout(this.schedulerTimer);
this.schedulerTimer = null;
}
}
setVolume(val) {
this.params.volume = Math.max(0, Math.min(1, val));
if (this.gainNode && this.am.ctx && !this.isMuted) {
const now = this.am.ctx.currentTime;
this.gainNode.gain.cancelScheduledValues(now);
this.gainNode.gain.linearRampToValueAtTime(this.params.volume, now + 0.05);
}
}
setDensity(val) {
this.params.density = Math.max(0, Math.min(1, val));
}
startAutoTelemetryScheduler() {
if (this.schedulerTimer) clearTimeout(this.schedulerTimer);
if (this.params.density <= 0.01) return;
// Calculate delay inversely proportional to density (2s to 12s)
const baseDelay = 12000 * (1.05 - this.params.density);
const jitter = Math.random() * 4000;
const nextInterval = Math.max(800, baseDelay + jitter);
this.schedulerTimer = setTimeout(() => {
this.playRandomTelemetrySound();
this.startAutoTelemetryScheduler();
}, nextInterval);
}
playRandomTelemetrySound() {
if (this.isMuted || this.params.volume <= 0.01 || !this.am.ctx) return;
switch (this.params.era) {
case 'tos':
Math.random() > 0.4 ? this.synthesizeTOSWarble() : this.synthesizeTOSRelayClick();
break;
case 'ds9':
Math.random() > 0.5 ? this.synthesizeCardassianSensor() : this.synthesizeLCARSSingleChirp();
break;
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: {
const r = Math.random();
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,
// not as a claim that a specific fictional beep means a specific thing.
window.dispatchEvent(new CustomEvent('scifi-telemetry-activity', {
detail: {
era: this.params.era,
density: this.params.density,
firedAt: performance.now()
}
}));
}
/**
* 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)
*/
synthesizeLCARSSingleChirp(pitch = null) {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const freq = pitch || this.lcarsPitches[Math.floor(Math.random() * this.lcarsPitches.length)];
const duration = 0.09;
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now);
// Subtle downward micro-pitch glide (12Hz) for that warm capacitive touch feel
osc.frequency.exponentialRampToValueAtTime(freq * 0.98, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.35, now + 0.008);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
// Filter to eliminate any click
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(3200, now);
osc.connect(env);
env.connect(filter);
filter.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* TNG LCARS Double Chirp (Iconic standard confirmation tone)
*/
synthesizeLCARSDoubleChirp() {
const ctx = this.am.ctx;
if (!ctx) return;
const idx = Math.floor(Math.random() * (this.lcarsPitches.length - 2));
const p1 = this.lcarsPitches[idx];
const p2 = this.lcarsPitches[idx + 2]; // Minor third or fourth higher
this.synthesizeLCARSSingleChirp(p1);
setTimeout(() => {
this.synthesizeLCARSSingleChirp(p2);
}, 65);
}
/**
* TNG LCARS Multi-Tone Data Acknowledgment Sequence
*/
synthesizeLCARSSequence() {
const ctx = this.am.ctx;
if (!ctx) return;
const notes = [
this.lcarsPitches[Math.floor(Math.random() * 4) + 4],
this.lcarsPitches[Math.floor(Math.random() * 4) + 6],
this.lcarsPitches[Math.floor(Math.random() * 4) + 2]
];
notes.forEach((freq, i) => {
setTimeout(() => {
this.synthesizeLCARSSingleChirp(freq);
}, i * 75);
});
}
/**
* High-tech Sensor Sweep Tone (Voyager/TNG Long-Range Sensor telemetry)
*/
synthesizeSensorSweep() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.38;
const startFreq = 1200 + Math.random() * 800;
const endFreq = startFreq * (Math.random() > 0.5 ? 1.6 : 0.65);
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(startFreq, now);
osc.frequency.exponentialRampToValueAtTime(endFreq, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.18, now + 0.05);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* TOS Original Series Bridge Electronic Computer Warble
* Two detuned square/triangle oscillators modulated by high-speed vibrato LFO
*/
synthesizeTOSWarble() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.45;
const baseFreq = this.tosFrequencies[Math.floor(Math.random() * this.tosFrequencies.length)];
const osc1 = ctx.createOscillator();
osc1.type = 'triangle';
osc1.frequency.setValueAtTime(baseFreq, now);
const osc2 = ctx.createOscillator();
osc2.type = 'sawtooth';
osc2.frequency.setValueAtTime(baseFreq * 1.5, now);
// Fast Vibrato LFO
const lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.setValueAtTime(14 + Math.random() * 8, now); // 14-22 Hz warble
const lfoGain = ctx.createGain();
lfoGain.gain.setValueAtTime(35, now);
lfo.connect(lfoGain);
lfoGain.connect(osc1.frequency);
lfoGain.connect(osc2.frequency);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.22, now + 0.04);
env.gain.setValueAtTime(0.22, now + duration * 0.7);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
// Bandpass filter to create that vintage analog 1960s telephone/relay resonance
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(baseFreq * 1.2, now);
filter.Q.setValueAtTime(3.5, now);
osc1.connect(env);
osc2.connect(env);
env.connect(filter);
filter.connect(this.gainNode);
osc1.start(now);
osc2.start(now);
lfo.start(now);
osc1.stop(now + duration);
osc2.stop(now + duration);
lfo.stop(now + duration);
}
/**
* TOS Mechanical Relay Solenoid Click
*/
synthesizeTOSRelayClick() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.025;
const osc = ctx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(1400, now);
osc.frequency.exponentialRampToValueAtTime(300, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.3, now);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* DS9 / Cardassian Cavernous Sensor Tone (Resonant metallic ring)
*/
synthesizeCardassianSensor() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.55;
const freq = 420 + Math.random() * 200;
const osc1 = ctx.createOscillator();
osc1.type = 'sine';
osc1.frequency.setValueAtTime(freq, now);
const osc2 = ctx.createOscillator();
osc2.type = 'sine';
osc2.frequency.setValueAtTime(freq * 1.414, now); // Tritone metallic dissonance
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.2, now + 0.015);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc1.connect(env);
osc2.connect(env);
env.connect(this.gainNode);
osc1.start(now);
osc2.start(now);
osc1.stop(now + duration);
osc2.stop(now + duration);
}
/**
* NX-01 Industrial Hydraulic Relay Click
*/
synthesizeNXRelay() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.04;
const osc = ctx.createOscillator();
osc.type = 'triangle';
osc.frequency.setValueAtTime(750, now);
osc.frequency.exponentialRampToValueAtTime(120, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.25, now);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* NX-01 Indicator Beep (Early 22nd century industrial tone)
*/
synthesizeNXIndicatorBeep() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.08;
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.2, now + 0.005);
env.gain.setValueAtTime(0.2, now + duration * 0.8);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Iconic TNG 2-Tone Door Chime ("Come in")
*/
synthesizeDoorChime() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const f1 = 880; // A5
const f2 = 1174.66; // D6 (Up a fourth)
const osc1 = ctx.createOscillator();
osc1.type = 'sine';
osc1.frequency.setValueAtTime(f1, now);
const env1 = ctx.createGain();
env1.gain.setValueAtTime(0.001, now);
env1.gain.linearRampToValueAtTime(0.35, now + 0.015);
env1.gain.exponentialRampToValueAtTime(0.001, now + 0.45);
osc1.connect(env1);
env1.connect(this.gainNode);
osc1.start(now);
osc1.stop(now + 0.45);
// Second tone starts at 0.16s
const osc2 = ctx.createOscillator();
osc2.type = 'sine';
osc2.frequency.setValueAtTime(f2, now + 0.16);
const env2 = ctx.createGain();
env2.gain.setValueAtTime(0.001, now + 0.16);
env2.gain.linearRampToValueAtTime(0.4, now + 0.175);
env2.gain.exponentialRampToValueAtTime(0.001, now + 0.7);
osc2.connect(env2);
env2.connect(this.gainNode);
osc2.start(now + 0.16);
osc2.stop(now + 0.7);
}
applyPreset(config) {
if (config.volume !== undefined) this.params.volume = config.volume;
if (config.density !== undefined) this.setDensity(config.density);
if (config.era !== undefined) this.params.era = config.era;
this.setVolume(this.params.volume);
this.startAutoTelemetryScheduler();
}
}
window.TelemetrySynth = TelemetrySynth;
/**
* Procedural Starship Alert & Event Synthesizer
* 100% synthesized programmatically in Web Audio API.
* Includes TNG Red Alert (3-tone), TOS Red Alert (hooter buzzer), Movie-era descending klaxon,
* Yellow Alert chime, and dynamic Warp Drive Throttle swell.
*/
class AlertSynth {
constructor(audioManager) {
this.am = audioManager;
this.gainNode = null;
this.activeAlert = null; // 'red', 'yellow', null
this.alertTimer = null;
this.alertType = 'tng'; // 'tng', 'tos', 'movie'
this.params = {
volume: 0.6
};
}
init() {
if (this.gainNode || !this.am.ctx) return;
const ctx = this.am.ctx;
this.gainNode = ctx.createGain();
this.gainNode.gain.setValueAtTime(this.params.volume, ctx.currentTime);
this.gainNode.connect(this.am.compressor);
}
setVolume(val) {
this.params.volume = Math.max(0, Math.min(1, val));
if (this.gainNode && this.am.ctx) {
const now = this.am.ctx.currentTime;
this.gainNode.gain.linearRampToValueAtTime(this.params.volume, now + 0.05);
}
}
triggerRedAlert(type = 'tng') {
this.init();
this.stopAlert();
this.activeAlert = 'red';
this.alertType = type;
const playLoop = () => {
if (this.activeAlert !== 'red') return;
let loopDuration = 1.35;
if (this.alertType === 'tng') {
this.synthesizeTNGRedAlertCycle();
loopDuration = 1.35;
} else if (this.alertType === 'tos') {
this.synthesizeTOSRedAlertCycle();
loopDuration = 1.1;
} else {
this.synthesizeMovieRedAlertCycle();
loopDuration = 1.4;
}
this.alertTimer = setTimeout(playLoop, loopDuration * 1000);
};
playLoop();
}
triggerYellowAlert() {
this.init();
this.stopAlert();
this.activeAlert = 'yellow';
const playLoop = () => {
if (this.activeAlert !== 'yellow') return;
this.synthesizeYellowAlertCycle();
this.alertTimer = setTimeout(playLoop, 2200);
};
playLoop();
}
stopAlert() {
this.activeAlert = null;
if (this.alertTimer) {
clearTimeout(this.alertTimer);
this.alertTimer = null;
}
}
/**
* TNG Red Alert Klaxon (3-tone rising & cascading electronic horn with resonant envelope)
*/
synthesizeTNGRedAlertCycle() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
// Frequencies for the iconic TNG 3-tone klaxon chord: F5 (698.46Hz), Ab5 (830.61Hz), C6 (1046.50Hz)
const freqs = [698.46, 830.61, 1046.50];
freqs.forEach((freq, idx) => {
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(freq * 0.94, now);
// Fast upward swoop on trigger
osc.frequency.exponentialRampToValueAtTime(freq, now + 0.12);
// Lowpass filter to give that brassy starship horn acoustic resonance
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1600, now);
filter.Q.setValueAtTime(4.0, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.25 / freqs.length, now + 0.08);
env.gain.setValueAtTime(0.25 / freqs.length, now + 0.45);
env.gain.exponentialRampToValueAtTime(0.0001, now + 0.85);
osc.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + 0.86);
});
}
/**
* TOS Red Alert Buzzer / Hooter Siren (Pulsing 2-tone frequency modulation)
*/
synthesizeTOSRedAlertCycle() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.85;
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(540, now);
osc.frequency.linearRampToValueAtTime(920, now + duration * 0.5);
osc.frequency.linearRampToValueAtTime(540, now + duration);
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(800, now);
filter.Q.setValueAtTime(2.2, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.01, now);
env.gain.linearRampToValueAtTime(0.28, now + 0.05);
env.gain.setValueAtTime(0.28, now + duration * 0.85);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Star Trek Movie Era Refit Descending Red Alert Klaxon
*/
synthesizeMovieRedAlertCycle() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 1.05;
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(1350, now);
osc.frequency.exponentialRampToValueAtTime(420, now + duration * 0.9);
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(2200, now);
filter.Q.setValueAtTime(3.5, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.01, now);
env.gain.linearRampToValueAtTime(0.3, now + 0.06);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Yellow Alert Pulsing Warning Chime
*/
synthesizeYellowAlertCycle() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const f1 = 660; // E5
const f2 = 880; // A5
[0, 0.22].forEach((offset, idx) => {
const freq = idx === 0 ? f1 : f2;
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now + offset);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now + offset);
env.gain.linearRampToValueAtTime(0.32, now + offset + 0.015);
env.gain.exponentialRampToValueAtTime(0.001, now + offset + 0.5);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now + offset);
osc.stop(now + offset + 0.52);
});
}
/**
* Procedural Warp Drive Acceleration Swell ("Engage!")
* Synthesizes rising plasma induction whine + deep bass detonation
*/
synthesizeWarpJump() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 2.8;
// 1. Rising High Induction Whine
const whineOsc = ctx.createOscillator();
whineOsc.type = 'sawtooth';
whineOsc.frequency.setValueAtTime(80, now);
whineOsc.frequency.exponentialRampToValueAtTime(3800, now + 1.8);
whineOsc.frequency.exponentialRampToValueAtTime(14000, now + 2.5);
const whineFilter = ctx.createBiquadFilter();
whineFilter.type = 'bandpass';
whineFilter.frequency.setValueAtTime(200, now);
whineFilter.frequency.exponentialRampToValueAtTime(4500, now + 1.8);
whineFilter.Q.setValueAtTime(5.0, now);
const whineEnv = ctx.createGain();
whineEnv.gain.setValueAtTime(0.01, now);
whineEnv.gain.linearRampToValueAtTime(0.35, now + 1.6);
whineEnv.gain.exponentialRampToValueAtTime(0.001, now + 2.7);
whineOsc.connect(whineFilter);
whineFilter.connect(whineEnv);
whineEnv.connect(this.gainNode);
// 2. Sub-bass Matter-Antimatter Boom
const subOsc = ctx.createOscillator();
subOsc.type = 'sine';
subOsc.frequency.setValueAtTime(140, now + 1.4);
subOsc.frequency.exponentialRampToValueAtTime(32, now + 2.6);
const subEnv = ctx.createGain();
subEnv.gain.setValueAtTime(0.001, now + 1.4);
subEnv.gain.linearRampToValueAtTime(0.7, now + 1.7);
subEnv.gain.exponentialRampToValueAtTime(0.0001, now + duration);
subOsc.connect(subEnv);
subEnv.connect(this.gainNode);
whineOsc.start(now);
whineOsc.stop(now + 2.7);
subOsc.start(now + 1.4);
subOsc.stop(now + duration);
}
}
window.AlertSynth = AlertSynth;
/**
* Procedural Doctor Who & TARDIS Sound Synthesizer
* 100% synthesized programmatically via Web Audio API.
* Includes Dematerialization Wheeze-Groan, Cloister Bell, Sonic Screwdriver, and TARDIS Console Foley.
*/
class WhoniverseAudioSynth {
constructor(audioManager) {
this.am = audioManager;
this.gainNode = null;
this.activeCloister = false;
this.cloisterTimer = null;
}
init() {
if (this.gainNode || !this.am.ctx) return;
const ctx = this.am.ctx;
this.gainNode = ctx.createGain();
this.gainNode.gain.setValueAtTime(0.7, ctx.currentTime);
this.gainNode.connect(this.am.compressor);
}
/**
* Procedural TARDIS Materialization / Dematerialization ("Wheeze-Groan")
* Modeled after Brian Hodgson's 1963 BBC Radiophonic technique:
* Dragging keys on piano bass strings -> reverse playback -> slow tape speed -> feedback loop.
*/
synthesizeDematCycle(cycles = 4) {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
for (let c = 0; c < cycles; c++) {
const cycleStart = ctx.currentTime + c * 1.85;
this.synthesizeSingleDematSwell(cycleStart, c, cycles);
}
}
synthesizeSingleDematSwell(startTime, cycleIndex, totalCycles) {
const ctx = this.am.ctx;
const duration = 1.75;
// Intensity fades slightly on later cycles
const intensity = 1.0 - (cycleIndex / totalCycles) * 0.35;
// 1. Friction Scrape Carrier (Sawtooth through resonant highpass/bandpass with frequency glide)
const frictionOsc = ctx.createOscillator();
frictionOsc.type = 'sawtooth';
// Frequency glides up then groans down
frictionOsc.frequency.setValueAtTime(120, startTime);
frictionOsc.frequency.exponentialRampToValueAtTime(840, startTime + 0.65);
frictionOsc.frequency.exponentialRampToValueAtTime(95, startTime + duration);
// Filter modeling the piano soundboard metallic scraping resonance
const frictionFilter = ctx.createBiquadFilter();
frictionFilter.type = 'bandpass';
frictionFilter.frequency.setValueAtTime(320, startTime);
frictionFilter.frequency.exponentialRampToValueAtTime(1450, startTime + 0.65);
frictionFilter.frequency.exponentialRampToValueAtTime(220, startTime + duration);
frictionFilter.Q.setValueAtTime(4.5, startTime);
const frictionGain = ctx.createGain();
frictionGain.gain.setValueAtTime(0.001, startTime);
frictionGain.gain.linearRampToValueAtTime(0.4 * intensity, startTime + 0.45);
frictionGain.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
// 2. Sub-Vortex Resonant Groan (FM synthesis for the deep cosmic groaning undertone)
const groanCarrier = ctx.createOscillator();
groanCarrier.type = 'triangle';
groanCarrier.frequency.setValueAtTime(55, startTime);
groanCarrier.frequency.linearRampToValueAtTime(138, startTime + 0.55);
groanCarrier.frequency.exponentialRampToValueAtTime(48, startTime + duration);
const groanMod = ctx.createOscillator();
groanMod.type = 'sine';
groanMod.frequency.setValueAtTime(28, startTime); // Phasing FM modulator
groanMod.frequency.linearRampToValueAtTime(65, startTime + 0.6);
const groanModGain = ctx.createGain();
groanModGain.gain.setValueAtTime(45, startTime);
groanMod.connect(groanModGain);
groanModGain.connect(groanCarrier.frequency);
const groanGain = ctx.createGain();
groanGain.gain.setValueAtTime(0.001, startTime);
groanGain.gain.linearRampToValueAtTime(0.6 * intensity, startTime + 0.5);
groanGain.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
// 3. Phasing Flutter / Swell (Tape-flange simulation via slow LFO)
const lfo = ctx.createOscillator();
lfo.type = 'sine';
lfo.frequency.setValueAtTime(5.5, startTime); // 5.5 Hz flanging flutter
const lfoDepth = ctx.createGain();
lfoDepth.gain.setValueAtTime(0.25, startTime);
lfo.connect(lfoDepth);
lfoDepth.connect(frictionGain.gain);
// Connect Graph
frictionOsc.connect(frictionFilter);
frictionFilter.connect(frictionGain);
frictionGain.connect(this.gainNode);
groanCarrier.connect(groanGain);
groanGain.connect(this.gainNode);
// Trigger Nodes
frictionOsc.start(startTime);
frictionOsc.stop(startTime + duration);
groanCarrier.start(startTime);
groanCarrier.stop(startTime + duration);
groanMod.start(startTime);
groanMod.stop(startTime + duration);
lfo.start(startTime);
lfo.stop(startTime + duration);
}
/**
* Procedural Cloister Bell (Deep, ominous bronze cathedral bell)
*/
triggerCloisterBell() {
this.init();
this.stopCloisterBell();
this.activeCloister = true;
const ringLoop = () => {
if (!this.activeCloister) return;
this.synthesizeCloisterStrike();
this.cloisterTimer = setTimeout(ringLoop, 3200); // Canonical cloister bell repetition rate
};
ringLoop();
}
stopCloisterBell() {
this.activeCloister = false;
if (this.cloisterTimer) {
clearTimeout(this.cloisterTimer);
this.cloisterTimer = null;
}
}
synthesizeCloisterStrike() {
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 4.2;
// Authentic bell inharmonic partial ratios: Fundamental, Minor 3rd, 5th, Octave, Major 7th
const bellPartials = [
{ freqRatio: 1.0, gain: 0.65, decay: 4.2 }, // Fundamental ~108 Hz
{ freqRatio: 1.19, gain: 0.45, decay: 3.6 }, // Minor third
{ freqRatio: 1.51, gain: 0.40, decay: 3.1 }, // Fifth
{ freqRatio: 2.01, gain: 0.30, decay: 2.4 }, // Octave
{ freqRatio: 2.74, gain: 0.22, decay: 1.8 }, // Upper strike tone
{ freqRatio: 3.42, gain: 0.15, decay: 1.2 } // High strike transient
];
const basePitch = 108.0; // Deep bronze bell pitch
bellPartials.forEach(p => {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(basePitch * p.freqRatio, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(p.gain * 0.35, now + 0.012); // Sharp hammer impact
env.gain.exponentialRampToValueAtTime(0.0001, now + p.decay);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + p.decay + 0.05);
});
}
/**
* Procedural Sonic Screwdriver (High-frequency modulated sweep & vibrato warble)
*/
synthesizeSonicScrewdriver(durationSeconds = 1.2) {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = durationSeconds;
// Dual square/saw oscillators
const osc1 = ctx.createOscillator();
osc1.type = 'square';
osc1.frequency.setValueAtTime(2350, now);
osc1.frequency.linearRampToValueAtTime(2650, now + duration * 0.5);
osc1.frequency.linearRampToValueAtTime(2350, now + duration);
const osc2 = ctx.createOscillator();
osc2.type = 'sawtooth';
osc2.frequency.setValueAtTime(2362, now); // 12Hz natural phase beat
// Rapid Vibrato LFO
const vibrato = ctx.createOscillator();
vibrato.type = 'sine';
vibrato.frequency.setValueAtTime(32, now); // 32 Hz warble rate
const vibGain = ctx.createGain();
vibGain.gain.setValueAtTime(140, now);
vibrato.connect(vibGain);
vibGain.connect(osc1.frequency);
vibGain.connect(osc2.frequency);
// Bandpass filter for metallic resonance
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(2500, now);
filter.Q.setValueAtTime(4.0, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.28, now + 0.03);
env.gain.setValueAtTime(0.28, now + duration * 0.85);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc1.connect(filter);
osc2.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
osc1.start(now);
osc2.start(now);
vibrato.start(now);
osc1.stop(now + duration);
osc2.stop(now + duration);
vibrato.stop(now + duration);
}
/**
* Fast-Return Spring Lever (Heavy spring recoil clack + resonant ring)
*/
synthesizeFastReturn() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.35;
const osc = ctx.createOscillator();
osc.type = 'triangle';
osc.frequency.setValueAtTime(620, now);
osc.frequency.exponentialRampToValueAtTime(95, now + 0.08);
const env = ctx.createGain();
env.gain.setValueAtTime(0.45, now);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* TARDIS Demat Switch / Relay Solenoid
*/
synthesizeDematSwitch() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 0.06;
const osc = ctx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(850, now);
osc.frequency.exponentialRampToValueAtTime(140, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.35, now);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Telepathic Circuit Chime (Glassy, mystical resonance)
*/
synthesizeTelepathicChime() {
this.init();
const ctx = this.am.ctx;
if (!ctx || !this.gainNode) return;
const now = ctx.currentTime;
const duration = 1.4;
const notes = [1046.50, 1318.51, 1567.98, 2093.00]; // C Major arpeggio shimmer
notes.forEach((freq, idx) => {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, now + idx * 0.08);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now + idx * 0.08);
env.gain.linearRampToValueAtTime(0.18, now + idx * 0.08 + 0.02);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now + idx * 0.08);
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;
/**
* Procedural Audio Synthesizers for Expanded Sci-Fi Universes
* Generates Epstein drives, Bio-ship neural pulses, DRADIS sonar, Singularity drives,
* Retro analog tone glides, and Ludicrous speed reality shifts via Web Audio API.
*/
class ExpandedSciFiAudioSynth {
constructor(audioManager) {
this.am = audioManager;
this.gainNode = null;
this.medicalTimer = null;
this.sparkTimer = null;
}
init() {
if (this.gainNode || !this.am.ctx) return;
const ctx = this.am.ctx;
this.gainNode = ctx.createGain();
this.gainNode.gain.setValueAtTime(0.7, ctx.currentTime);
this.gainNode.connect(this.am.compressor);
}
/**
* Epstein Drive Fusion Torch Burn (The Expanse / Industrial Space)
* Tremendous raw fusion thrust with high-pressure magnetic plasma acceleration
*/
synthesizeEpsteinBurn() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 3.5;
// 1. High-frequency plasma induction whine
const whineOsc = ctx.createOscillator();
whineOsc.type = 'sawtooth';
whineOsc.frequency.setValueAtTime(140, now);
whineOsc.frequency.exponentialRampToValueAtTime(1800, now + 1.2);
whineOsc.frequency.exponentialRampToValueAtTime(3200, now + 2.5);
const whineFilter = ctx.createBiquadFilter();
whineFilter.type = 'bandpass';
whineFilter.frequency.setValueAtTime(280, now);
whineFilter.frequency.exponentialRampToValueAtTime(2600, now + 2.0);
whineFilter.Q.setValueAtTime(4.0, now);
const whineGain = ctx.createGain();
whineGain.gain.setValueAtTime(0.001, now);
whineGain.gain.linearRampToValueAtTime(0.35, now + 1.0);
whineGain.gain.exponentialRampToValueAtTime(0.001, now + duration);
whineOsc.connect(whineFilter);
whineFilter.connect(whineGain);
whineGain.connect(this.gainNode);
// 2. Colossal fusion blast roar (filtered noise)
const roarBuffer = this.am.createNoiseBuffer('brown', 4);
const roarSource = ctx.createBufferSource();
roarSource.buffer = roarBuffer;
const roarFilter = ctx.createBiquadFilter();
roarFilter.type = 'lowpass';
roarFilter.frequency.setValueAtTime(180, now);
roarFilter.frequency.linearRampToValueAtTime(550, now + 1.2);
roarFilter.frequency.exponentialRampToValueAtTime(120, now + duration);
const roarGain = ctx.createGain();
roarGain.gain.setValueAtTime(0.001, now);
roarGain.gain.linearRampToValueAtTime(0.7, now + 1.2);
roarGain.gain.exponentialRampToValueAtTime(0.001, now + duration);
roarSource.connect(roarFilter);
roarFilter.connect(roarGain);
roarGain.connect(this.gainNode);
whineOsc.start(now);
whineOsc.stop(now + duration);
roarSource.start(now);
roarSource.stop(now + duration);
}
/**
* Bio-Ship Starburst / Neural Pulse (Farscape Moya & Bioships)
* Organic vocalized dimensional fold and vascular wave
*/
synthesizeStarburst() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 2.8;
const osc1 = ctx.createOscillator();
osc1.type = 'sine';
osc1.frequency.setValueAtTime(85, now);
osc1.frequency.exponentialRampToValueAtTime(940, now + 1.4);
osc1.frequency.exponentialRampToValueAtTime(45, now + duration);
const osc2 = ctx.createOscillator();
osc2.type = 'triangle';
osc2.frequency.setValueAtTime(125, now);
osc2.frequency.exponentialRampToValueAtTime(1420, now + 1.4);
osc2.frequency.exponentialRampToValueAtTime(65, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.45, now + 1.3);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc1.connect(env);
osc2.connect(env);
env.connect(this.gainNode);
osc1.start(now);
osc2.start(now);
osc1.stop(now + duration);
osc2.stop(now + duration);
}
/**
* Battlestar Galactica DRADIS Sonar Ping (Military Space)
* The iconic tactical combat contact echo
*/
synthesizeDradisPing() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 1.4;
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(1860, now);
osc.frequency.exponentialRampToValueAtTime(1540, now + 0.08);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.35, now + 0.01);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* FTL Jump Thunderclap (BSG / Military Space)
* Sudden vacuum displacement shockwave
*/
synthesizeFtlJump() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 2.2;
const noiseBuffer = this.am.createNoiseBuffer('brown', 2.5);
const noise = ctx.createBufferSource();
noise.buffer = noiseBuffer;
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(800, now);
filter.frequency.exponentialRampToValueAtTime(45, now + 1.8);
const env = ctx.createGain();
env.gain.setValueAtTime(0.8, now);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
noise.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
noise.start(now);
noise.stop(now + duration);
}
/**
* Retro Astrogator Tone Glide (Jupiter 2 / Retro Future)
* 1960s Theremin / electronic oscillator glissando
*/
synthesizeRetroAstrogator() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 1.6;
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(440, now);
osc.frequency.linearRampToValueAtTime(1180, now + 0.6);
osc.frequency.linearRampToValueAtTime(320, now + 1.1);
osc.frequency.linearRampToValueAtTime(660, now + duration);
const vibrato = ctx.createOscillator();
vibrato.type = 'sine';
vibrato.frequency.setValueAtTime(8, now);
const vibGain = ctx.createGain();
vibGain.gain.setValueAtTime(25, now);
vibrato.connect(vibGain);
vibGain.connect(osc.frequency);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.3, now + 0.1);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
vibrato.start(now);
osc.stop(now + duration);
vibrato.stop(now + duration);
}
/**
* HAL 9000 Logic Confirmation Chime (Discovery One)
*/
synthesizeHalChime() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const f1 = 784; // G5
const f2 = 523; // C5
[0, 0.14].forEach((offset, idx) => {
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(idx === 0 ? f1 : f2, now + offset);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now + offset);
env.gain.linearRampToValueAtTime(0.25, now + offset + 0.01);
env.gain.exponentialRampToValueAtTime(0.001, now + offset + 0.5);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now + offset);
osc.stop(now + offset + 0.52);
});
}
/**
* Event Horizon Gravity Singularity Pulse (Deep Space)
* Deep sub-bass dimensional warping thrum
*/
synthesizeSingularityEngage() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 3.2;
const sub = ctx.createOscillator();
sub.type = 'sine';
sub.frequency.setValueAtTime(95, now);
sub.frequency.exponentialRampToValueAtTime(28, now + 2.2);
const mod = ctx.createOscillator();
mod.type = 'triangle';
mod.frequency.setValueAtTime(14, now);
mod.frequency.linearRampToValueAtTime(45, now + 1.8);
const modGain = ctx.createGain();
modGain.gain.setValueAtTime(60, now);
mod.connect(modGain);
modGain.connect(sub.frequency);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.65, now + 1.5);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
sub.connect(env);
env.connect(this.gainNode);
sub.start(now);
mod.start(now);
sub.stop(now + duration);
mod.stop(now + duration);
}
/**
* Outlaw Afterburner Thruster Surge (Cowboy Bebop / Milano)
*/
synthesizeAfterburner() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 2.4;
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(80, now);
osc.frequency.exponentialRampToValueAtTime(850, now + 0.8);
osc.frequency.linearRampToValueAtTime(620, now + duration);
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(350, now);
filter.frequency.exponentialRampToValueAtTime(1400, now + 0.8);
filter.Q.setValueAtTime(3.0, now);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.5, now + 0.6);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(filter);
filter.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Space Station Docking Clamp Latch & Airlock Purge
*/
synthesizeDockingClamp() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
// Heavy mechanical solenoid impact
const osc = ctx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(380, now);
osc.frequency.exponentialRampToValueAtTime(65, now + 0.12);
const env = ctx.createGain();
env.gain.setValueAtTime(0.5, now);
env.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + 0.25);
// Followed by pneumatic seal hiss
setTimeout(() => {
if (!this.am.ctx) return;
const t = this.am.ctx.currentTime;
const hissBuf = this.am.createNoiseBuffer('white', 1.2);
const hiss = this.am.ctx.createBufferSource();
hiss.buffer = hissBuf;
const hFilter = this.am.ctx.createBiquadFilter();
hFilter.type = 'bandpass';
hFilter.frequency.setValueAtTime(2200, t);
hFilter.Q.setValueAtTime(2.5, t);
const hEnv = this.am.ctx.createGain();
hEnv.gain.setValueAtTime(0.001, t);
hEnv.gain.linearRampToValueAtTime(0.25, t + 0.05);
hEnv.gain.exponentialRampToValueAtTime(0.001, t + 0.9);
hiss.connect(hFilter);
hFilter.connect(hEnv);
hEnv.connect(this.gainNode);
hiss.start(t);
hiss.stop(t + 0.9);
}, 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)
*/
synthesizeLudicrousSpeed() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 3.2;
const osc = ctx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(60, now);
osc.frequency.exponentialRampToValueAtTime(5400, now + 2.2);
const env = ctx.createGain();
env.gain.setValueAtTime(0.01, now);
env.gain.linearRampToValueAtTime(0.4, now + 1.8);
env.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Infinite Improbability Reality-Warp Flip (Heart of Gold)
*/
synthesizeImprobabilityFlip() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 1.8;
const osc = ctx.createOscillator();
osc.type = 'triangle';
osc.frequency.setValueAtTime(1400, now);
osc.frequency.exponentialRampToValueAtTime(180, now + 0.7);
osc.frequency.exponentialRampToValueAtTime(2200, now + 1.3);
osc.frequency.exponentialRampToValueAtTime(440, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.35, now + 0.1);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
osc.start(now);
osc.stop(now + duration);
}
/**
* Geiger Counter Click Burst (Nostromo / Mining)
*/
synthesizeGeigerBurst() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const clicks = 8 + Math.floor(Math.random() * 8);
for (let i = 0; i < clicks; i++) {
const clickTime = now + (i * 0.04) + (Math.random() * 0.03);
const osc = ctx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(2800 + Math.random() * 800, clickTime);
const env = ctx.createGain();
env.gain.setValueAtTime(0.18, clickTime);
env.gain.exponentialRampToValueAtTime(0.001, clickTime + 0.015);
osc.connect(env);
env.connect(this.gainNode);
osc.start(clickTime);
osc.stop(clickTime + 0.016);
}
}
/**
* Cheerful Door Sigh (Heart of Gold / Sirius Cybernetics Corp)
*/
synthesizeCheerfulDoor() {
this.init();
const ctx = this.am.ctx;
if (!ctx) return;
const now = ctx.currentTime;
const duration = 0.9;
const osc = ctx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(620, now);
osc.frequency.linearRampToValueAtTime(840, now + 0.35);
osc.frequency.linearRampToValueAtTime(520, now + duration);
const env = ctx.createGain();
env.gain.setValueAtTime(0.001, now);
env.gain.linearRampToValueAtTime(0.25, now + 0.15);
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
osc.connect(env);
env.connect(this.gainNode);
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 (5001000 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 (3003200 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;
/**
* 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;