Skip to content

MP3Deck

The MP3Deck component loads and plays local audio files (MP3, WAV, etc.). It provides playback controls, seeking, and looping capabilities.

Props

PropTypeDefaultDescription
outputModStreamRefRequiredReference to output the audio signal
labelstring'mp3-deck'Label for the component in metadata
srcstring''Audio source URL (controlled or initial value)
onSrcChange(src: string) => void-Callback when source URL changes
gainnumber1.0Gain level 0-1+ (controlled or initial value)
onGainChange(gain: number) => void-Callback when gain changes
loopbooleanfalseWhether audio loops (controlled or initial value)
onLoopChange(loop: boolean) => void-Callback when loop state changes
onPlayingChange(isPlaying: boolean) => void-Callback when playback state changes
onTimeUpdate(currentTime: number, duration: number) => void-Callback when playback position updates
onError(error: string | null) => void-Callback when error state changes
onEnd() => void-Callback when track finishes playing
childrenfunction-Render prop function receiving control props

Render Props

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

PropertyTypeDescription
srcstringCurrent audio source URL
setSrc(src: string) => voidSet audio source by URL
loadFile(file: File) => voidLoad audio from a File object
gainnumberCurrent gain level (0-1+)
setGain(value: number) => voidUpdate the gain level
loopbooleanWhether the audio loops
setLoop(value: boolean) => voidEnable or disable looping
isPlayingbooleanWhether audio is currently playing
play() => voidStart playback
pause() => voidPause playback
stop() => voidStop playback and reset to start
currentTimenumberCurrent playback position in seconds
durationnumberTotal duration of audio in seconds
seek(time: number) => voidSeek to a specific time in seconds
isActivebooleanWhether the audio deck is active
isReadybooleanWhether the audio is loaded and ready to play
errorstring | nullError message if loading failed

Usage

Basic Usage

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

function App() {
  const deckOut = useRef(null);

  return (
    <>
      <MP3Deck output={deckOut}>
        {({ setSrc, play }) => (
          <button onClick={() => {
            setSrc('/path/to/audio.mp3');
            play();
          }}>
            Load and Play
          </button>
        )}
      </MP3Deck>
      <Monitor input={deckOut} />
    </>
  );
}

Full Playback Controls

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

function App() {
  const deckOut = useRef(null);

  return (
    <MP3Deck output={deckOut}>
      {({
        loadFile,
        isPlaying,
        isReady,
        play,
        pause,
        stop,
        currentTime,
        duration,
        seek,
        loop,
        setLoop,
        gain,
        setGain,
        error
      }) => (
        <div>
          {error && <div style={{ color: 'red' }}>Error: {error}</div>}

          <input
            type="file"
            accept="audio/*"
            onChange={(e) => {
              const file = e.target.files?.[0];
              if (file) loadFile(file);
            }}
          />

          <div>
            <button onClick={play} disabled={!isReady || isPlaying}>
              {!isReady ? 'Loading...' : 'Play'}
            </button>
            <button onClick={pause} disabled={!isPlaying}>Pause</button>
            <button onClick={stop}>Stop</button>
          </div>

          <div>
            <label>
              Position: {currentTime.toFixed(1)}s / {duration.toFixed(1)}s
            </label>
            <input
              type="range"
              min="0"
              max={duration || 0}
              step="0.1"
              value={currentTime}
              onChange={(e) => seek(Number(e.target.value))}
            />
          </div>

          <div>
            <label>
              <input
                type="checkbox"
                checked={loop}
                onChange={(e) => setLoop(e.target.checked)}
              />
              Loop
            </label>
          </div>

          <div>
            <label>Volume: {gain.toFixed(2)}</label>
            <input
              type="range"
              min="0"
              max="1"
              step="0.01"
              value={gain}
              onChange={(e) => setGain(Number(e.target.value))}
            />
          </div>
        </div>
      )}
    </MP3Deck>
  );
}

With Effects Chain

tsx
import { MP3Deck, Filter, Delay, Monitor } from '@mode-7/mod';
import { useRef } from 'react';

