Chapter 33

Performance Optimization

Learning Objectives

  • Learn optimization techniques
  • Understand Web Workers
  • Optimize memory usage
  • Improve user experience

Web Workers

Web Workers run JavaScript in background threads, keeping the UI responsive.

// Main thread (app.js)
const worker = new Worker('worker.js');

// Send data to worker
worker.postMessage({
    type: 'separate',
    audioData: audioBuffer.getChannelData(0)
});

// Receive results
worker.onmessage = (event) => {
    const { type, stems } = event.data;
    if (type === 'separation-complete') {
        displayStems(stems);
    }
};

// Worker thread (worker.js)
self.onmessage = async (event) => {
    const { type, audioData } = event.data;
    
    if (type === 'separate') {
        // Heavy processing here
        const stems = await separateStems(audioData);
        
        // Send back to main thread
        self.postMessage({
            type: 'separation-complete',
            stems: stems
        });
    }
};

Chunked Processing

// Process large audio in chunks
async function processInChunks(audioBuffer, chunkDuration = 5) {
    const chunkSize = chunkDuration * audioBuffer.sampleRate;
    const numChunks = Math.ceil(audioBuffer.length / chunkSize);
    
    const results = [];
    
    for (let i = 0; i < numChunks; i++) {
        const start = i * chunkSize;
        const end = Math.min(start + chunkSize, audioBuffer.length);
        
        // Extract chunk
        const chunk = audioBuffer.slice(start, end);
        
        // Process chunk
        const result = await processChunk(chunk);
        results.push(result);
        
        // Update progress
        updateProgress((i + 1) / numChunks * 100);
        
        // Yield to UI thread
        await new Promise(resolve => setTimeout(resolve, 0));
    }
    
    // Combine results
    return combineResults(results);
}

// Benefits:
// - Lower memory usage
// - Progress feedback
// - Can cancel mid-way
// - UI stays responsive

Memory Management

// Best practices for memory

// 1. Release references when done
function processTrack(track) {
    const audioBuffer = loadTrack(track);
    const stems = separate(audioBuffer);
    
    // Done with original
    // audioBuffer = null; // Let GC collect it
    
    return stems;
}

// 2. Reuse buffers
class BufferPool {
    constructor() {
        this.pool = [];
    }
    
    getBuffer(size) {
        if (this.pool.length > 0) {
            return this.pool.pop();
        }
        return new Float32Array(size);
    }
    
    releaseBuffer(buffer) {
        this.pool.push(buffer);
    }
}

// 3. Use typed arrays
const audioData = new Float32Array(44100 * 5); // 5 seconds
// More efficient than regular arrays

// 4. Avoid copying
// Bad: creates copy
const copy = audioData.slice(0);

// Good: view into same data
const view = audioData.subarray(0, 1000);

Caching

// Cache analysis results
class AnalysisCache {
    constructor() {
        this.cache = new Map();
    }
    
    async analyze(audioBuffer) {
        // Generate hash of audio data
        const hash = await this.hashAudio(audioBuffer);
        
        // Check cache
        if (this.cache.has(hash)) {
            return this.cache.get(hash);
        }
        
        // Analyze
        const result = await analysisEngine.analyze(audioBuffer);
        
        // Store in cache
        this.cache.set(hash, result);
        
        return result;
    }
    
    async hashAudio(audioBuffer) {
        const data = audioBuffer.getChannelData(0);
        const buffer = new TextEncoder().encode(data.toString());
        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }
}

// Service Worker caching for offline use
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request).then((response) => {
            return response || fetch(event.request);
        })
    );
});

🎵 MixMind Connection

How MixMind Optimizes Performance

// MixMind's optimization strategies

// 1. Web Worker for AI processing
// separation-worker.js
self.onmessage = async (event) => {
    const { audioData, chunkIndex } = event.data;
    
    try {
        // Run model in worker
        const stems = await separationEngine.separate(audioData);
        
        self.postMessage({
            type: 'chunk-complete',
            chunkIndex: chunkIndex,
            stems: stems
        });
    } catch (error) {
        self.postMessage({
            type: 'error',
            error: error.message
        });
    }
};

// 2. Chunked processing with progress
async function separateWithProgress(audioBuffer) {
    const chunks = splitIntoChunks(audioBuffer, 5);
    const results = [];
    
    for (let i = 0; i < chunks.length; i++) {
        const chunk = chunks[i];
        
        // Process in worker
        const result = await processInWorker(chunk, i);
        results.push(result);
        
        // Update UI
        const progress = (i + 1) / chunks.length * 100;
        updateProgressUI(progress);
        
        // Check for cancellation
        if (cancelled) {
            throw new Error('Cancelled by user');
        }
    }
    
    return mergeResults(results);
}

// 3. Lazy loading
class LazyModel {
    constructor() {
        this.model = null;
    }
    
    async load() {
        if (!this.model) {
            // Only load when needed
            this.model = await ort.InferenceSession.create('model.onnx');
        }
        return this.model;
    }
}

// 4. Streaming results
async function* streamStems(audioBuffer) {
    const chunks = splitIntoChunks(audioBuffer, 5);
    
    for (const chunk of chunks) {
        const stems = await separate(chunk);
        yield stems; // Yield each chunk as it completes
    }
}

// Usage
for await (const stems of streamStems(audio)) {
    displayStem(stems); // Show immediately
}

Exercise 33.1: Practice

Goal: Apply what you've learned in this chapter

Try the concepts from this chapter in your browser's console or a simple HTML file. Experiment with the code examples and modify them to see what happens.

📝 Quick Reference:

Review the key concepts from this chapter and make sure you understand each one before moving to the next chapter.