This commit is contained in:
2026-04-05 02:16:09 +02:00
parent d678560d60
commit 1bc57a99ce
2 changed files with 8423 additions and 154 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -15,24 +15,68 @@ export function RetroSoundTest() {
return audioCtxRef.current; return audioCtxRef.current;
}; };
const playSound = (freq: number, type: OscillatorType, duration: number, vol: number, fade: boolean = false) => { // Helper: Create audio chain (oscillator → filter → gain → output)
const createFilteredOscillator = (
ctx: AudioContext,
startTime: number,
hz: number,
volume: number,
oscType: OscillatorType,
filterFreq: number
) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const filter = ctx.createBiquadFilter();
osc.type = oscType;
osc.frequency.setValueAtTime(hz, startTime);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(filterFreq, startTime);
filter.Q.setValueAtTime(1, startTime);
gain.gain.setValueAtTime(volume, startTime);
osc.connect(filter).connect(gain).connect(ctx.destination);
return { osc, gain };
};
const playSound = (
freq: number,
type: OscillatorType,
duration: number,
vol: number,
fade: boolean = false,
useFilter: boolean = false,
filterFreq: number = 800
) => {
const ctx = getAudioCtx(); const ctx = getAudioCtx();
const startTime = ctx.currentTime;
if (useFilter) {
const { osc, gain } = createFilteredOscillator(ctx, startTime, freq, vol, type, filterFreq);
if (fade) {
gain.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);
}
osc.start(startTime);
osc.stop(startTime + duration);
} else {
const osc = ctx.createOscillator(); const osc = ctx.createOscillator();
const gain = ctx.createGain(); const gain = ctx.createGain();
osc.type = type; osc.type = type;
osc.frequency.setValueAtTime(freq, ctx.currentTime); osc.frequency.setValueAtTime(freq, startTime);
gain.gain.setValueAtTime(vol, startTime);
gain.gain.setValueAtTime(vol, ctx.currentTime);
if (fade) { if (fade) {
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + duration); gain.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);
} }
osc.connect(gain); osc.connect(gain).connect(ctx.destination);
gain.connect(ctx.destination); osc.start(startTime);
osc.stop(startTime + duration);
osc.start(); }
osc.stop(ctx.currentTime + duration);
}; };
// White noise (for 8-bit style noise) // White noise (for 8-bit style noise)
@@ -62,8 +106,8 @@ export function RetroSoundTest() {
// Play sound repeatedly with random delays // Play sound repeatedly with random delays
const playRepeating = () => { const playRepeating = () => {
// Play the sound // Play the sound - triangle wave with low-pass filter for dull, machine-like tone
playSound(1000, 'sawtooth', 0.08, 0.1); playSound(1000, 'triangle', 0.08, 0.1, false, true, 800);
playNoise(0.09, 0.02); playNoise(0.09, 0.02);
// Schedule next play with random delay (0-1000ms) // Schedule next play with random delay (0-1000ms)
@@ -96,72 +140,143 @@ export function RetroSoundTest() {
}; };
}, []); }, []);
// Crew Bailout Alarm - 90s style aggressive buzzer // Beep composer - creates sequential beeps with automatic timing
const crewBailout = (durationSeconds: number) => { const createBeepSequence = () => {
const ctx = getAudioCtx(); const ctx = getAudioCtx();
const startTime = ctx.currentTime; let currentTime = ctx.currentTime;
const endTime = startTime + durationSeconds;
// Timing constants return {
const beepDuration = 0.06; // 60ms ON beep: (
const pauseDuration = 0.06; // 60ms OFF hz: number,
const cycleDuration = beepDuration + pauseDuration; // 120ms total ~ 8 beeps/sec duration: number,
fade: boolean = false,
autoOffset: boolean = true,
oscType: OscillatorType = 'triangle',
volume: number = 0.12,
filterFreq: number = 800,
addNoise: boolean = false
) => {
const { osc, gain } = createFilteredOscillator(ctx, currentTime, hz, volume, oscType, filterFreq);
const oscillators: OscillatorNode[] = []; if (fade) {
const gainNodes: GainNode[] = []; gain.gain.exponentialRampToValueAtTime(0.0001, currentTime + duration);
let currentTime = startTime;
// Generate beeps for the entire duration
while (currentTime < endTime) {
// Create two oscillators for beating effect
const osc1 = ctx.createOscillator();
const osc2 = ctx.createOscillator();
const gain = ctx.createGain();
osc1.type = 'square';
osc2.type = 'square';
osc1.frequency.setValueAtTime(2850, currentTime);
osc2.frequency.setValueAtTime(2855, currentTime); // 5Hz difference for beating
// Envelope
const attackTime = 0.002; // 2ms attack
const releaseTime = 0.01; // 10ms release
// Attack
gain.gain.setValueAtTime(0, currentTime);
gain.gain.linearRampToValueAtTime(0.15, currentTime + attackTime);
// Sustain
gain.gain.setValueAtTime(0.15, currentTime + beepDuration - releaseTime);
// Release
gain.gain.linearRampToValueAtTime(0, currentTime + beepDuration);
// Connect nodes
osc1.connect(gain);
osc2.connect(gain);
gain.connect(ctx.destination);
// Start and stop
osc1.start(currentTime);
osc2.start(currentTime);
osc1.stop(currentTime + beepDuration);
osc2.stop(currentTime + beepDuration);
// Store for cleanup
oscillators.push(osc1, osc2);
gainNodes.push(gain);
// Move to next beep
currentTime += cycleDuration;
} }
// Cleanup after completion osc.start(currentTime);
osc.stop(currentTime + duration + 0.05);
// Add vintage noise/hiss for realism
if (addNoise) {
const noiseBufferSize = ctx.sampleRate * duration;
const noiseBuffer = ctx.createBuffer(1, noiseBufferSize, ctx.sampleRate);
const noiseOutput = noiseBuffer.getChannelData(0);
for (let i = 0; i < noiseBufferSize; i++) {
noiseOutput[i] = (Math.random() * 2 - 1) * 0.02; // Low-level noise
}
const noiseSource = ctx.createBufferSource();
noiseSource.buffer = noiseBuffer;
const noiseGain = ctx.createGain();
noiseGain.gain.setValueAtTime(0.8, currentTime);
noiseSource.connect(noiseGain).connect(ctx.destination);
noiseSource.start(currentTime);
}
if (autoOffset) currentTime += duration;
return currentTime;
},
// Multi-tone beep (plays multiple frequencies simultaneously)
multiBeep: (
frequencies: number[],
duration: number,
fade: boolean = false,
autoOffset: boolean = true,
oscType: OscillatorType | OscillatorType[] = 'square',
volume: number = 0.08,
filterFreq: number = 1200,
addNoise: boolean = true
) => {
// Play multiple oscillators at once
frequencies.forEach((hz, index) => {
const vol = volume * (index === 0 ? 1 : 0.7); // First tone louder
const type = Array.isArray(oscType) ? oscType[index] || oscType[0] : oscType;
const { osc, gain } = createFilteredOscillator(ctx, currentTime, hz, vol, type, filterFreq);
if (fade) {
gain.gain.exponentialRampToValueAtTime(0.0001, currentTime + duration);
}
osc.start(currentTime);
osc.stop(currentTime + duration + 0.05);
});
// Add noise
if (addNoise) {
const noiseBufferSize = ctx.sampleRate * duration;
const noiseBuffer = ctx.createBuffer(1, noiseBufferSize, ctx.sampleRate);
const noiseOutput = noiseBuffer.getChannelData(0);
for (let i = 0; i < noiseBufferSize; i++) {
noiseOutput[i] = (Math.random() * 2 - 1) * 0.006; // Much quieter vintage hiss
}
const noiseSource = ctx.createBufferSource();
noiseSource.buffer = noiseBuffer;
const noiseGain = ctx.createGain();
noiseGain.gain.setValueAtTime(0.3, currentTime); // Reduced from 1.0
noiseSource.connect(noiseGain).connect(ctx.destination);
noiseSource.start(currentTime);
}
if (autoOffset) currentTime += duration;
return currentTime;
},
gap: (duration: number) => {
currentTime += duration;
return currentTime;
},
getTime: () => currentTime,
setTime: (time: number) => { currentTime = time; }
};
};
// Patlabor notification pattern - authentic multi-tone from spectrum analysis
const patlaborPattern = () => {
const seq = createBeepSequence();
// Timing from spectrogram: 0.8s beep, 0.1s gap, 0.8s beep, 0.1s gap, 1.5s beep
seq.multiBeep([1000, 3000], 0.7, false, true, 'sawtooth'); // First beep with dual tones
seq.multiBeep([800, 2500], 0.35, false, true, 'sawtooth');
seq.multiBeep([1000, 3000], 0.7, false, true, 'sawtooth');
seq.multiBeep([800, 2500], 0.35, false, true, 'sawtooth');
seq.multiBeep([1000, 3000], 2.85, false, true, 'sawtooth');
};
// Custom demo pattern (high-low-high with square waves)
const customPattern = () => {
const seq = createBeepSequence();
seq.beep(1400, 0.6, false, true, 'square');
seq.gap(0.2);
seq.beep(700, 0.6, false, true, 'square');
seq.gap(0.2);
seq.beep(1400, 1.0, true, true, 'square');
};
// Rising beeps sweep
const risingBeeps = () => {
for (let i = 0; i < 8; i++) {
setTimeout(() => { setTimeout(() => {
oscillators.forEach(osc => osc.disconnect()); playSound(1800 + i * 50, 'square', 0.03, 0.08);
gainNodes.forEach(gain => gain.disconnect()); playNoise(0.03, 0.015);
}, durationSeconds * 1000 + 100); }, i * 40);
}
}; };
return ( return (
@@ -176,71 +291,12 @@ export function RetroSoundTest() {
<h3 style={{ margin: '0 0 10px 0' }}>Preset Sound</h3> <h3 style={{ margin: '0 0 10px 0' }}>Preset Sound</h3>
<button <button
style={{ padding: '10px 20px', backgroundColor: '#7f8c8d', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold' }} style={{ padding: '10px 20px', backgroundColor: '#7f8c8d', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold' }}
onClick={() => { onClick={risingBeeps}
for (let i = 0; i < 8; i++) {
setTimeout(() => {
playSound(1800 + i * 50, 'square', 0.03, 0.08);
playNoise(0.03, 0.015);
}, i * 40);
}
}}
> >
RISING BEEPS (sweep) RISING BEEPS (sweep)
</button> </button>
</div> </div>
{/* Crew Bailout Alarm - 90s Style */}
<div style={{ marginTop: '20px', padding: '15px', backgroundColor: '#c0392b', borderRadius: '8px' }}>
<h3 style={{ margin: '0 0 10px 0', color: 'white' }}>🚨 Crew Bailout Alarm (90s)</h3>
<p style={{ fontSize: '12px', color: 'white', margin: '0 0 10px 0' }}>
Agresivní mechanický bzučák: 2850 Hz + 2855 Hz square, 8 beeps/sec
</p>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<button
style={{
padding: '10px 20px',
backgroundColor: '#a93226',
color: 'white',
border: '2px solid white',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold'
}}
onClick={() => crewBailout(2)}
>
2 sekundy
</button>
<button
style={{
padding: '10px 20px',
backgroundColor: '#922b21',
color: 'white',
border: '2px solid white',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold'
}}
onClick={() => crewBailout(5)}
>
🚨 5 sekund
</button>
<button
style={{
padding: '10px 20px',
backgroundColor: '#7b241c',
color: 'white',
border: '2px solid white',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold'
}}
onClick={() => crewBailout(10)}
>
💀 10 sekund
</button>
</div>
</div>
{/* Repeating Sound Toggle */} {/* Repeating Sound Toggle */}
<div style={{ marginTop: '20px', padding: '15px', backgroundColor: isRepeating ? '#e74c3c' : '#27ae60', borderRadius: '8px' }}> <div style={{ marginTop: '20px', padding: '15px', backgroundColor: isRepeating ? '#e74c3c' : '#27ae60', borderRadius: '8px' }}>
<h3 style={{ margin: '0 0 10px 0', color: 'white' }}>🔁 Test</h3> <h3 style={{ margin: '0 0 10px 0', color: 'white' }}>🔁 Test</h3>
@@ -259,22 +315,43 @@ export function RetroSoundTest() {
> >
{isRepeating ? '⏹ STOP' : '▶ START'} Random Buzzer {isRepeating ? '⏹ STOP' : '▶ START'} Random Buzzer
</button> </button>
<h3 style={{ margin: '0 0 10px 0', color: 'white' }}>Patlabor notify effect</h3> <h3 style={{ margin: '15px 0 10px 0', color: 'white' }}>🤖 Patlabor Notify Pattern</h3>
<p style={{ fontSize: '12px', color: 'white', margin: '0 0 10px 0' }}>
0.8s 0.1s gap 0.8s 0.1s gap 1.5s | 1000Hz+3000Hz | Total: 3.3s
</p>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<button <button
style={{ style={{
padding: '12px 24px', padding: '12px 24px',
backgroundColor: isRepeating ? '#c0392b' : '#229954', backgroundColor: '#1e5a8e',
color: 'white', color: 'white',
border: '2px solid blue', border: '2px solid #3498db',
borderRadius: '4px', borderRadius: '4px',
cursor: 'pointer', cursor: 'pointer',
fontWeight: 'bold', fontWeight: 'bold',
fontSize: '16px' fontSize: '16px'
}} }}
onClick={() => playSound(380, 'square', 0.1, 0.1)} onClick={patlaborPattern}
> >
Patlabor Notify 🔔 Pattern Notify
</button> </button>
<button
style={{
padding: '12px 24px',
backgroundColor: '#2e7d32',
color: 'white',
border: '2px solid #4caf50',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '16px'
}}
onClick={customPattern}
>
🎵 Custom Pattern
</button>
</div>
</div> </div>
{/* Raw Noise Test */} {/* Raw Noise Test */}
@@ -286,10 +363,10 @@ export function RetroSoundTest() {
key={freq} key={freq}
style={{ padding: '8px 15px', backgroundColor: '#34495e', color: 'white', border: 'none', borderRadius: '4px' }} style={{ padding: '8px 15px', backgroundColor: '#34495e', color: 'white', border: 'none', borderRadius: '4px' }}
onClick={() => { onClick={() => {
playSound(freq, 'sawtooth', 2, 0.1); playSound(freq, 'triangle', 2, 0.1, false, true, 800);
}} }}
> >
{freq} Hz {freq} Hz triangle+filter
</button> </button>
))} ))}
</div> </div>