UNPKG

advanced-games-library

Version:

Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes

610 lines (535 loc) 17.9 kB
/** * Enhanced Audio System - מערכת אודיו מתקדמת * מספקת ניהול מתקדם של צלילים, מוזיקה ואפקטים קוליים * תומכת ב-3D audio, reverb, filters ועוד */ export interface AudioTrack { id: string; url: string; type: 'sound' | 'music' | 'voice' | 'ambient'; volume: number; loop: boolean; fadeDuration?: number; reverb?: ReverbSettings; filter?: FilterSettings; spatialSettings?: SpatialAudioSettings; } export interface ReverbSettings { enabled: boolean; roomSize: number; // 0-1 damping: number; // 0-1 wetLevel: number; // 0-1 dryLevel: number; // 0-1 } export interface FilterSettings { type: 'lowpass' | 'highpass' | 'bandpass' | 'notch'; frequency: number; // Hz q: number; // Quality factor gain?: number; // dB (for peaking filters) } export interface SpatialAudioSettings { position: { x: number; y: number; z: number }; orientation?: { x: number; y: number; z: number }; maxDistance: number; rolloffFactor: number; coneInnerAngle?: number; coneOuterAngle?: number; coneOuterGain?: number; } export interface AudioContext { masterVolume: number; musicVolume: number; soundVolume: number; voiceVolume: number; ambientVolume: number; globalReverb: ReverbSettings; listenerPosition: { x: number; y: number; z: number }; listenerOrientation: { forward: number[]; up: number[] }; } export interface AudioAnalytics { totalTracksPlayed: number; averagePlayDuration: number; mostPlayedTracks: { [trackId: string]: number }; audioErrors: Array<{ timestamp: number; trackId: string; error: string; userAgent: string; }>; performanceMetrics: { audioLatency: number; bufferUnderruns: number; memoryUsage: number; }; } class EnhancedAudioSystem { private context: AudioContext; private tracks: Map<string, AudioTrack> = new Map(); private activeAudio: Map<string, any> = new Map(); // Web Audio API objects private analytics: AudioAnalytics; private audioContext?: any; // Web Audio API context private masterGainNode?: any; private convolver?: any; // For reverb private analyser?: any; // For audio analysis private spatialProcessor?: any; // For 3D audio constructor() { this.context = { masterVolume: 1.0, musicVolume: 0.8, soundVolume: 1.0, voiceVolume: 1.0, ambientVolume: 0.6, globalReverb: { enabled: false, roomSize: 0.5, damping: 0.5, wetLevel: 0.3, dryLevel: 0.7, }, listenerPosition: { x: 0, y: 0, z: 0 }, listenerOrientation: { forward: [0, 0, -1], up: [0, 1, 0] } }; this.analytics = { totalTracksPlayed: 0, averagePlayDuration: 0, mostPlayedTracks: {}, audioErrors: [], performanceMetrics: { audioLatency: 0, bufferUnderruns: 0, memoryUsage: 0, }, }; this.initializeAudioSystem(); } /** * אתחול מערכת האודיו עם Web Audio API */ private async initializeAudioSystem(): Promise<void> { try { // Initialize Web Audio Context if (typeof window !== 'undefined' && window.AudioContext) { this.audioContext = new window.AudioContext(); this.masterGainNode = this.audioContext.createGain(); this.masterGainNode.connect(this.audioContext.destination); // Initialize reverb convolver this.convolver = this.audioContext.createConvolver(); this.createReverbImpulseResponse(); // Initialize analyser for audio visualization this.analyser = this.audioContext.createAnalyser(); this.analyser.fftSize = 2048; this.analyser.connect(this.masterGainNode); // Initialize spatial audio processor this.initializeSpatialAudio(); console.log('🔊 Enhanced Audio System initialized'); } } catch (error) { this.logError('SYSTEM_INIT', error as Error); console.warn('Audio system fallback to basic mode'); } } /** * אתחול אודיו מרחבי (3D Audio) */ private initializeSpatialAudio(): void { if (this.audioContext) { // Set up listener for 3D audio const listener = this.audioContext.listener; if (listener.positionX) { // Modern API listener.positionX.value = this.context.listenerPosition.x; listener.positionY.value = this.context.listenerPosition.y; listener.positionZ.value = this.context.listenerPosition.z; listener.forwardX.value = this.context.listenerOrientation.forward[0]; listener.forwardY.value = this.context.listenerOrientation.forward[1]; listener.forwardZ.value = this.context.listenerOrientation.forward[2]; listener.upX.value = this.context.listenerOrientation.up[0]; listener.upY.value = this.context.listenerOrientation.up[1]; listener.upZ.value = this.context.listenerOrientation.up[2]; } else if (listener.setPosition) { // Legacy API listener.setPosition( this.context.listenerPosition.x, this.context.listenerPosition.y, this.context.listenerPosition.z ); listener.setOrientation( ...this.context.listenerOrientation.forward, ...this.context.listenerOrientation.up ); } } } /** * יצירת תגובת דחף לreverb */ private createReverbImpulseResponse(): void { if (!this.audioContext || !this.convolver) return; const length = this.audioContext.sampleRate * 2; // 2 seconds const impulse = this.audioContext.createBuffer(2, length, this.audioContext.sampleRate); for (let channel = 0; channel < 2; channel++) { const channelData = impulse.getChannelData(channel); for (let i = 0; i < length; i++) { const decay = Math.pow(1 - (i / length), 2); channelData[i] = (Math.random() * 2 - 1) * decay; } } this.convolver.buffer = impulse; } /** * טעינת track אודיו */ async loadTrack(track: AudioTrack): Promise<void> { try { this.tracks.set(track.id, track); // Pre-load audio for better performance if (this.audioContext) { const response = await fetch(track.url); const arrayBuffer = await response.arrayBuffer(); const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer); // Store the decoded buffer for quick access this.activeAudio.set(`${track.id}_buffer`, audioBuffer); } console.log(`🎵 Track loaded: ${track.id}`); } catch (error) { this.logError(track.id, error as Error); throw new Error(`Failed to load track: ${track.id}`); } } /** * נגינת track */ async playTrack(trackId: string, options?: { fadeIn?: number; startTime?: number; playbackRate?: number; volume?: number; }): Promise<void> { try { const track = this.tracks.get(trackId); if (!track || !this.audioContext) { throw new Error(`Track not found or audio context unavailable: ${trackId}`); } // Stop existing instance if playing await this.stopTrack(trackId); const audioBuffer = this.activeAudio.get(`${trackId}_buffer`); if (!audioBuffer) { throw new Error(`Audio buffer not loaded for track: ${trackId}`); } // Create audio source const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.loop = track.loop; if (options?.playbackRate) { source.playbackRate.value = options.playbackRate; } // Create gain node for volume control const gainNode = this.audioContext.createGain(); const volumeMultiplier = this.getVolumeMultiplier(track.type); const finalVolume = (options?.volume ?? track.volume) * volumeMultiplier * this.context.masterVolume; gainNode.gain.value = options?.fadeIn ? 0 : finalVolume; // Apply spatial audio if configured let finalNode: any = gainNode; if (track.spatialSettings && this.audioContext.createPanner) { const panner = this.audioContext.createPanner(); this.configureSpatialAudio(panner, track.spatialSettings); gainNode.connect(panner); finalNode = panner; } // Apply reverb if enabled if (track.reverb?.enabled || this.context.globalReverb.enabled) { const reverbGain = this.audioContext.createGain(); const reverb = track.reverb || this.context.globalReverb; reverbGain.gain.value = reverb.wetLevel; finalNode.connect(reverbGain); reverbGain.connect(this.convolver); this.convolver.connect(this.masterGainNode); // Dry signal const dryGain = this.audioContext.createGain(); dryGain.gain.value = reverb.dryLevel; finalNode.connect(dryGain); dryGain.connect(this.masterGainNode); } else { finalNode.connect(this.masterGainNode); } // Apply filters if configured if (track.filter) { const filter = this.audioContext.createBiquadFilter(); this.configureFilter(filter, track.filter); source.connect(filter); filter.connect(gainNode); } else { source.connect(gainNode); } // Store references for control this.activeAudio.set(trackId, { source, gainNode, startTime: this.audioContext.currentTime + (options?.startTime || 0) }); // Start playback source.start(this.audioContext.currentTime + (options?.startTime || 0)); // Handle fade in if (options?.fadeIn) { gainNode.gain.exponentialRampToValueAtTime( finalVolume, this.audioContext.currentTime + options.fadeIn ); } // Analytics this.updateAnalytics(trackId, 'play'); console.log(`▶️ Playing: ${trackId}`); } catch (error) { this.logError(trackId, error as Error); throw error; } } /** * עצירת track */ async stopTrack(trackId: string, fadeOut?: number): Promise<void> { try { const audioData = this.activeAudio.get(trackId); if (!audioData) return; const { source, gainNode } = audioData; if (fadeOut && this.audioContext) { // Fade out gainNode.gain.exponentialRampToValueAtTime( 0.01, this.audioContext.currentTime + fadeOut ); setTimeout(() => { source.stop(); this.activeAudio.delete(trackId); }, fadeOut * 1000); } else { source.stop(); this.activeAudio.delete(trackId); } console.log(`⏹️ Stopped: ${trackId}`); } catch (error) { this.logError(trackId, error as Error); } } /** * הגדרת עוצמת קול */ setVolume(type: 'master' | 'music' | 'sound' | 'voice' | 'ambient', volume: number): void { volume = Math.max(0, Math.min(1, volume)); switch (type) { case 'master': this.context.masterVolume = volume; if (this.masterGainNode) { this.masterGainNode.gain.value = volume; } break; case 'music': this.context.musicVolume = volume; break; case 'sound': this.context.soundVolume = volume; break; case 'voice': this.context.voiceVolume = volume; break; case 'ambient': this.context.ambientVolume = volume; break; } // Update all active tracks this.updateActiveTrackVolumes(); } /** * עדכון מיקום מאזין (3D Audio) */ updateListenerPosition(position: { x: number; y: number; z: number }): void { this.context.listenerPosition = position; if (this.audioContext?.listener) { const listener = this.audioContext.listener; if (listener.positionX) { listener.positionX.value = position.x; listener.positionY.value = position.y; listener.positionZ.value = position.z; } else if (listener.setPosition) { listener.setPosition(position.x, position.y, position.z); } } } /** * עדכון כיוון מאזין (3D Audio) */ updateListenerOrientation(forward: number[], up: number[]): void { this.context.listenerOrientation = { forward, up }; if (this.audioContext?.listener) { const listener = this.audioContext.listener; if (listener.forwardX) { listener.forwardX.value = forward[0]; listener.forwardY.value = forward[1]; listener.forwardZ.value = forward[2]; listener.upX.value = up[0]; listener.upY.value = up[1]; listener.upZ.value = up[2]; } else if (listener.setOrientation) { listener.setOrientation(...forward, ...up); } } } /** * הגדרת אודיו מרחבי לtrack */ private configureSpatialAudio(panner: any, settings: SpatialAudioSettings): void { // Position if (panner.positionX) { panner.positionX.value = settings.position.x; panner.positionY.value = settings.position.y; panner.positionZ.value = settings.position.z; } else if (panner.setPosition) { panner.setPosition(settings.position.x, settings.position.y, settings.position.z); } // Orientation (if specified) if (settings.orientation && panner.orientationX) { panner.orientationX.value = settings.orientation.x; panner.orientationY.value = settings.orientation.y; panner.orientationZ.value = settings.orientation.z; } // Distance model panner.distanceModel = 'inverse'; panner.maxDistance = settings.maxDistance; panner.rolloffFactor = settings.rolloffFactor; // Cone settings (directional audio) if (settings.coneInnerAngle !== undefined) { panner.coneInnerAngle = settings.coneInnerAngle; panner.coneOuterAngle = settings.coneOuterAngle || 360; panner.coneOuterGain = settings.coneOuterGain || 0; } } /** * הגדרת פילטר אודיו */ private configureFilter(filter: any, settings: FilterSettings): void { filter.type = settings.type; filter.frequency.value = settings.frequency; filter.Q.value = settings.q; if (settings.gain !== undefined) { filter.gain.value = settings.gain; } } /** * קבלת מכפיל עוצמה לפי סוג */ private getVolumeMultiplier(type: string): number { switch (type) { case 'music': return this.context.musicVolume; case 'sound': return this.context.soundVolume; case 'voice': return this.context.voiceVolume; case 'ambient': return this.context.ambientVolume; default: return 1.0; } } /** * עדכון עוצמת קול לכל הtracks הפעילים */ private updateActiveTrackVolumes(): void { this.activeAudio.forEach((audioData, trackId) => { const track = this.tracks.get(trackId); if (track && audioData.gainNode) { const volumeMultiplier = this.getVolumeMultiplier(track.type); const finalVolume = track.volume * volumeMultiplier * this.context.masterVolume; audioData.gainNode.gain.value = finalVolume; } }); } /** * עדכון אנליטיקס */ private updateAnalytics(trackId: string, action: 'play' | 'stop'): void { if (action === 'play') { this.analytics.totalTracksPlayed++; this.analytics.mostPlayedTracks[trackId] = (this.analytics.mostPlayedTracks[trackId] || 0) + 1; } } /** * רישום שגיאה */ private logError(trackId: string, error: Error): void { const errorRecord = { timestamp: Date.now(), trackId, error: error.message, userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Unknown' }; this.analytics.audioErrors.push(errorRecord); // Keep only last 50 errors if (this.analytics.audioErrors.length > 50) { this.analytics.audioErrors.shift(); } console.error(`Audio Error [${trackId}]:`, error); } /** * קבלת נתוני ביצועים */ getPerformanceMetrics(): AudioAnalytics['performanceMetrics'] { const now = performance.now(); return { audioLatency: this.audioContext ? this.audioContext.baseLatency || 0 : 0, bufferUnderruns: 0, // Would need more complex tracking memoryUsage: this.activeAudio.size, }; } /** * ניקוי משאבים */ dispose(): void { // Stop all active tracks this.activeAudio.forEach((_, trackId) => { this.stopTrack(trackId); }); // Close audio context if (this.audioContext) { this.audioContext.close(); } // Clear data this.tracks.clear(); this.activeAudio.clear(); console.log('🔊 Enhanced Audio System disposed'); } /** * קבלת אנליטיקס */ getAnalytics(): AudioAnalytics { return { ...this.analytics, performanceMetrics: this.getPerformanceMetrics() }; } /** * קבלת מצב מערכת האודיו */ getSystemStatus(): { isInitialized: boolean; activeTrackCount: number; context: AudioContext; capabilities: { webAudioAPI: boolean; spatialAudio: boolean; reverb: boolean; filters: boolean; }; } { return { isInitialized: !!this.audioContext, activeTrackCount: this.activeAudio.size, context: this.context, capabilities: { webAudioAPI: !!this.audioContext, spatialAudio: !!(this.audioContext?.createPanner), reverb: !!this.convolver, filters: !!(this.audioContext?.createBiquadFilter), } }; } } export default EnhancedAudioSystem;