UI/Components/Audio waveforms/Bar Waveform

Bar Waveform

A customizable bar audio waveform visualization component.

Animate a bar waveform, often used for audio visualizations.

0:00 / 0:10

Installation

CLI

npx shadcn@latest add "https://clippkit.com/r/bar-waveform"

Manual

Create a new file, for example, at src/components/clippkit/bar-waveform.tsx (or your preferred location) and paste the following code into it.

/**
 * Free Remotion Template Component
 * ---------------------------------
 * This template is free to use in your projects!
 * Credit appreciated but not required.
 *
 * Created by the team at https://www.reactvideoeditor.com
 *
 * Happy coding and building amazing videos! 🎉
 */
 
"use client";
 
import React, { useEffect, useState } from "react";
import {
  MediaUtilsAudioData,
  visualizeAudioWaveform,
} from "@remotion/media-utils";
import { useCurrentFrame, useVideoConfig } from "remotion";
 
// Helper function to generate waveform samples
const generateWaveformSamples = (
  audioData: MediaUtilsAudioData | null | undefined,
  numberOfSamples: number,
  frame: number,
  waveSpeed: number,
  fps: number
): number[] => {
  if (audioData) {
    // For bar waveform, we might want to adjust how windowInSeconds or smoothing works
    // For now, using a similar approach to linear waveform
    return visualizeAudioWaveform({
      fps,
      frame,
      audioData,
      numberOfSamples,
      windowInSeconds: 1 / fps, // Visualize a single frame's worth of audio
    });
  }
  // Fallback for when audioData is not available - simple sine wave
  return Array(numberOfSamples)
    .fill(0)
    .map((_, i) => {
      return (
        Math.sin(frame / waveSpeed + (i / numberOfSamples) * 2 * Math.PI) *
          0.5 +
        0.5
      );
    });
};
 
interface BarWaveformProps {
  audioData?: MediaUtilsAudioData | null;
  numberOfSamples?: number;
  barColor?: string;
  barWidth?: number; // Width of each bar
  barGap?: number; // Gap between bars
  waveAmplitude?: number; // Max height of the bars
  waveSpeed?: number;
  containerStyle?: React.CSSProperties;
  height?: string | number;
  width?: string | number;
  barBorderRadius?: string | number;
  growUpwardsOnly?: boolean; // New prop: if true, bars only grow upwards from the center
}
 
export function BarWaveform({
  audioData,
  numberOfSamples = 64,
  barColor = "var(--foreground)",
  barWidth = 5,
  barGap = 2,
  waveAmplitude = 100,
  waveSpeed = 10,
  containerStyle,
  height: propHeight,
  width: propWidth,
  barBorderRadius = 0,
  growUpwardsOnly = false, // Default value for the new prop
}: BarWaveformProps) {
  const frame = useCurrentFrame();
  const { width: videoWidth, height: videoHeight, fps } = useVideoConfig();
 
  const finalWidth = typeof propWidth === "number" ? propWidth : videoWidth;
  const finalHeight = typeof propHeight === "number" ? propHeight : videoHeight;
 
  const [barHeights, setBarHeights] = useState<number[]>([]);
 
  useEffect(() => {
    const waveformData = generateWaveformSamples(
      audioData,
      numberOfSamples,
      frame,
      waveSpeed,
      fps
    );
 
    // Map waveform data (0 to 1) to bar heights
    const newBarHeights = waveformData.map((sample) =>
      // Ensure minimum height for visibility, scale by waveAmplitude and center
      Math.max(1, sample * waveAmplitude)
    );
    setBarHeights(newBarHeights);
  }, [frame, audioData, numberOfSamples, waveAmplitude, waveSpeed, fps]);
 
  // Calculate total width occupied by bars and gaps to center them if needed
  const totalBarWidth = numberOfSamples * barWidth;
  const totalGapWidth = (numberOfSamples - 1) * barGap;
  const waveformVisualWidth = totalBarWidth + totalGapWidth;
  const startX = (finalWidth - waveformVisualWidth) / 2;
 
  return (
    <div
      style={{
        width: finalWidth,
        height: finalHeight,
        display: "flex",
        alignItems: "center",
        justifyContent: "center", // Or use startX for precise positioning
        overflow: "hidden",
        backgroundColor: "transparent",
        ...containerStyle,
      }}
    >
      <svg
        viewBox={`0 0 ${finalWidth} ${finalHeight}`}
        width={finalWidth}
        height={finalHeight}
        style={{
          width: finalWidth,
          height: finalHeight,
        }}
      >
        {barHeights.map((barH, i) => {
          const x = startX + i * (barWidth + barGap);
          // Bars drawn from center, extending up and down
          // For simplicity, drawing upwards from baseline for now
          let rectY: number;
          let rectHeightValue: number;
 
          if (growUpwardsOnly) {
            // Bar grows upwards from the center line
            const upwardHeight = barH / 2; // Height from the center line
            rectY = finalHeight / 2 - upwardHeight;
            // Height of the bar is upwardHeight, capped by the available space above the center line (finalHeight / 2)
            // Also ensure a minimum height of 1 for visibility if upwardHeight is very small but not zero
            rectHeightValue =
              upwardHeight > 0
                ? Math.max(1, Math.min(upwardHeight, finalHeight / 2))
                : 0;
          } else {
            // Original behavior: bar is centered vertically
            rectY = finalHeight / 2 - barH / 2;
            // Ensure a minimum height of 1 for visibility if barH is very small but not zero
            rectHeightValue =
              barH > 0 ? Math.max(1, Math.min(barH, finalHeight)) : 0;
          }
 
          return (
            <rect
              key={i}
              x={x}
              y={rectY}
              width={barWidth}
              height={rectHeightValue}
              fill={barColor}
              rx={barBorderRadius} // For rounded corners
              ry={barBorderRadius} // For rounded corners
            />
          );
        })}
      </svg>
    </div>
  );
}

