Chapter 32

Module Deep Dive

Learning Objectives

  • Explore each engine in detail
  • Understand their APIs
  • See how they interact
  • Learn implementation details

Audio Engine

// audio-engine.js - Core audio operations
class AudioEngine {
    constructor() {
        this.context = new AudioContext();
    }
    
    // Load audio file
    async loadFile(file) {
        const arrayBuffer = await file.arrayBuffer();
        const audioBuffer = await this.context.decodeAudioData(arrayBuffer);
        return audioBuffer;
    }
    
    // Play audio
    play(audioBuffer, options = {}) {
        const source = this.context.createBufferSource();
        source.buffer = audioBuffer;
        
        if (options.gain !== undefined) {
            const gainNode = this.context.createGain();
            gainNode.gain.value = options.gain;
            source.connect(gainNode);
            gainNode.connect(this.context.destination);
        } else {
            source.connect(this.context.destination);
        }
        
        source.start(options.startTime || 0);
        return source;
    }
    
    // Create preview (first 30 seconds)
    createPreview(audioBuffer, duration = 30) {
        const length = Math.min(
            audioBuffer.length,
            duration * audioBuffer.sampleRate
        );
        
        const preview = this.context.createBuffer(
            audioBuffer.numberOfChannels,
            length,
            audioBuffer.sampleRate
        );
        
        for (let ch = 0; ch < audioBuffer.numberOfChannels; ch++) {
            const source = audioBuffer.getChannelData(ch);
            const dest = preview.getChannelData(ch);
            for (let i = 0; i < length; i++) {
                dest[i] = source[i];
            }
        }
        
        return preview;
    }
}

Analysis Engine

// analysis-engine.js - Analyze tracks
class AnalysisEngine {
    constructor() {
        this.beatDetector = new BeatDetector();
        this.bpmDetector = new BPMDetector();
        this.keyDetector = new KeyDetector();
        this.phraseDetector = new PhraseDetector();
        this.energyAnalyzer = new EnergyAnalyzer();
    }
    
    async analyze(audioBuffer) {
        // Run all analyses in parallel
        const [beats, bpm, key, phrases, energy] = await Promise.all([
            this.beatDetector.analyze(audioBuffer),
            this.bpmDetector.analyze(audioBuffer),
            this.keyDetector.analyze(audioBuffer),
            this.phraseDetector.analyze(audioBuffer, beats),
            this.energyAnalyzer.analyze(audioBuffer)
        ]);
        
        return {
            beats: beats,
            bpm: bpm,
            key: key,
            phrases: phrases,
            energy: energy,
            duration: audioBuffer.duration
        };
    }
    
    // Check compatibility between tracks
    checkCompatibility(analysis1, analysis2) {
        const bpmDiff = Math.abs(analysis1.bpm - analysis2.bpm);
        const keysCompatible = this.keyDetector.areKeysCompatible(
            analysis1.key,
            analysis2.key
        );
        
        return {
            bpmCompatible: bpmDiff < 10,
            keysCompatible: keysCompatible,
            overall: bpmDiff < 10 && keysCompatible,
            suggestions: this.generateSuggestions(analysis1, analysis2)
        };
    }
}

Separation Engine

// separation-engine.js - AI stem separation
class SeparationEngine {
    constructor() {
        this.pipeline = new SeparationPipeline();
    }
    
    async separate(audioBuffer, onProgress) {
        // Set up progress callback
        this.pipeline.onProgress = onProgress;
        
        // Run separation
        const result = await this.pipeline.separate(audioBuffer);
        
        return {
            vocals: result.stems.vocals,
            drums: result.stems.drums,
            bass: result.stems.bass,
            other: result.stems.other,
            analysis: result.analysis
        };
    }
    
    // Quick preview (lower quality, faster)
    async quickPreview(audioBuffer) {
        // Process only first 30 seconds
        const preview = audioBuffer.slice(0, 30 * audioBuffer.sampleRate);
        return await this.separate(preview);
    }
}

Transition Engine

// transition-engine.js - Create mixes
class TransitionEngine {
    constructor() {
        this.audioEngine = new AudioEngine();
    }
    
