Frequency

Interactive frequency visualization that syncs with musical input from audio

Installation

Run the following command

It will create a new file Frequency.tsx inside the components/cards/Frequency.tsx directory.

mkdir -p components/cards && touch components/cards/Frequency.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import React, { useState, useEffect, useRef } from "react";
import { motion } from "framer-motion";
 
const Frequency: React.FC = () => {
  const [audioData, setAudioData] = useState<number[]>(Array(30).fill(100)); // Initial heights
  const audioContextRef = useRef<AudioContext | null>(null);
  const analyserRef = useRef<AnalyserNode | null>(null);
  const sourceRef = useRef<MediaElementAudioSourceNode | null>(null);
  const audioElementRef = useRef<HTMLAudioElement | null>(null);
  const animationIdRef = useRef<number | null>(null);
 
  const setupAudio = () => {
    if (audioContextRef.current) return; // Prevent multiple AudioContexts
 
    audioContextRef.current = new (window.AudioContext ||
      (window as any).webkitAudioContext)();
    const audio = audioElementRef.current!;
 
    // Create a MediaElementSource from the audio element
    sourceRef.current = audioContextRef.current.createMediaElementSource(audio);
 
    // Create an AnalyserNode
    analyserRef.current = audioContextRef.current.createAnalyser();
    analyserRef.current.fftSize = 64; // Smaller FFT for lower frequencies
 
    // Connect audio source to analyser and destination
    sourceRef.current.connect(analyserRef.current);
    analyserRef.current.connect(audioContextRef.current.destination);
 
    // Create a buffer array to store the audio data
    const dataArray = new Uint8Array(analyserRef.current.frequencyBinCount);
 
    // Function to update the audio data and animate
    const updateAudioData = () => {
      analyserRef.current!.getByteFrequencyData(dataArray);
 
      // Map the dataArray to fit the number of bars (30 bars)
      const normalizedData = Array.from(dataArray)
        .slice(0, 30)
        .map((val) => {
          return (val / 255) * 200 + 100; // Normalize to fit the height (100px to 300px)
        });
 
      setAudioData(normalizedData); // Update the height values
 
      // Request the next animation frame
      animationIdRef.current = requestAnimationFrame(updateAudioData);
    };
 
    // Start updating the audio data
    animationIdRef.current = requestAnimationFrame(updateAudioData);
  };
 
  useEffect(() => {
    // Clean up on unmount
    return () => {
      if (animationIdRef.current) cancelAnimationFrame(animationIdRef.current);
      if (audioContextRef.current) {
        // Disconnect and close audio context to prevent memory leaks
        if (sourceRef.current) {
          sourceRef.current.disconnect();
        }
        audioContextRef.current.close();
      }
    };
  }, []);
 
  return (
    <div className="size-full center flex-col">
      <audio
        ref={audioElementRef}
        src="/music/audio2.mp3"
        onPlay={setupAudio}
        controls
        className="mb-20"
        autoPlay
      />
 
      <div className="flex items-end h-60">
        {audioData.map((height, index) => (
          <motion.div
            key={index}
            className="bg-gray-400 w-2 mx-0.5 rounded-2xl"
            animate={{
              height: `${height}px`, // Dynamic height based on the audio data
              backgroundColor: height > 200 ? "#3B82F6" : "#6B7280", // Change color based on intensity
            }}
            transition={{
              type: "spring",
              stiffness: 200,
              damping: 20,
            }}
          />
        ))}
      </div>
    </div>
  );
};
 
export default Frequency;

Credits

Built by Bossadi Zenith