Chapter 34

User Experience

Learning Objectives

  • Learn UX principles
  • Design good feedback
  • Handle errors gracefully
  • Create intuitive interfaces

Progress Feedback

// Show progress for long operations
function updateProgress(percent, message) {
    const progressBar = document.getElementById('progressBar');
    const progressText = document.getElementById('progressText');
    
    progressBar.style.width = percent + '%';
    progressText.textContent = message || `${Math.round(percent)}%`;
}

// Detailed progress messages
const progressMessages = {
    0: 'Starting...',
    10: 'Loading audio...',
    20: 'Analyzing audio...',
    30: 'Loading AI model...',
    40: 'Separating stems...',
    70: 'Processing chunk 1/3...',
    80: 'Processing chunk 2/3...',
    90: 'Processing chunk 3/3...',
    95: 'Finalizing...',
    100: 'Complete!'
};

function updateProgress(percent) {
    const message = progressMessages[percent] || 'Processing...';
    updateProgressUI(percent, message);
}

Error Handling

// Graceful error handling
async function safeSeparation(file) {
    try {
        // Check file type
        if (!file.type.startsWith('audio/')) {
            throw new Error('Please upload an audio file (MP3, WAV, etc.)');
        }
        
        // Check file size
        if (file.size > 50 * 1024 * 1024) { // 50 MB
            throw new Error('File is too large. Please upload files under 50 MB.');
        }
        
        // Load audio
        const audioBuffer = await audioEngine.loadFile(file);
        
        // Check duration
        if (audioBuffer.duration > 600) { // 10 minutes
            throw new Error('Audio is too long. Please upload tracks under 10 minutes.');
        }
        
        // Separate
        const stems = await separationEngine.separate(audioBuffer);
        
        return { success: true, stems };
        
    } catch (error) {
        // User-friendly error messages
        let message = 'An error occurred';
        
        if (error.message.includes('decode')) {
            message = 'Could not read audio file. Please try a different format.';
        } else if (error.message.includes('ONNX')) {
            message = 'AI model failed to load. Please check your internet connection.';
        } else if (error.message.includes('memory')) {
            message = 'Not enough memory. Please try a shorter track.';
        }
        
        showError(message);
        return { success: false, error: message };
    }
}

// Show error to user
function showError(message) {
    const errorDiv = document.createElement('div');
    errorDiv.className = 'error-message';
    errorDiv.textContent = message;
    document.body.appendChild(errorDiv);
    
    setTimeout(() => errorDiv.remove(), 5000);
}

Responsive Design

/* CSS for responsive layout */
.container {
    display: grid;
    grid-template-columns: 250px 1fr;
    gap: 20px;
    padding: 20px;
}

/* Mobile: stack vertically */
@media (max-width: 768px) {
    .container {
        grid-template-columns: 1fr;
    }
    
    .sidebar {
        position: fixed;
        top: 0;
        left: -100%;
        width: 80%;
        height: 100vh;
        transition: left 0.3s;
    }
    
    .sidebar.open {
        left: 0;
    }
}

/* Touch-friendly buttons */
button {
    min-height: 44px; /* iOS minimum touch target */
    min-width: 44px;
    font-size: 16px; /* Prevents zoom on iOS */
}

/* Reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

Accessibility



Separates track into vocals, drums, bass, and other

function announce(message) { document.getElementById('announcer').textContent = message; } // Progress updates announce('Separation 50% complete'); // Color contrast button { background: #00d4ff; color: #000; /* Contrast ratio > 4.5:1 for WCAG AA */ } /* Focus indicators */ button:focus { outline: 2px solid #00d4ff; outline-offset: 2px; }

🎵 MixMind Connection

How MixMind Designs for Users

// MixMind's UX approach

// 1. Immediate feedback
class UploadHandler {
    handleDrop(file) {
        // Show loading immediately
        this.showLoading();
        
        // Validate
        if (!this.validate(file)) {
            this.showError('Invalid file');
            return;
        }
        
        // Process
        this.processFile(file)
            .then(() => this.showSuccess())
            .catch(err => this.showError(err.message));
    }
}

// 2. Progress everywhere
class ProgressTracker {
    constructor() {
        this.progressBars = new Map();
    }
    
    createProgress(id, label) {
        const bar = document.createElement('div');
        bar.className = 'progress-bar';
        bar.innerHTML = `
            ${label}
            
0% `; this.progressBars.set(id, bar); return bar; } update(id, percent, message) { const bar = this.progressBars.get(id); bar.querySelector('.fill').style.width = percent + '%'; bar.querySelector('.percent').textContent = Math.round(percent) + '%'; if (message) { bar.querySelector('.label').textContent = message; } } } // 3. Undo/Redo class UndoManager { constructor() { this.history = []; this.currentIndex = -1; } push(action) { // Remove any redo history this.history = this.history.slice(0, this.currentIndex + 1); // Add action this.history.push(action); this.currentIndex++; } undo() { if (this.currentIndex >= 0) { const action = this.history[this.currentIndex]; action.undo(); this.currentIndex--; } } redo() { if (this.currentIndex < this.history.length - 1) { this.currentIndex++; const action = this.history[this.currentIndex]; action.execute(); } } } // 4. Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey) { switch(e.key) { case 'z': e.preventDefault(); undoManager.undo(); break; case 'y': e.preventDefault(); undoManager.redo(); break; case 's': e.preventDefault(); exportMix(); break; } } });

Exercise 34.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.