    async createMix(config) {
        const { tracks, transitions } = config;
        
        // Start with first track
        let mix = tracks[0].buffer;
        
        // Apply each transition
        for (let i = 0; i < transitions.length; i++) {
            const transition = transitions[i];
            const nextTrack = tracks[i + 1];
            
            // Create transition
            mix = await this.createTransition(
                mix,
                nextTrack.buffer,
                transition.settings
            );
        }
        
        return mix;
    }
    
    async createTransition(buffer1, buffer2, settings) {
        const { type, duration, syncToBeats } = settings;
        
        switch(type) {
            case 'crossfade':
                return this.crossfade(buffer1, buffer2, duration);
            case 'eq_blend':
                return this.eqBlend(buffer1, buffer2, duration);
            case 'filter_sweep':
                return this.filterSweep(buffer1, buffer2, duration);
            default:
                return this.crossfade(buffer1, buffer2, duration);
        }
    }
}

Export Engine

// export-engine.js - Render final mix
class ExportEngine {
    async render(audioBuffer, format = 'wav') {
        // Convert AudioBuffer to WAV
        const wav = this.audioBufferToWav(audioBuffer);
        
        // Create blob
        const blob = new Blob([wav], { type: 'audio/wav' });
        
        return blob;
    }
    
    audioBufferToWav(buffer) {
        const numChannels = buffer.numberOfChannels;
        const sampleRate = buffer.sampleRate;
        const format = 1; // PCM
        const bitDepth = 16;
        
        // Calculate file size
        const bytesPerSample = bitDepth / 8;
        const dataSize = buffer.length * numChannels * bytesPerSample;
        const headerSize = 44;
        const totalSize = headerSize + dataSize;
        
        // Create ArrayBuffer
        const arrayBuffer = new ArrayBuffer(totalSize);
        const view = new DataView(arrayBuffer);
        
        // WAV header
        this.writeString(view, 0, 'RIFF');
        view.setUint32(4, totalSize - 8, true);
        this.writeString(view, 8, 'WAVE');
        this.writeString(view, 12, 'fmt ');
        view.setUint32(16, 16, true);
        view.setUint16(20, format, true);
        view.setUint16(22, numChannels, true);
        view.setUint32(24, sampleRate, true);
        view.setUint32(28, sampleRate * numChannels * bytesPerSample, true);
        view.setUint16(32, numChannels * bytesPerSample, true);
        view.setUint16(34, bitDepth, true);
        this.writeString(view, 36, 'data');
        view.setUint32(40, dataSize, true);
        
        // Write audio data
        this.interleaveAndWrite(view, buffer, 44);
        
        return arrayBuffer;
    }
    
    // Trigger download
    download(blob, filename = 'mix.wav') {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
    }
}

🎵 MixMind Connection

How MixMind Ties It All Together

// app.js - Main application
class MixMindApp {
    constructor() {
        this.audioEngine = new AudioEngine();
        this.analysisEngine = new AnalysisEngine();
        this.separationEngine = new SeparationEngine();
        this.transitionEngine = new TransitionEngine();
        this.exportEngine = new ExportEngine();
        
        this.tracks = [];
        this.analyses = [];
        this.stems = [];
    }
    
    async addTrack(file) {
        // Load
        const audioBuffer = await this.audioEngine.loadFile(file);
        this.tracks.push(audioBuffer);
        
        // Analyze
        const analysis = await this.analysisEngine.analyze(audioBuffer);
        this.analyses.push(analysis);
        
        // Update UI
        this.updateTrackList();
    }
    
    async separateTrack(index) {
        const audioBuffer = this.tracks[index];
        
        const stems = await this.separationEngine.separate(
            audioBuffer,
            (progress) => this.updateProgress(progress)
        );
        
        this.stems[index] = stems;
        this.updateUI();
    }
    
    async createMix() {
        const config = {
            tracks: this.tracks.map((buffer, i) => ({
                buffer: buffer,
                analysis: this.analyses[i],
                stems: this.stems[i]
            })),
            transitions: this.getTransitionConfig()
        };
        
        const mix = await this.transitionEngine.createMix(config);
        return mix;
    }
    
    async exportMix() {
        const mix = await this.createMix();
        const blob = await this.exportEngine.render(mix);
        this.exportEngine.download(blob, 'my-mix.wav');
    }
}

// Initialize app
const app = new MixMindApp();

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