advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
610 lines (535 loc) • 17.9 kB
text/typescript
/**
* 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;