reelcn
Audio

Audio

Audio Reactive

Wraps anything and drives its scale, or a glow, from how loud the audio is right now.

Install

Usage

<AudioReactive src={staticFile("track.mp3")} effect="both">
  <Img src={staticFile("logo.png")} />
</AudioReactive>

Props

NameTypeDefaultDescription
srcstringAudio file; `staticFile("…")` or a URL.
effect?"scale" | "glow" | "both""scale"
intensity?number1How far the effect travels at full volume. 1 is a 20% swell.
color?string
windowInSeconds?number10
children?ReactNode
style?CSSProperties
className?string

Use

  • Making a logo, headline or card move with the track
  • Reacting to a voice whose tempo you do not know

Avoid

  • A known BPM, where pure math is steadier — use use-beat

Dependencies

@remotion/media-utils, remotion

Source

registry/items/audio-reactive.tsx
/**
 * @title Audio Reactive
 * @category audio
 * @description Wraps anything and drives its scale, or a glow, from how loud the audio is right now.
 * @duration sustained
 * @use Making a logo, headline or card move with the track
 * @use Reacting to a voice whose tempo you do not know
 * @avoid A known BPM, where pure math is steadier — use `use-beat`
 * @tags audio, reactive, pulse, wrapper
 * @example
 * <AudioReactive src={staticFile("track.mp3")} effect="both">
 *   <Img src={staticFile("logo.png")} />
 * </AudioReactive>
 */
import { useWindowedAudioData, visualizeAudioWaveform } from "@remotion/media-utils";
import type React from "react";
import { useCurrentFrame, useVideoConfig } from "remotion";
import { alpha, useTheme, useViewport } from "./core";

export type AudioReactiveProps = {
  /** Audio file; `staticFile("…")` or a URL. */
  src: string;
  effect?: "scale" | "glow" | "both";
  /** How far the effect travels at full volume. 1 is a 20% swell. */
  intensity?: number;
  color?: string;
  windowInSeconds?: number;
  children?: React.ReactNode;
  style?: React.CSSProperties;
  className?: string;
};

export function AudioReactive({
  src,
  effect = "scale",
  intensity = 1,
  color,
  windowInSeconds = 10,
  children,
  style,
  className,
}: AudioReactiveProps) {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const { u } = useViewport();
  const theme = useTheme();
  const { audioData, dataOffsetInSeconds } = useWindowedAudioData({ src, frame, fps, windowInSeconds });
  const samples = audioData
    ? visualizeAudioWaveform({
        fps,
        frame,
        audioData,
        numberOfSamples: 8,
        windowInSeconds: 1 / fps,
        channel: 0,
        dataOffsetInSeconds,
      })
    : [0];
  // `visualizeAudioWaveform`'s minus-one-to-one range alternates sign per index as a
  // drawing convention, not a real polarity, so summing magnitudes (not raw values)
  // keeps a silent clip from averaging out to a false zero. Real mixes leave headroom
  // well under full scale, so the mean gets a bigger gain than a peak reading would need.
  const level = Math.min(1, (samples.reduce((total, value) => total + Math.abs(value), 0) / samples.length) * 24);
  const tone = color ?? theme.colors.accent;

  return (
    <div
      className={className}
      style={{
        display: "inline-block",
        scale: effect === "glow" ? "1" : String(1 + level * 0.2 * intensity),
        filter: effect === "scale" ? undefined : `drop-shadow(0 0 ${u(30) * level * intensity}px ${alpha(tone, 0.75)})`,
        ...style,
      }}
    >
      {children}
    </div>
  );
}

Related