Interop guide

Hooks vs Core.

Use React hooks for normal app controls, core primitives for custom Web Audio graphs, and ensureAudioContext() when a React screen needs both.

Choice

Start with hooks, drop to core when the graph is yours.

The React layer owns provider ergonomics. The core layer owns reusable playback primitives.

Hooks first for React apps

Use hooks when playback belongs to React UI state. AudioProvider creates the context lazily, connects masterGain -> analyser -> destination, and lets hooks expose stable play, stop, and isPlaying controls.

hook playback
import { AudioProvider, useTone } from "@webaudio-kit/react";

function CueButton() {
  const cue = useTone({
    durationMs: 180,
    frequency: 880,
    gain: 0.12,
    type: "square",
  });

  return (
    <button onClick={() => void cue.play()}>
      {cue.isPlaying ? "Restart cue" : "Play cue"}
    </button>
  );
}

export function App() {
  return (
    <AudioProvider>
      <CueButton />
    </AudioProvider>
  );
}

Core first for non-React and custom graphs

Use @webaudio-kit/core directly when React is not involved or when your application owns the destination node, scheduling, and handle lifecycle.

custom graph
import { playFrequencySweep, playTone } from "@webaudio-kit/core";

const audioContext = new AudioContext();
const masterGain = audioContext.createGain();
const analyser = audioContext.createAnalyser();

masterGain.gain.value = 0.2;
masterGain.connect(analyser);
analyser.connect(audioContext.destination);

await audioContext.resume();

const tone = playTone(
  audioContext,
  { durationMs: 240, frequency: 660, gain: 0.12 },
  masterGain,
);

const sweep = playFrequencySweep(
  audioContext,
  { durationMs: 700, from: 400, gain: 0.08, to: 1600 },
  masterGain,
);

tone.stop();
sweep.stop();

React + core interop

In React, call ensureAudioContext() from the user action instead of direct audio.audioContext null checks. It returns the same provider runtime that hooks use.

provider-routed core playback
import { playNoise, playTone } from "@webaudio-kit/core";
import { useAudioContext } from "@webaudio-kit/react";

function LayeredCueButton() {
  const audio = useAudioContext();

  async function playLayeredCue() {
    const runtime = await audio.ensureAudioContext();
    const tone = playTone(
      runtime.audioContext,
      {
        durationMs: 180,
        envelope: { attackMs: 8, releaseMs: 45 },
        frequency: 880,
        gain: 0.1,
        pattern: { repeat: 2, gapMs: 80 },
        type: "square",
      },
      runtime.masterGain,
    );
    const noise = playNoise(
      runtime.audioContext,
      {
        durationMs: 120,
        envelope: { attackMs: 4, releaseMs: 50 },
        gain: 0.025,
        type: "pink",
      },
      runtime.masterGain,
    );

    setTimeout(() => {
      tone.stop();
      noise.stop();
    }, 700);
  }

  return (
    <>
      <button onClick={() => void playLayeredCue()}>Play layered cue</button>
      <button onClick={() => audio.stopAll()}>Stop hook playback</button>
    </>
  );
}

Passing runtime.masterGain routes core playback through the provider analyser, so WaveformCanvas, SpectrumCanvas, and master volume still react to the custom cue.

The direct call shape is playTone(runtime.audioContext, options, runtime.masterGain) or playNoise(runtime.audioContext, options, runtime.masterGain).

Decision checklist

Use hooks when

The playback belongs to React UI state and you want stable controls, hook cleanup, and provider stopAll().

Use core when

The app is not React, or you own a custom Web Audio graph and will track every returned handle yourself.

Use both when

A React screen mostly uses hooks, but one advanced action needs direct playTone, playFrequencySweep, or playNoise.