Skip to content

Crossfade Tuning

Crossfade quality depends on three variables: backend choice, duration, and the lead time between the trackEndingSoon signal and when the fade starts. This page covers the trade-offs and how to tune each.

Backend choice determines accuracy

The audio-element backend uses a requestAnimationFrame loop (~50 fps). One frame is ~20 ms. Gain steps are applied every frame, so transitions are not sample-accurate, and slight audible stepping may occur on short fades.

The webaudio backend uses linearRampToValueAtTime on a GainNode. This schedules the ramp in the browser’s audio render thread, independent of the animation frame rate. Gain transitions are sample-accurate.

For production DJ-style crossfades: use webaudio.

For casual “smooth transition” crossfades at 5 s+: either backend works.

TypeScript
player.setup({ backend: 'webaudio' });

Duration guide

ContextRecommended duration
Radio / background music3–5 s
DJ-style mix8–16 s
Album transition1–3 s
”Instant” (hard cut with preload)0 s

Set duration in crossfadeDefaults:

TypeScript
player.setup({
crossfadeDefaults: {
duration: 5,
curve: 'equal-power',
},
});

Override per-call:

TypeScript
await player.crossfadeTo(nextTrack, {
duration: 12,
});
// curve is not honored per-call — a manual crossfadeTo() always ramps linearly.
// Set crossfadeDefaults.curve in setup() to shape the automatic AutoAdvance fade.

Lead time: threshold vs duration

trackEndingSoonThreshold controls when AutoAdvancePlugin initiates the crossfade. It must be greater than or equal to crossfadeDuration:

TypeScript
player.setup({
trackEndingSoonThreshold: 10, // fire 10s before end
crossfadeDefaults: { duration: 8 }, // 8s fade
});

player.addPlugin(AutoAdvancePlugin, {
crossfade: true,
crossfadeDuration: 8,
});

If threshold < duration, the fade will start but will be cut off when the outgoing track reaches ended. The incoming track will become primary mid-fade.

Equal-power vs linear

equal-power is almost always correct for music:

Code
equal-power gain sum = cos²(t×π/2) + sin²(t×π/2) = 1.0 (constant)
linear gain sum at t=0.5 = 0.5 + 0.5 = 1.0, but dips to ~0.71 perceived

Use linear only when you deliberately want a brief dip (unusual).

Mid-crossfade gain inspection

During an active crossfade, inspect the secondary gain via the backend:

TypeScript
player.on('crossfadeStart', () => {
const interval = setInterval(() => {
if (!player.isTransitioning()) {
clearInterval(interval);
return;
}
const gain = player.backend().secondaryGain();
console.log('Secondary gain:', gain.toFixed(3));
}, 100);
});

See also