Riff Reactor

Perform the lead of an original synth-rock track. Every hit plays; streaks make the band hit harder.

Add to project

npx shadcn@latest add joacod/audiobits/riff-reactor

Adds editable source and the audiobits runtime dependency. Installation guide

Installs the catalog defaults and sound controller. The website visuals and input system stay in the showcase.

Use this sound

Use in src/integration.ts. Call play() from a user gesture and handle activation errors. Connect updates to application state; stop on cancellation or hiding, and dispose on removal.

import { createAudio } from "audiobits";
import { createRiff } from "./sounds/experiences/riff-reactor/controller";
import { riffReactorSong } from "./sounds/experiences/riff-reactor/song";

// The band's top tier needs about 30 voices; 48 keeps stealing out of reach.
const audio = createAudio({ maxVoices: 48, masterGainDb: -8 });
const riff = createRiff(audio, riffReactorSong);
let request = 0;

export async function play() {
  const token = ++request;
  await audio.start();
  if (token !== request) return;
  // Starts the band from the top. Returns the audio-clock transport.
  riff.start();
}

// Your judge decides that an input hit lead note `index`.
export function hit(index: number) {
  return riff.performLead(index);
}

// A stray or wrong input. The band keeps playing at its level.
export function miss() {
  riff.choke();
}

// Normalized performance in [0, 1]. The tier lands on the next bar line.
export function update(performance: number) {
  riff.setIntensity(performance >= 0.75 ? 2 : performance >= 0.35 ? 1 : 0);
}

export function stop() {
  request++;
  riff.stop();
}

export async function dispose() {
  stop();
  riff.dispose();
  await audio.dispose();
}
Production lifecycle

Customize or inspect

Connect your application's state and events to the sound controller. The full showcase source includes the browser host, input, and visuals.

View complete public source