Chess_Engine / web /src /services /soundService.ts
electro-sb's picture
Containerization and frontend backend communication issue fixed
d086f83
// Sound service for chess UI
// Define sound types
type SoundType = 'move' | 'capture' | 'check' | 'castle' | 'game-end' | 'promote';
// Map of sound files
const SOUND_FILES: Record<SoundType, string> = {
'move': '/assets/sounds/move.mp3',
'capture': '/assets/sounds/capture.mp3',
'check': '/assets/sounds/check.mp3',
'castle': '/assets/sounds/castle.mp3',
'game-end': '/assets/sounds/game-end.mp3',
'promote': '/assets/sounds/move.mp3' // Reusing move sound for promote
};
// Audio objects for preloading
const audioCache: Record<SoundType, HTMLAudioElement> = {} as Record<SoundType, HTMLAudioElement>;
// Sound enabled flag (can be toggled by user)
let soundEnabled = true;
// Create fallback sounds using the Web Audio API if sound files are not available
const audioContext = typeof AudioContext !== 'undefined' ? new AudioContext() : null;
// Function to create a simple beep sound
const createBeepSound = (frequency: number, duration: number): AudioBuffer | null => {
if (!audioContext) return null;
const sampleRate = audioContext.sampleRate;
const buffer = audioContext.createBuffer(1, sampleRate * duration, sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < buffer.length; i++) {
data[i] = Math.sin(2 * Math.PI * frequency * i / sampleRate) *
(i < buffer.length * 0.1 ? i / (buffer.length * 0.1) :
i > buffer.length * 0.9 ? (buffer.length - i) / (buffer.length * 0.1) : 1);
}
return buffer;
};
// Create fallback sounds
const fallbackSounds: Record<SoundType, AudioBuffer | null> = {
'move': audioContext ? createBeepSound(440, 0.1) : null, // A4 note
'capture': audioContext ? createBeepSound(330, 0.2) : null, // E4 note
'check': audioContext ? createBeepSound(587, 0.3) : null, // D5 note
'castle': audioContext ? createBeepSound(261, 0.3) : null, // C4 note
'game-end': audioContext ? createBeepSound(196, 0.5) : null, // G3 note
'promote': audioContext ? createBeepSound(523, 0.2) : null // C5 note
};
/**
* Preload all sound files
*/
export const preloadSounds = (): void => {
Object.entries(SOUND_FILES).forEach(([key, file]) => {
try {
const audio = new Audio();
audio.src = file;
audio.preload = 'auto';
audioCache[key as SoundType] = audio;
} catch (error) {
console.error(`Failed to preload sound: ${file}`, error);
}
});
};
/**
* Play a sound effect
* @param type The type of sound to play
*/
export const playSound = (type: SoundType): void => {
if (!soundEnabled) return;
try {
// Try to play the audio file
const audio = audioCache[type];
if (audio) {
audio.currentTime = 0;
audio.play().catch(err => {
console.warn('Error playing sound file, using fallback:', err);
playFallbackSound(type);
});
} else {
// Try to create a new audio element
const newAudio = new Audio(SOUND_FILES[type]);
newAudio.play().catch(err => {
console.warn('Error playing sound file, using fallback:', err);
playFallbackSound(type);
});
}
} catch (error) {
console.error(`Failed to play sound: ${type}`, error);
playFallbackSound(type);
}
};
/**
* Play a fallback sound using Web Audio API
* @param type The type of sound to play
*/
const playFallbackSound = (type: SoundType): void => {
if (!audioContext) return;
try {
const buffer = fallbackSounds[type];
if (buffer) {
const source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start();
} else {
// Create a simple beep as last resort
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Set properties based on sound type
switch (type) {
case 'move':
oscillator.frequency.value = 440; // A4
gainNode.gain.value = 0.1;
break;
case 'capture':
oscillator.frequency.value = 330; // E4
gainNode.gain.value = 0.2;
break;
case 'check':
oscillator.frequency.value = 587; // D5
gainNode.gain.value = 0.3;
break;
case 'castle':
oscillator.frequency.value = 261; // C4
gainNode.gain.value = 0.2;
break;
case 'game-end':
oscillator.frequency.value = 196; // G3
gainNode.gain.value = 0.3;
break;
case 'promote':
oscillator.frequency.value = 523; // C5
gainNode.gain.value = 0.2;
break;
}
// Start and stop the oscillator
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.2);
}
} catch (error) {
console.error('Failed to play fallback sound:', error);
}
};
/**
* Play a sound if sound is enabled
* @param type The type of sound to play
*/
export const playSoundIfEnabled = (type: SoundType): void => {
if (soundEnabled) {
playSound(type);
}
};
/**
* Toggle sound on/off
* @param enabled Whether sound should be enabled
*/
export const toggleSound = (enabled?: boolean): boolean => {
if (enabled !== undefined) {
soundEnabled = enabled;
} else {
soundEnabled = !soundEnabled;
}
return soundEnabled;
};
/**
* Set sound enabled state
* @param enabled Whether sound should be enabled
*/
export const setSoundEnabled = (enabled: boolean): void => {
soundEnabled = enabled;
};
/**
* Check if sound is enabled
*/
export const isSoundEnabled = (): boolean => {
return soundEnabled;
};