Chapter 24

Transition Techniques

Learning Objectives

  • Understand transition types
  • Learn crossfade techniques
  • Implement EQ transitions
  • See how MixMind creates transitions

Transition Types

DJs use various transition techniques:

TypeDescriptionUse Case
CrossfadeGradually blend two tracksSmooth, seamless mixes
EQ BlendSwap frequency rangesEnergy changes
CutHard switchHigh energy, drops
Filter SweepFilter out/in during transitionDramatic effects
Echo OutAdd echo to outgoing trackSmooth exits

Crossfade Implementation

// Linear crossfade
function linearCrossfade(buffer1, buffer2, duration) {
    const fadeSamples = Math.floor(duration * buffer1.sampleRate);
    const output = audioContext.createBuffer(2, buffer1.length + buffer2.length - fadeSamples, buffer1.sampleRate);
    
    for (let channel = 0; channel < 2; channel++) {
        const data1 = buffer1.getChannelData(channel);
        const data2 = buffer2.getChannelData(channel);
        const out = output.getChannelData(channel);
        
        let outIndex = 0;
        
        // Track 1 only
        for (let i = 0; i < buffer1.length - fadeSamples; i++) {
            out[outIndex++] = data1[i];
        }
        
        // Crossfade region
        for (let i = 0; i < fadeSamples; i++) {
            const fadeOut = 1 - (i / fadeSamples);
            const fadeIn = i / fadeSamples;
            out[outIndex++] = data1[buffer1.length - fadeSamples + i] * fadeOut + data2[i] * fadeIn;
        }
        
        // Track 2 only
        for (let i = fadeSamples; i < buffer2.length; i++) {
            out[outIndex++] = data2[i];
        }
    }
    
    return output;
}

// Equal-power crossfade (sounds more natural)
function equalPowerCrossfade(buffer1, buffer2, duration) {
    // Use sine/cosine curves instead of linear
    // fadeOut = cos(i / fadeSamples * π/2)
    // fadeIn = sin(i / fadeSamples * π/2)
    // ... similar implementation
}

EQ Transition

// EQ blend - fade bass out, then in
async function eqTransition(buffer1, buffer2, duration) {
    const fadeSamples = Math.floor(duration * buffer1.sampleRate);
    
    // Create filtered versions
    const filtered1 = await applyFilterFade(buffer1, 'lowpass', duration, true);
    const filtered2 = await applyFilterFade(buffer2, 'highpass', duration, false);
    
    // Mix them
    return mixBuffers(filtered1, filtered2);
}

// Apply filter with automation
async function applyFilterFade(buffer, type, duration, fadeOut) {
    const offlineContext = new OfflineAudioContext(
        buffer.numberOfChannels,
        buffer.length,
        buffer.sampleRate
    );
    
    const source = offlineContext.createBufferSource();
    source.buffer = buffer;
    
    const filter = offlineContext.createBiquadFilter();
    filter.type = type;
    
    // Automate filter
    const startTime = buffer.duration - duration;
    if (fadeOut) {
        filter.frequency.setValueAtTime(20000, startTime);
        filter.frequency.exponentialRampToValueAtTime(200, buffer.duration);
    } else {
        filter.frequency.setValueAtTime(20, 0);
        filter.frequency.exponentialRampToValueAtTime(20000, duration);
    }
    
    source.connect(filter);
    filter.connect(offlineContext.destination);
    source.start();
    
    return await offlineContext.startRendering();
}

Beat-Synced Transitions

// Align transitions to beat grid
function beatSyncedTransition(buffer1, buffer2, beats1, beats2, mixPoint) {
    // Find nearest beats to mix point
    const beat1 = findNearestBeat(beats1, mixPoint);
    const beat2 = beats2[0]; // Start of track 2
    
    // Calculate offset to align beats
    const offset = beat1.time - mixPoint;
    
    // Adjust track 2 start time
    const adjustedStart = mixPoint + offset;
    
    // Create transition with aligned beats
    return createTransition(buffer1, buffer2, adjustedStart);
}

// Phrase-aligned mixing
function phraseAlignedMix(track1, track2, phrases1) {
    // Find phrase boundary in track 1
    const phraseEnd = findNextPhraseEnd(phrases1, currentTime);
    
    // Start track 2 at this phrase boundary
    const mixStartTime = phraseEnd.time - 32; // 32 seconds before phrase end
    
    return {
        startTime: mixStartTime,
        duration: 32,
        type: 'crossfade'
    };
}

🎵 MixMind Connection

How MixMind Creates Transitions

// MixMind's transition engine
class TransitionEngine {
    async createTransition(track1, track2, settings) {
        const { type, duration, syncToBeats } = settings;
        
        // Beat sync if enabled
        if (syncToBeats) {
            const alignment = this.alignBeats(track1.beats, track2.beats);
            // Adjust timing...
        }
        
        switch(type) {
            case 'crossfade':
                return this.crossfade(track1.buffer, track2.buffer, duration);
            case 'eq_blend':
                return this.eqBlend(track1.buffer, track2.buffer, duration);
            case 'filter_sweep':
                return this.filterSweep(track1.buffer, track2.buffer, duration);
            case 'cut':
                return this.hardCut(track1.buffer, track2.buffer);
            case 'echo_out':
                return this.echoOut(track1.buffer, track2.buffer, duration);
        }
    }
    
    // Auto-select transition based on tracks
    autoSelectTransition(track1, track2) {
        const energy1 = this.getEnergy(track1);
        const energy2 = this.getEnergy(track2);
        
        // Similar energy = crossfade
        // Big energy change = EQ blend
        // High energy = cut
        
        if (Math.abs(energy1 - energy2) < 0.2) {
            return { type: 'crossfade', duration: 16 };
        } else if (energy2 > energy1) {
            return { type: 'eq_blend', duration: 16 };
        } else {
            return { type: 'cut', duration: 0 };
        }
    }
}

// Rendering the final mix
async function renderMix(tracks, transitions) {
    let output = tracks[0].buffer;
    
    for (let i = 1; i < tracks.length; i++) {
        const transition = transitions[i - 1];
        output = await transitionEngine.createTransition(
            { buffer: output, beats: tracks[i-1].beats },
            { buffer: tracks[i].buffer, beats: tracks[i].beats },
            transition.settings
        );
    }
    
    return output;
}

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