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:
| Type | Description | Use Case |
|---|---|---|
| Crossfade | Gradually blend two tracks | Smooth, seamless mixes |
| EQ Blend | Swap frequency ranges | Energy changes |
| Cut | Hard switch | High energy, drops |
| Filter Sweep | Filter out/in during transition | Dramatic effects |
| Echo Out | Add echo to outgoing track | Smooth 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.