Update the import paths in your Remotion compositions if you placed the file in a different location than shown in the usage examples.

Usage

Once the BarWaveform component is added to your project (either via CLI or Manually), you can integrate it into your Remotion project by importing it and defining a Composition.

Prerequisite

Ensure you have a Remotion project set up. If not, please refer to the Remotion documentation to get started.

Project Structure Example

Here’s an example folder layout showing where to place the component and how it fits into a typical Remotion project

app/main.tsx
import React from "react";
import { MediaUtilsAudioData, useAudioData } from "@remotion/media-utils";
import { Player } from "@remotion/player";
import { Audio } from "remotion";
 
import BarWaveform from "../components/bar-waveform"; // Import BarWaveform
 
// Media source for the demo
const MEDIA_SRC =
  "https://rwxrdxvxndclnqvznxfj.supabase.co/storage/v1/object/public/sounds//moon-landing.mp3";
 
// Define props for the new composition component
interface AudioWaveformCompositionProps {
  barWaveformProps: Omit<
    React.ComponentProps<typeof BarWaveform>,
    "audioData"
  > & { audioData?: MediaUtilsAudioData | null };
  mediaSrc: string;
}
 
// New component combining BarWaveform and Audio
const AudioWaveformComposition: React.FC<AudioWaveformCompositionProps> = ({
  barWaveformProps,
  mediaSrc,
}) => {
  return (
    <>
      <BarWaveform {...barWaveformProps} />
      <Audio src={mediaSrc} />
    </>
  );
};
 
export function BarWaveformDemo() {
  const audioData = useAudioData(MEDIA_SRC);
 
  const barWaveformProps = React.useMemo(
    () => ({
      numberOfSamples: 50,
      barColor: "var(--foreground)",
      barWidth: 3,
      barGap: 2,
      waveAmplitude: 220,
      waveSpeed: 5,
      audioData,
      barBorderRadius: 0,
      growUpwardsOnly: false,
    }),
    [audioData]
  );
 
  // Calculate duration in frames for the player
  const playerDurationInFrames = audioData
    ? Math.floor(audioData.durationInSeconds * 30)
    : 300;
 
  return (
    <Player
      component={AudioWaveformComposition} // Use the new component
      inputProps={{ barWaveformProps, mediaSrc: MEDIA_SRC }} // Pass props for AudioWaveformComposition
      durationInFrames={playerDurationInFrames}
      compositionWidth={640}
      compositionHeight={360}
      fps={30}
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "transparent",
      }}
      controls
      loop
    />
  );
}

Define a Composition

In your Remotion project's entry file (commonly src/Root.tsx, src/index.tsx, app/main.tsx), import BarWaveform and define a Composition.

app/main.tsx (or equivalent)
import { Player } from "@remotion/player";
import { Audio } from "remotion";

import BarWaveform from "@/components/clippkit/bar-waveform"; // Assuming you placed it in src/components/clippkit

// Example audio source
const MEDIA_SRC = "YOUR_AUDIO_SOURCE_URL_HERE"; // e.g., a public URL to an mp3 file

export default function MyVideoComposition() {
  // You would typically use useAudioData here if MEDIA_SRC is dynamic or needs processing
  // For simplicity in this example, we'll assume direct usage if BarWaveform handles it internally
  // or audioData is passed via inputProps if needed.

  const barWaveformProps = {
    numberOfSamples: 50,
    barColor: "lightgreen",
    waveAmplitude: 100,
    waveSpeed: 8,
    barWidth: 6,
    barGap: 2,
    // audioData: audioData, // Pass if useAudioData is used in this component
  };

  return (
    <Player
      component={BarWaveform} // Or a wrapper component if using <Audio> alongside
      inputProps={barWaveformProps}
      durationInFrames={300} // Adjust based on your audio duration
      compositionWidth={640}
      compositionHeight={360}
      fps={30}
      style={{
        width: "100%",
        height: "100%",
      }}
      autoPlay
      controls
      loop
    />
  );
}

API Reference

The component exported as BarWaveform (e.g., from apps/docs/registry/default/ui/bar-waveform.tsx or your project's component path) accepts the following props to customize its appearance and animation:

PropTypeDefault ValueDescription
audioDataMediaUtilsAudioDatanullAudio data processed by @remotion/media-utils.
numberOfSamplesnumber64Number of bars to render.
barColorstring"var(--foreground)"Color of the bars. Accepts any valid CSS color value.
barWidthnumber5Width of each bar in pixels.
barGapnumber2Gap between each bar in pixels.
barBorderRadiusstring | number0Border radius for the bars.
waveAmplitudenumber100Maximum height of the bars.
waveSpeednumber10Speed of the wave animation. Higher value means slower animation.
heightstring | numberVideo config heightExplicit height for the waveform container. Overrides video config height.
widthstring | numberVideo config widthExplicit width for the waveform container. Overrides video config width.
containerStyleReact.CSSProperties{}Custom CSS styles for the main container div.

On this page