XGravity/Public/ecma/Sounds/MothershipImpactSound.ecma.js

136 lines
5.3 KiB
JavaScript

"use strict";
const MothershipImpactSound = (function(){
// Curva de saturación analógica para darle máxima pegada sin romper el audio
const make_distortion_curve = (amount = 20) => {
const samples = 44100;
const curve = new Float32Array(samples);
const deg = Math.PI / 180;
for(let i = 0; i < samples; ++i){
const x = (i * 2) / samples - 1;
curve[i] = ((3 + amount) * x * 20 * deg) / (Math.PI + amount * Math.abs(x));
}
return curve;
};
return function(audio_ctx, output_node, volume_multiplier = 1.0){
const now = audio_ctx.currentTime;
const duration = 2.4; // Retumbe largo y cavernoso
// Bus maestro del efecto con booster de presencia
const master_bus = audio_ctx.createGain();
master_bus.gain.setValueAtTime(volume_multiplier * 1.35, now);
// Saturador suave para sonido "aplastante"
const shaper = audio_ctx.createWaveShaper();
shaper.curve = make_distortion_curve(18);
shaper.oversample = "2x";
// ========================================================
// CAPA 1: SUB-GRAVE SÍSMICO (Terremoto acústico en cabina)
// ========================================================
const sub_osc = audio_ctx.createOscillator();
const sub_gain = audio_ctx.createGain();
sub_osc.type = "sine";
sub_osc.frequency.setValueAtTime(145, now);
// Caída drástica a frecuencias casi inaudibles pero que mueven aire
sub_osc.frequency.exponentialRampToValueAtTime(22, now + 0.45);
sub_osc.frequency.setValueAtTime(20, now + duration);
sub_gain.gain.setValueAtTime(0.001, now);
sub_gain.gain.linearRampToValueAtTime(1.0, now + 0.015);
sub_gain.gain.exponentialRampToValueAtTime(0.001, now + 1.8);
sub_osc.connect(sub_gain);
sub_gain.connect(shaper);
// ========================================================
// CAPA 2: CRUJIDO DE CASCO Y METRALLA (Ráfaga de ruido blanco)
// ========================================================
const buffer_size = Math.floor(audio_ctx.sampleRate * 1.5);
const noise_buffer = audio_ctx.createBuffer(1, buffer_size, audio_ctx.sampleRate);
const noise_data = noise_buffer.getChannelData(0);
for(let i = 0; i < buffer_size; i++){
noise_data[i] = (Math.random() * 2 - 1);
}
const noise_src = audio_ctx.createBufferSource();
noise_src.buffer = noise_buffer;
const noise_filter = audio_ctx.createBiquadFilter();
noise_filter.type = "lowpass";
noise_filter.frequency.setValueAtTime(4200, now);
noise_filter.frequency.exponentialRampToValueAtTime(110, now + 1.2);
noise_filter.Q.setValueAtTime(2.5, now);
const noise_env = audio_ctx.createGain();
noise_env.gain.setValueAtTime(0.001, now);
noise_env.gain.linearRampToValueAtTime(0.9, now + 0.02);
noise_env.gain.exponentialRampToValueAtTime(0.001, now + 1.4);
noise_src.connect(noise_filter);
noise_filter.connect(noise_env);
noise_env.connect(shaper);
// ========================================================
// CAPA 3: DEFORMACIÓN METÁLICA (Resonador desafinado)
// ========================================================
const metal_osc_a = audio_ctx.createOscillator();
const metal_osc_b = audio_ctx.createOscillator();
const metal_gain = audio_ctx.createGain();
metal_osc_a.type = "sawtooth";
metal_osc_b.type = "triangle";
metal_osc_a.frequency.setValueAtTime(78, now);
metal_osc_a.frequency.exponentialRampToValueAtTime(32, now + 0.9);
metal_osc_b.frequency.setValueAtTime(114, now);
metal_osc_b.frequency.exponentialRampToValueAtTime(38, now + 0.9);
metal_gain.gain.setValueAtTime(0.001, now);
metal_gain.gain.linearRampToValueAtTime(0.55, now + 0.04);
metal_gain.gain.exponentialRampToValueAtTime(0.001, now + 1.1);
metal_osc_a.connect(metal_gain);
metal_osc_b.connect(metal_gain);
metal_gain.connect(shaper);
// ========================================================
// CAPA 4: COLA DE REVERBERACIÓN DE LA NAVE (Eco retumbante)
// ========================================================
const rumble_delay = audio_ctx.createDelay();
rumble_delay.delayTime.setValueAtTime(0.075, now);
const rumble_fb = audio_ctx.createGain();
rumble_fb.gain.setValueAtTime(0.48, now);
const rumble_filter = audio_ctx.createBiquadFilter();
rumble_filter.type = "lowpass";
rumble_filter.frequency.setValueAtTime(320, now);
rumble_delay.connect(rumble_filter);
rumble_filter.connect(rumble_fb);
rumble_fb.connect(rumble_delay);
rumble_filter.connect(master_bus);
// Conexiones de salida
shaper.connect(master_bus);
shaper.connect(rumble_delay);
master_bus.connect(output_node);
// Disparo coordinado de los generadores
sub_osc.start(now);
noise_src.start(now);
metal_osc_a.start(now);
metal_osc_b.start(now);
sub_osc.stop(now + duration);
noise_src.stop(now + 1.5);
metal_osc_a.stop(now + 1.2);
metal_osc_b.stop(now + 1.2);
};
})();