// Performance Optimization System for Web Audio Visualizer // Optimized for Hugging Face Spaces free tier deployment interface PerformanceMetrics { fps: number frameTime: number memoryUsage: number cpuUsage: number gpuMemory: number particleCount: number lastUpdate: number } interface OptimizationSettings { targetFPS: number minFPS: number maxParticles: number minParticles: number adaptiveQualityStep: number memoryThreshold: number cpuThreshold: number } interface CacheEntry { data: any timestamp: number accessCount: number size: number } export class PerformanceOptimizer { private metrics: PerformanceMetrics private settings: OptimizationSettings private frameHistory: number[] = [] private lastFrameTime: number = 0 private adaptiveLevel: number = 1.0 // 1.0 = max quality, 0.1 = min quality // Caching system private shaderCache: Map = new Map() private geometryCache: Map = new Map() private materialCache: Map = new Map() private calculationCache: Map = new Map() // Object pools for reuse private bufferPool: Map = new Map() // Performance monitoring private performanceObserver: PerformanceObserver | null = null private memoryInfo: any = null constructor(settings: Partial = {}) { this.settings = { targetFPS: 60, minFPS: 30, maxParticles: 15000, minParticles: 3000, adaptiveQualityStep: 0.05, memoryThreshold: 100 * 1024 * 1024, // 100MB cpuThreshold: 80, // 80% CPU usage ...settings } this.metrics = { fps: 60, frameTime: 16.67, memoryUsage: 0, cpuUsage: 0, gpuMemory: 0, particleCount: this.settings.maxParticles, lastUpdate: performance.now() } this.initializePerformanceMonitoring() } private initializePerformanceMonitoring() { // Modern performance monitoring if ('PerformanceObserver' in window) { this.performanceObserver = new PerformanceObserver((_list) => { // Performance entries are processed in updatePerformance method }) try { this.performanceObserver.observe({ entryTypes: ['measure'] }) } catch (e) { console.warn('Performance monitoring not fully supported') } } // Memory monitoring for supported browsers if ('memory' in performance) { this.memoryInfo = (performance as any).memory } } // Adaptive quality management updatePerformance(currentTime: number): void { const deltaTime = currentTime - this.lastFrameTime this.lastFrameTime = currentTime if (deltaTime > 0) { const currentFPS = 1000 / deltaTime this.frameHistory.push(currentFPS) // Keep only last 60 frames for rolling average if (this.frameHistory.length > 60) { this.frameHistory.shift() } // Calculate average FPS const avgFPS = this.frameHistory.reduce((a, b) => a + b, 0) / this.frameHistory.length this.metrics.fps = Math.round(avgFPS) this.metrics.frameTime = deltaTime // Update memory usage if available if (this.memoryInfo) { this.metrics.memoryUsage = this.memoryInfo.usedJSHeapSize this.metrics.gpuMemory = this.memoryInfo.totalJSHeapSize } // Adaptive quality adjustment this.adjustQualityLevel() } } private adjustQualityLevel(): void { const { fps } = this.metrics const { targetFPS, minFPS, adaptiveQualityStep } = this.settings if (fps < minFPS) { // Performance is bad, reduce quality aggressively this.adaptiveLevel = Math.max(0.1, this.adaptiveLevel - adaptiveQualityStep * 2) console.warn(`🚨 Performance low (${fps} FPS), reducing quality to ${(this.adaptiveLevel * 100).toFixed(0)}%`) } else if (fps < targetFPS) { // Performance is below target, reduce quality gradually this.adaptiveLevel = Math.max(0.1, this.adaptiveLevel - adaptiveQualityStep) } else if (fps > targetFPS + 10 && this.adaptiveLevel < 1.0) { // Performance is good, increase quality gradually this.adaptiveLevel = Math.min(1.0, this.adaptiveLevel + adaptiveQualityStep * 0.5) } // Update particle count based on quality level const targetParticles = Math.floor( this.settings.minParticles + (this.settings.maxParticles - this.settings.minParticles) * this.adaptiveLevel ) this.metrics.particleCount = targetParticles } // Smart caching system getCachedCalculation(key: string, calculator: () => any, ttl: number = 1000): any { const cached = this.calculationCache.get(key) const now = performance.now() if (cached && (now - cached.timestamp) < ttl) { cached.accessCount++ return cached.data } // Calculate new value const data = calculator() this.calculationCache.set(key, { data, timestamp: now, accessCount: 1, size: this.estimateSize(data) }) // Clean cache if it gets too large this.cleanCache() return data } // Shader caching for Three.js materials getCachedShader(vertexShader: string, fragmentShader: string, uniforms: any): any { const key = this.hashShader(vertexShader, fragmentShader) if (this.shaderCache.has(key)) { const cached = this.shaderCache.get(key) // Update uniforms on cached shader Object.assign(cached.uniforms, uniforms) return cached } // Create new shader (this would be done in the component) return null // Component will create and cache } cacheShader(vertexShader: string, fragmentShader: string, material: any): void { const key = this.hashShader(vertexShader, fragmentShader) this.shaderCache.set(key, material) } // Geometry and buffer pooling getPooledBuffer(type: string, size: number): Float32Array { const poolKey = `${type}_${size}` if (!this.bufferPool.has(poolKey)) { this.bufferPool.set(poolKey, []) } const pool = this.bufferPool.get(poolKey)! if (pool.length > 0) { const buffer = pool.pop()! buffer.fill(0) // Reset buffer return buffer } // Create new buffer if pool is empty return new Float32Array(size) } returnPooledBuffer(type: string, size: number, buffer: Float32Array): void { const poolKey = `${type}_${size}` if (!this.bufferPool.has(poolKey)) { this.bufferPool.set(poolKey, []) } const pool = this.bufferPool.get(poolKey)! // Limit pool size to prevent memory bloat if (pool.length < 5) { pool.push(buffer) } } // Level of Detail (LOD) calculations getLODSettings(distance: number, importance: number = 1.0): { particleSize: number updateFrequency: number detailLevel: number } { const distanceFactor = Math.max(0.1, Math.min(1.0, 5.0 / distance)) const qualityFactor = this.adaptiveLevel * importance return { particleSize: distanceFactor * qualityFactor, updateFrequency: Math.max(1, Math.floor(10 / (distanceFactor * qualityFactor))), detailLevel: distanceFactor * qualityFactor } } // Frame skipping for heavy operations shouldSkipFrame(_operation: string, frequency: number): boolean { const frameCount = Math.floor(performance.now() / 16.67) // Approximate frame count const skipPattern = Math.max(1, Math.floor(frequency / this.adaptiveLevel)) return frameCount % skipPattern !== 0 } // Memory management private cleanCache(): void { const now = performance.now() const maxCacheAge = 30000 // 30 seconds const maxCacheSize = 50 * 1024 * 1024 // 50MB let totalSize = 0 const entries = Array.from(this.calculationCache.entries()) // Calculate total cache size entries.forEach(([_, entry]) => { totalSize += entry.size }) // Clean old or least used entries if cache is too large if (totalSize > maxCacheSize) { entries .sort((a, b) => { const scoreA = a[1].accessCount / (now - a[1].timestamp) const scoreB = b[1].accessCount / (now - b[1].timestamp) return scoreA - scoreB // Lower score = less valuable }) .slice(0, Math.floor(entries.length * 0.3)) // Remove 30% least valuable .forEach(([key, _]) => { this.calculationCache.delete(key) }) } // Remove old entries entries.forEach(([key, entry]) => { if (now - entry.timestamp > maxCacheAge) { this.calculationCache.delete(key) } }) } // Utility functions private hashShader(vertex: string, fragment: string): string { // Simple hash function for shader caching let hash = 0 const str = vertex + fragment for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i) hash = ((hash << 5) - hash) + char hash = hash & hash // Convert to 32-bit integer } return hash.toString() } private estimateSize(obj: any): number { // Rough size estimation for caching const str = JSON.stringify(obj) return str.length * 2 // Approximate bytes (UTF-16) } // Public getters getMetrics(): PerformanceMetrics { return { ...this.metrics } } getAdaptiveLevel(): number { return this.adaptiveLevel } getOptimalParticleCount(baseCount: number): number { return Math.floor(baseCount * this.adaptiveLevel) } getOptimalUpdateFrequency(baseFrequency: number): number { return Math.max(1, Math.floor(baseFrequency / this.adaptiveLevel)) } // Cleanup dispose(): void { if (this.performanceObserver) { this.performanceObserver.disconnect() } this.shaderCache.clear() this.geometryCache.clear() this.materialCache.clear() this.calculationCache.clear() this.bufferPool.clear() } } // Singleton instance for global use export const performanceOptimizer = new PerformanceOptimizer({ targetFPS: 60, minFPS: 25, // Lower threshold for Hugging Face Spaces maxParticles: 8000, // Reduced for CPU deployment minParticles: 2000, adaptiveQualityStep: 0.08, memoryThreshold: 80 * 1024 * 1024, // 80MB for free tier cpuThreshold: 75 })