Skip to content

Mixer

The Mixer component combines multiple audio signals into a single output, with independent level control for each input. Essential for creating multi-source audio applications.

Props

PropTypeDefaultDescription
inputsModStreamRef[]RequiredArray of audio signals to mix
outputModStreamRefRequiredMixed audio output
labelstring'mixer'Label for the component in metadata
levelsnumber[]-Controlled levels array (0-1+) for each input
onLevelsChange(levels: number[]) => void-Callback when levels change (for controlled mode)
childrenfunction-Render prop function receiving control props

Render Props

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

PropertyTypeDescription
levelsnumber[]Array of level values (0-1+) for each input
setLevels(levels: number[]) => voidUpdate all levels at once
setLevel(index: number, value: number) => voidUpdate a single level
isActivebooleanWhether the mixer is active

Usage

Basic Usage

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

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

  return (
    <>
      <ToneGenerator output={tone1Out} frequency={220} />
      <ToneGenerator output={tone2Out} frequency={330} />
      <NoiseGenerator output={noiseOut} type="white" />

      <Mixer
        inputs={[tone1Out, tone2Out, noiseOut]}
        output={mixOut}
      />

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

With Fader Controls

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

function App() {
  const input1 = useRef(null);
  const input2 = useRef(null);
  const input3 = useRef(null);
  const input4 = useRef(null);
  const mixOut = useRef(null);

  const channelNames = ['Kick', 'Snare', 'Hi-Hat', 'Bass'];

  return (
    <Mixer
      inputs={[input1, input2, input3, input4]}
      output={mixOut}
    >
      {({ levels, setLevel }) => (
        <div style={{ display: 'flex', gap: '20px' }}>
          {levels.map((level, index) => (
            <div key={index} style={{ textAlign: 'center' }}>
              <label>{channelNames[index]}</label>
              <input
                type="range"
                orient="vertical"
                min="0"
                max="1.5"
                step="0.01"
                value={level}
                onChange={(e) => setLevel(index, Number(e.target.value))}
                style={{
                  writingMode: 'bt-lr',
                  appearance: 'slider-vertical',
                  height: '150px',
                  width: '30px',
                }}
              />
              <div>{(level * 100).toFixed(0)}%</div>
              <button onClick={() => setLevel(index, level > 0 ? 0 : 1)}>
                {level > 0 ? 'Mute' : 'Unmute'}
              </button>
            </div>
          ))}
        </div>
      )}
    </Mixer>
  );
}

Submixer Setup

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

function App() {
  // Individual sources
  const tone1Out = useRef(null);
  const tone2Out = useRef(null);
  const tone3Out = useRef(null);

  // Processed versions
  const filter1Out = useRef(null);
  const filter2Out = useRef(null);
  const filter3Out = useRef(null);

  // Submixes
  const dryMixOut = useRef(null);
  const reverbOut = useRef(null);

  // Master
  const masterOut = useRef(null);

  return (
    <>
      {/* Sources */}
      <ToneGenerator output={tone1Out} frequency={220} />
      <ToneGenerator output={tone2Out} frequency={330} />
      <ToneGenerator output={tone3Out} frequency={440} />

      {/* Processing */}
      <Filter input={tone1Out} output={filter1Out} type="lowpass" frequency={800} />
      <Filter input={tone2Out} output={filter2Out} type="lowpass" frequency={1200} />
      <Filter input={tone3Out} output={filter3Out} type="lowpass" frequency={1600} />

      {/* Submixer for dry signals */}
      <Mixer
        inputs={[filter1Out, filter2Out, filter3Out]}
        output={dryMixOut}
      />

      {/* Reverb send */}
      <Reverb input={dryMixOut} output={reverbOut} />

      {/* Master mixer */}
      <Mixer inputs={[dryMixOut, reverbOut]} output={masterOut}>
        {({ setLevel }) => {
          React.useEffect(() => {
            setLevel(0, 0.8);  // Dry at 80%
            setLevel(1, 0.4);  // Reverb at 40%
          }, []);
          return null;
        }}
      </Mixer>

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

Dynamic Routing

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

function App() {
  const [numChannels, setNumChannels] = useState(4);
  const inputs = Array.from({ length: numChannels }, () => useRef(null));
  const mixOut = useRef(null);

  return (
    <div>
      <div>
        <label>Number of channels:</label>
        <input
          type="number"
          min="1"
          max="16"
          value={numChannels}
          onChange={(e) => setNumChannels(Number(e.target.value))}
        />
      </div>

      <Mixer inputs={inputs} output={mixOut}>
        {({ levels, setLevel, setLevels }) => (
          <div>
            <button onClick={() => setLevels(levels.map(() => 1))}>
              Reset All to 100%
            </button>
            <button onClick={() => setLevels(levels.map(() => 0))}>
              Mute All
            </button>

            <div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
              {levels.map((level, index) => (
                <div key={index}>
                  <div>Ch {index + 1}</div>
                  <input
                    type="range"
                    orient="vertical"
                    min="0"
                    max="1"
                    step="0.01"
                    value={level}
                    onChange={(e) => setLevel(index, Number(e.target.value))}
                    style={{ writingMode: 'bt-lr', height: '100px' }}
                  />
                  <div>{(level * 100).toFixed(0)}%</div>
                </div>
              ))}
            </div>
          </div>
        )}
      </Mixer>
    </div>
  );
}

Controlled Props

Manage mixer state externally using controlled props:

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

function App() {
  const input1 = useRef(null);
  const input2 = useRef(null);
  const input3 = useRef(null);
  const mixOut = useRef(null);

  const [levels, setLevels] = useState([1, 1, 1]);

  const setLevel = (index: number, value: number) => {
    setLevels(prev => {
      const newLevels = [...prev];
      newLevels[index] = value;
      return newLevels;
    });
  };

  return (
    <>
      <Mixer
        inputs={[input1, input2, input3]}
        output={mixOut}
        levels={levels}
        onLevelsChange={setLevels}
      />

      <button onClick={() => setLevels([1, 1, 1])}>Reset All</button>
      <button onClick={() => setLevels([0, 0, 0])}>Mute All</button>

      {levels.map((level, i) => (
        <div key={i}>
          <label>Channel {i + 1}: {(level * 100).toFixed(0)}%</label>
          <input
            type="range"
            min="0"
            max="1"
            step="0.01"
            value={level}
            onChange={(e) => setLevel(i, Number(e.target.value))}
          />
        </div>
      ))}
    </>
  );
}

Programmatic Control

Control mixer programmatically using controlled state:

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

function App() {
  const input1 = useRef(null);
  const input2 = useRef(null);
  const input3 = useRef(null);
  const mixOut = useRef(null);
  const [levels, setLevels] = useState([0, 0, 0]);

  useEffect(() => {
    // Fade in channels one by one
    let channel = 0;
    const interval = setInterval(() => {
      if (channel < 3) {
        setLevels(prev => {
          const newLevels = [...prev];
          newLevels[channel] = 1;
          return newLevels;
        });
        channel++;
      } else {
        clearInterval(interval);
      }
    }, 1000);

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

  return (
    <Mixer
      inputs={[input1, input2, input3]}
      output={mixOut}
      levels={levels}
      onLevelsChange={setLevels}
    />
  );
}

Important Notes

Level Values

  • 0 = Silent (muted)
  • 1 = Unity gain (100%, no change)
  • 1 = Amplification (can cause clipping!)

  • Values can exceed 1.0 for gain boost

Number of Inputs

  • No hard limit on the number of inputs
  • Each input gets its own gain control
  • More inputs = more mixing overhead
  • Levels array matches the order of inputs array

Mixing Behavior

  • All inputs are summed together
  • Each input is scaled by its level value
  • Output can exceed the normal range if levels are too high
  • Consider using a Compressor after the mixer to prevent clipping

Initial Levels

  • All channels start at 1.0 (unity gain) by default
  • Use setLevels() or setLevel() to adjust on mount

Clipping Prevention

When mixing multiple sources, the combined output can exceed safe levels and cause distortion. Monitor your levels and consider:

  • Keeping individual channel levels below 1.0
  • Using a Compressor or Limiter after the mixer
  • Reducing the overall output gain on the Monitor

Released under the MIT License.