Skip to content

CrossFade

The CrossFade component smoothly blends between two audio signals with various crossfade curves. Perfect for DJ-style mixing, transitions, and creative blending effects.

Props

PropTypeDefaultDescription
inputs[ModStreamRef, ModStreamRef]RequiredExactly two audio signals to crossfade
outputModStreamRefRequiredCrossfaded audio output
labelstring'crossfade'Label for the component in metadata
modeCrossFadeMode'equal-power'Crossfade curve type
childrenfunction-Render prop function receiving control props

Crossfade Modes

  • 'linear' - Simple linear crossfade (can have volume dip in middle)
  • 'equal-power' - Constant power using cosine curves (recommended)
  • 'equal-gain' - Equal gain crossfade
  • 'exponential' - Exponential curve for smoother transitions
  • 'dj-cut' - DJ-style sharp cut in the middle
  • 'smooth-step' - S-curve using smoothstep function

Render Props

When using the children render prop, the following controls are provided:

PropertyTypeDescription
mixnumberMix position (0-1)
setMix(value: number) => voidUpdate the mix position
modeCrossFadeModeCurrent crossfade mode
setMode(mode: CrossFadeMode) => voidUpdate the crossfade mode
isActivebooleanWhether the crossfade is active

Usage

Basic Usage

tsx
import { ToneGenerator, CrossFade, Monitor } from '@mode-7/mod';
import { useRef } from 'react';

function App() {
  const tone1Out = useRef(null);
  const tone2Out = useRef(null);
  const fadeOut = useRef(null);

  return (
    <>
      <ToneGenerator output={tone1Out} frequency={220} />
      <ToneGenerator output={tone2Out} frequency={440} />

      <CrossFade
        inputs={[tone1Out, tone2Out]}
        output={fadeOut}
      />

      <Monitor input={fadeOut} />
    </>
  );
}

DJ Mixer Style

tsx
import { CrossFade } from '@mode-7/mod';
import { useRef } from 'react';

function App() {
  const deck1Out = useRef(null);
  const deck2Out = useRef(null);
  const fadeOut = useRef(null);

  return (
    <CrossFade inputs={[deck1Out, deck2Out]} output={fadeOut}>
      {({ mix, setMix, mode, setMode }) => (
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <span>A</span>
            <input
              type="range"
              min="0"
              max="1"
              step="0.01"
              value={mix}
              onChange={(e) => setMix(Number(e.target.value))}
              style={{ width: '300px' }}
            />
            <span>B</span>
          </div>

          <div style={{ textAlign: 'center', marginTop: '10px' }}>
            {mix === 0 && 'Deck A Only'}
            {mix === 1 && 'Deck B Only'}
            {mix > 0 && mix < 1 && `Mix: ${(mix * 100).toFixed(0)}% B`}
          </div>

          <div style={{ marginTop: '20px' }}>
            <label>Crossfade Curve:</label>
            <select value={mode} onChange={(e) => setMode(e.target.value as any)}>
              <option value="equal-power">Equal Power (Smooth)</option>
              <option value="dj-cut">DJ Cut (Sharp)</option>
              <option value="linear">Linear</option>
              <option value="smooth-step">Smooth Step</option>
            </select>
          </div>
        </div>
      )}
    </CrossFade>
  );
}

Automated Crossfade

tsx
import { MP3Deck, CrossFade, LFO, Monitor } from '@mode-7/mod';
import { useRef, useState } from 'react';

function App() {
  const [autoFade, setAutoFade] = useState(false);
  const deck1Out = useRef(null);
  const deck2Out = useRef(null);
  const lfoOut = useRef(null);
  const fadeOut = useRef(null);

  return (
    <>
      <MP3Deck output={deck1Out}>
        {({ setSrc }) => (
          <button onClick={() => setSrc('/track1.mp3')}>
            Load Track 1
          </button>
        )}
      </MP3Deck>

      <MP3Deck output={deck2Out}>
        {({ setSrc }) => (
          <button onClick={() => setSrc('/track2.mp3')}>
            Load Track 2
          </button>
        )}
      </MP3Deck>

      {autoFade && (
        <LFO output={lfoOut}>
          {({ setFrequency, setWaveform, setAmplitude }) => {
            React.useEffect(() => {
              setFrequency(0.1);  // 10 second cycle
              setWaveform('triangle');
              setAmplitude(0.5);
            }, []);
            return null;
          }}
        </LFO>
      )}

      <CrossFade inputs={[deck1Out, deck2Out]} output={fadeOut}>
        {({ mix, setMix }) => (
          <div>
            <button onClick={() => setAutoFade(!autoFade)}>
              {autoFade ? 'Manual Mode' : 'Auto Fade Mode'}
            </button>

            {!autoFade && (
              <div>
                <input
                  type="range"
                  min="0"
                  max="1"
                  step="0.01"
                  value={mix}
                  onChange={(e) => setMix(Number(e.target.value))}
                />
              </div>
            )}
          </div>
        )}
      </CrossFade>

      <Monitor input={fadeOut} />
    </>
  );
}

