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.