UNPKG

handible

Version:

Revolutionary hand tracking and gesture control for the web. Transform any webcam into a powerful 3D controller with MediaPipe and Three.js.

195 lines (151 loc) 6.89 kB
// Audio System for Button Sounds and UI Feedback // Provides high-quality, low-latency audio feedback for user interactions class AudioSystem { constructor() { this.audioContext = null; this.isEnabled = true; this.volume = 0.3; this.init(); } init() { try { // Create audio context this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.warn('Web Audio API not supported, button sounds disabled'); this.isEnabled = false; } } // Create a click sound using oscillators createClickSound() { if (!this.audioContext || !this.isEnabled) return; try { // Resume audio context if it's suspended (required by modern browsers) if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } const oscillator = this.audioContext.createOscillator(); const gainNode = this.audioContext.createGain(); // Connect oscillator to gain to destination oscillator.connect(gainNode); gainNode.connect(this.audioContext.destination); // Configure the click sound oscillator.frequency.setValueAtTime(800, this.audioContext.currentTime); // High frequency for click oscillator.frequency.exponentialRampToValueAtTime(400, this.audioContext.currentTime + 0.1); // Drop frequency // Configure envelope (quick attack, fast decay) gainNode.gain.setValueAtTime(0, this.audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(this.volume, this.audioContext.currentTime + 0.01); // Quick attack gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.15); // Quick decay // Start and stop oscillator.start(this.audioContext.currentTime); oscillator.stop(this.audioContext.currentTime + 0.15); } catch (e) { console.warn('Error playing click sound:', e); } } // Create a success sound for scene switches createSuccessSound() { if (!this.audioContext || !this.isEnabled) return; try { if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } const oscillator = this.audioContext.createOscillator(); const gainNode = this.audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(this.audioContext.destination); // Success sound: rising tone oscillator.frequency.setValueAtTime(523, this.audioContext.currentTime); // C5 oscillator.frequency.linearRampToValueAtTime(659, this.audioContext.currentTime + 0.1); // E5 oscillator.frequency.linearRampToValueAtTime(784, this.audioContext.currentTime + 0.2); // G5 // Smoother envelope for success sound gainNode.gain.setValueAtTime(0, this.audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(this.volume * 0.6, this.audioContext.currentTime + 0.05); gainNode.gain.linearRampToValueAtTime(this.volume * 0.4, this.audioContext.currentTime + 0.15); gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.3); oscillator.start(this.audioContext.currentTime); oscillator.stop(this.audioContext.currentTime + 0.3); } catch (e) { console.warn('Error playing success sound:', e); } } // Create a hover sound for button hover effects createHoverSound() { if (!this.audioContext || !this.isEnabled) return; try { if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } const oscillator = this.audioContext.createOscillator(); const gainNode = this.audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(this.audioContext.destination); // Subtle hover sound oscillator.frequency.setValueAtTime(600, this.audioContext.currentTime); // Very subtle envelope for hover gainNode.gain.setValueAtTime(0, this.audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(this.volume * 0.2, this.audioContext.currentTime + 0.02); gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.08); oscillator.start(this.audioContext.currentTime); oscillator.stop(this.audioContext.currentTime + 0.08); } catch (e) { console.warn('Error playing hover sound:', e); } } // Create an error sound for invalid actions createErrorSound() { if (!this.audioContext || !this.isEnabled) return; try { if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } const oscillator = this.audioContext.createOscillator(); const gainNode = this.audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(this.audioContext.destination); // Error sound: low, buzzy tone oscillator.frequency.setValueAtTime(200, this.audioContext.currentTime); oscillator.frequency.linearRampToValueAtTime(150, this.audioContext.currentTime + 0.2); // Sharp envelope for error gainNode.gain.setValueAtTime(0, this.audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(this.volume * 0.4, this.audioContext.currentTime + 0.02); gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.2); oscillator.start(this.audioContext.currentTime); oscillator.stop(this.audioContext.currentTime + 0.2); } catch (e) { console.warn('Error playing error sound:', e); } } setVolume(volume) { this.volume = Math.max(0, Math.min(1, volume)); } setEnabled(enabled) { this.isEnabled = enabled; } getVolume() { return this.volume; } getEnabled() { return this.isEnabled; } } // Create and export the global audio system instance export const audioSystem = new AudioSystem(); // Convenience functions for console control export const toggleButtonSounds = () => { audioSystem.setEnabled(!audioSystem.isEnabled); console.log(`Button sounds ${audioSystem.isEnabled ? 'enabled' : 'disabled'}`); return audioSystem.isEnabled; }; export const setButtonVolume = (volume) => { audioSystem.setVolume(volume); console.log(`Button volume set to ${audioSystem.volume}`); return audioSystem.volume; }; // Make functions available globally for console access if (typeof window !== 'undefined') { window.toggleButtonSounds = toggleButtonSounds; window.setButtonVolume = setButtonVolume; } // Export the AudioSystem class as well for custom instances export { AudioSystem };