function App() {
  const deckOut = useRef(null);
  const filterOut = useRef(null);
  const delayOut = useRef(null);

  return (
    <>
      <MP3Deck output={deckOut}>
        {({ loadFile, play, pause, isPlaying, isReady }) => (
          <div>
            <input
              type="file"
              accept="audio/*"
              onChange={(e) => {
                const file = e.target.files?.[0];
                if (file) loadFile(file);
              }}
            />
            <button
              onClick={isPlaying ? pause : play}
              disabled={!isReady}
            >
              {!isReady ? 'Loading...' : isPlaying ? 'Pause' : 'Play'}
            </button>
          </div>
        )}
      </MP3Deck>
      <Filter input={deckOut} output={filterOut} type="lowpass" frequency={2000} />
      <Delay input={filterOut} output={delayOut} time={0.5} feedback={0.3} />
      <Monitor input={delayOut} />
    </>
  );
}

Controlled Props

You can control the MP3Deck from external state using controlled props:

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

function App() {
  const deckOut = useRef(null);
  const [src, setSrc] = useState('');
  const [gain, setGain] = useState(1.0);
  const [loop, setLoop] = useState(false);
  const [isPlaying, setPlaying] = useState(false);

  return (
    <>
      <MP3Deck
        output={deckOut}
        src={src}
        onSrcChange={setSrc}
        gain={gain}
        onGainChange={setGain}
        loop={loop}
        onLoopChange={setLoop}
        onPlayingChange={setPlaying}
      >
        {({ loadFile, currentTime, duration, seek }) => (
          <div>
            <input
              type="file"
              accept="audio/*"
              onChange={(e) => {
                const file = e.target.files?.[0];
                if (file) loadFile(file);
              }}
            />

            <div>
              <label>Position: {currentTime.toFixed(1)}s / {duration.toFixed(1)}s</label>
              <input
                type="range"
                min="0"
                max={duration || 0}
                step="0.1"
                value={currentTime}
                onChange={(e) => seek(Number(e.target.value))}
              />
            </div>
          </div>
        )}
      </MP3Deck>

      <button onClick={() => setPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>

      <label>
        <input
          type="checkbox"
          checked={loop}
          onChange={(e) => setLoop(e.target.checked)}
        />
        Loop
      </label>

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

Imperative Refs

For programmatic control of playback and file loading, you can use refs:

tsx
import { MP3Deck, MP3DeckHandle, Monitor } from '@mode-7/mod';
import { useRef, useEffect } from 'react';

function App() {
  const deckRef = useRef<MP3DeckHandle>(null);
  const deckOut = useRef(null);

  useEffect(() => {
    // Access current state
    if (deckRef.current) {
      const state = deckRef.current.getState();
      console.log('Source:', state.src);
      console.log('Is playing:', state.isPlaying);
      console.log('Is ready:', state.isReady);
      console.log('Current time:', state.currentTime);
      console.log('Duration:', state.duration);
      console.log('Gain:', state.gain);
      console.log('Loop:', state.loop);
    }
  }, []);

  const handlePlayback = () => {
    if (!deckRef.current) return;

    // Control playback
    deckRef.current.play();
    // deckRef.current.pause();
    // deckRef.current.stop();
  };

  const skipToChorus = () => {
    // Skip to 60 seconds (example chorus position)
    deckRef.current?.seek(60);
  };

  const loadNewFile = (file: File) => {
    deckRef.current?.loadFile(file);
  };

  return (
    <>
      <MP3Deck ref={deckRef} output={deckOut} />
      <button onClick={handlePlayback}>Play</button>
      <button onClick={skipToChorus}>Skip to Chorus</button>
      <input
        type="file"
        accept="audio/*"
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) loadNewFile(file);
        }}
      />
      <Monitor input={deckOut} />
    </>
  );
}

Note: The imperative handle provides play(), pause(), stop(), seek(), and loadFile() methods for playback control, plus getState() for read-only state access. To control gain and loop programmatically, use the controlled props pattern shown above.

Important Notes

Supported Formats

  • MP3, WAV, OGG, AAC, and other formats supported by the browser's <audio> element
  • Format support varies by browser

Loading Audio

  • Use setSrc() for URLs (local or remote)
  • Use loadFile() for File objects from <input type="file">
  • Audio must be loaded before playback can start

CORS Considerations

  • Remote audio files must have appropriate CORS headers
  • The crossOrigin="anonymous" attribute is set automatically for remote URLs
  • Local blob URLs (from File objects) do not use crossOrigin

User Gesture Required

Some browsers require a user gesture (like a button click) before audio can play. Make sure playback is triggered by user interaction.

Released under the MIT License.