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.