A/B Comparison Tool

tsx
import { CrossFade } from '@mode-7/mod';
import { useRef } from 'react';

function App() {
  const processedOut = useRef(null);
  const unprocessedOut = useRef(null);
  const fadeOut = useRef(null);

  return (
    <CrossFade inputs={[unprocessedOut, processedOut]} output={fadeOut} mode="dj-cut">
      {({ mix, setMix }) => (
        <div>
          <h3>A/B Comparison</h3>
          <div style={{ display: 'flex', gap: '10px' }}>
            <button onClick={() => setMix(0)}>
              A (Unprocessed)
            </button>
            <button onClick={() => setMix(0.5)}>
              50/50 Mix
            </button>
            <button onClick={() => setMix(1)}>
              B (Processed)
            </button>
          </div>

          <div style={{ marginTop: '20px' }}>
            <input
              type="range"
              min="0"
              max="1"
              step="0.01"
              value={mix}
              onChange={(e) => setMix(Number(e.target.value))}
              style={{ width: '100%' }}
            />
            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
              <span>Unprocessed</span>
              <span>Processed</span>
            </div>
          </div>
        </div>
      )}
    </CrossFade>
  );
}

Controlled Props

Manage crossfade state externally using controlled props:

tsx
import { CrossFade } from '@mode-7/mod';
import { useRef, useState } from 'react';

function App() {
  const deck1Out = useRef(null);
  const deck2Out = useRef(null);
  const fadeOut = useRef(null);

  const [mix, setMix] = useState(0);
  const [mode, setMode] = useState<'equal-power' | 'dj-cut'>('equal-power');

  return (
    <>
      <CrossFade
        inputs={[deck1Out, deck2Out]}
        output={fadeOut}
        mix={mix}
        onMixChange={setMix}
        mode={mode}
        onModeChange={setMode}
      />

      <div>
        <button onClick={() => setMix(0)}>Deck A</button>
        <button onClick={() => setMix(0.5)}>50/50</button>
        <button onClick={() => setMix(1)}>Deck B</button>
      </div>

      <input
        type="range"
        min="0"
        max="1"
        step="0.01"
        value={mix}
        onChange={(e) => setMix(Number(e.target.value))}
      />

      <select value={mode} onChange={(e) => setMode(e.target.value as any)}>
        <option value="equal-power">Equal Power</option>
        <option value="dj-cut">DJ Cut</option>
      </select>
    </>
  );
}

Imperative Refs

Control crossfade programmatically using refs:

tsx
import { CrossFade, CrossFadeHandle } from '@mode-7/mod';
import { useRef, useEffect } from 'react';

function App() {
  const deck1Out = useRef(null);
  const deck2Out = useRef(null);
  const fadeOut = useRef(null);
  const fadeRef = useRef<CrossFadeHandle>(null);

  useEffect(() => {
    if (fadeRef.current) {
      // Automated DJ-style crossfade
      fadeRef.current.setMode('dj-cut');

      let direction = 1;
      let position = 0;

      const interval = setInterval(() => {
        position += direction * 0.01;
        if (position >= 1 || position <= 0) {
          direction *= -1;
        }
        fadeRef.current?.setMix(position);
      }, 50);

      return () => clearInterval(interval);
    }
  }, []);

  return (
    <CrossFade
      ref={fadeRef}
      inputs={[deck1Out, deck2Out]}
      output={fadeOut}
    />
  );
}

Important Notes

Mix Values

  • 0 = 100% Input A, 0% Input B
  • 0.5 = 50% Input A, 50% Input B
  • 1 = 0% Input A, 100% Input B

Crossfade Modes

Equal Power (Recommended)

  • Maintains constant perceived loudness
  • No volume dip in the middle
  • Most natural sounding
  • Best for music mixing

DJ Cut

  • Sharp transition in the middle (45-55%)
  • Quick crossfade in the center
  • Sounds like a switch
  • Best for DJ-style cuts

Linear

  • Simple linear blend
  • Can have slight volume dip at 50%
  • Good for fades to/from silence

Smooth Step

  • S-curve transition
  • Slow at the edges, fast in middle
  • Smooth, musical fades

Exponential

  • Curved transition
  • More gradual than linear
  • Smooth crossfades

Common Uses

  • DJ Mixing: Blend between two tracks
  • A/B Testing: Compare processed vs unprocessed audio
  • Sound Design: Morph between two sounds
  • Transitions: Smooth scene changes
  • Layer Blending: Mix two versions of the same material

DJ Techniques

  • Use 'equal-power' for smooth blends
  • Use 'dj-cut' for quick switches
  • Keep mix at 0 or 1 when not transitioning
  • Practice transitions with the crossfade curve that feels right
  • Mixer - Mix multiple sources with independent levels
  • MP3Deck - DJ-style audio playback
  • LFO - Automate crossfade position
  • Monitor - Listen to the mix

Released under the MIT License.