How to Create an Audio Visualizer using Nextjs
Tags: webdev, nextjs, api, react
Published: 2024-04-14 14:59:38


An audio visualizer is a graphical representation of audio frequencies and amplitudes on a display. It adds a visually appealing element to audio playback, animating and synchronizing visual elements with the audio being played. This can enhance the user experience by providing a visually immersive and interactive interface. In this article, we will develop a simple Next.js application that illustrates how to use the Web Audio API to capture audio data from a source and display visual representations on the screen.

## Why is an Audio Visualizer necessary

An audio visualizer is an essential feature that can make a website more exciting and interactive. Imagine listening to music or audio and seeing the sound come to life with colorful visuals and animations. It takes your browsing experience to a whole new level!

Also, Adding an audio visualizer to a site helps you better understand and connect with the audio content. Think about it — when you can see the visual representation of the sound, it becomes much easier to grasp its meaning. 

## How does an Audio visualizer work

So, you’re probably aware that when you’re jamming to your favorite tunes online, your browser is doing some heavy lifting behind the scenes to make it all happen. That’s where the Web Audio API comes into play. This powerful tool allows developers to create and manipulate audio graphs, which represent the flow of audio data through various nodes. Each node has a specific task, such as reading audio input from a file or microphone, applying effects like reverb or distortion, or sending the audio to your speakers or headphones.

But the Web Audio API doesn't stop at audio processing. It also gives developers precise control over timing and synchronization, allowing them to fine-tune the timing and playback of audio for a seamless and immersive experience.

### Terms Associated with the Web Audio API

The Web Audio API offers a flexible system for managing audio on the web. It has a variety of methods that enable developers to perform various audio effects. 

In this article, we'll explore three essential terms in the Web Audio API for our project. For  more in-depth information, visit [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API).

`AudioContext`: It's where you manage all the audio stuff, like creating sounds, adding effects, and controlling how they're played. Think of it as a dashboard where you connect different audio parts to make your sounds come to life on the web.

`AnalyserNode`: An `AnalyserNode` is a type of audio node that performs real-time analysis of audio data. It allows developers to extract frequency and time-domain data from audio signals.

`SourceNode`: It is a type of audio nodein the Web Audio API that represents an audio source, such as an audio file, microphone input, or synthesized sound.

`SourceNodes` are created using methods provided by the `AudioContext` as the `createMediaElementSource()`

## Getting started

Let’s start by creating a new Next.js project by running the following command in your terminal. 
```bash
npx create-next-app Audio-App
```
Don't forget to include Tailwind CSS as well, which will help you style your project with ease.

At the `page.js`, rename the `Home` component to `AudioApp`. Your `page.js` app should look like this:

```jsx
"use client";
import React, { useState, useRef } from "react";

function AudioApp() {
  return <main></main>;
}

export default AudioApp;
```
Now, you have created a new Next.js project named `Audio-App`, installed Tailwind CSS, and renamed the default page with the `Home` component to `AudioApp`.

### Styling of the app

To display an audio visualizer, an audio file is required. One way to do this is to add a form to your `AudioApp.js` component that allows users to upload their own music. 

I found a helpful template from Flowbite that we can use: [Flowbite File Input](https://flowbite.com/docs/forms/file-input).

```jsx
"use client";
import React from "react";

function AudioApp() {
  return (
    <main>
      <div className="h-screen font-sans text-gray-900 bg-gray-300 border-box">
        <div className="flex justify-center w-full mx-auto sm:max-w-lg">
          <div className="flex flex-col items-center justify-center w-full h-auto my-20 bg-white sm:w-3/4 sm:rounded-lg sm:shadow-xl">
            <div className="mt-10 mb-10 text-center">
              <h2 className="text-2xl font-semibold mb-2">Upload your music</h2>
              <p className="text-xs text-gray-500">File should be of mp3 format</p>
            </div>
            <form
              action="#"
              className="relative w-4/5 h-32 max-w-xs mb-10 bg-white bg-gray-100 rounded-lg shadow-inner"
            >
              <input type="file" id="file-upload" className="hidden" />
              <label
                htmlFor="file-upload"
                className="z-20 flex flex-col-reverse items-center justify-center w-full h-full cursor-pointer"
              >
                <p className="z-10 text-xs font-light text-center text-gray-500">
                  Upload your music here
                </p>
                <svg
                  className="z-10 w-8 h-8 text-indigo-400"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
                </svg>
              </label>
            </form>
          </div>
        </div>
      </div>
    </main>
  );
}

export default AudioApp;
```

This will create a styled form for media upload. To display the audio player and audio visualizer, we will add this styled music card from [Tailwindcomponents](https://tailwindcomponents.com/component/music-card-osu-lazer) below the form.

```jsx
"use client";
import React, { useState, useRef } from "react";

function AudioApp() {

  return (
    <main>
      <div className="h-screen font-sans text-gray-900 bg-gray-300 border-box">
        <div className="flex justify-center w-full mx-auto sm:max-w-lg">
          <div className="flex flex-col items-center justify-center w-full h-auto my-20 bg-white sm:w-3/4 sm:rounded-lg sm:shadow-xl">
            <div className="mt-10 mb-10 text-center">
              <h2 className="text-2xl font-semibold mb-2">Upload your music</h2>
              <p className="text-xs text-gray-500">File should be of mp3 format</p>
            </div>
            <form
              action="#"
              className="relative w-4/5 h-32 max-w-xs mb-10 bg-white bg-gray-100 rounded-lg shadow-inner"
            >
              <input type="file" id="file-upload" className="hidden" />
              <label
                htmlFor="file-upload"
                className="z-20 flex flex-col-reverse items-center justify-center w-full h-full cursor-pointer"
              >
                <p className="z-10 text-xs font-light text-center text-gray-500">
                  Upload your music here
                </p>
                <svg
                  className="z-10 w-8 h-8 text-indigo-400"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
                </svg>
              </label>
            </form>
          </div>
        </div>
      </div>

      <main className="flex min-h-screen w-full items-center justify-center">
        <article className="group relative flex h-[12rem] w-[50rem] overflow-hidden rounded-2xl bg-[#3a4448]">
          {/* <!-- image (left side) --> */}
          <div className="absolute inset-y-0 left-0 w-48">
            <img
              src="https://images.unsplash.com/photo-1487180144351-b8472da7d491?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTh8fG11c2ljfGVufDB8fDB8fHww"
              alt=""
              className="h-full w-full object-cover object-center opacity-95"
            />

            <div className="invisible absolute inset-0 flex h-full w-full items-center justify-center bg-[#0c0c0c]/70 opacity-0 transition-all group-hover:visible group-hover:opacity-100"></div>
          </div>

          <div className="absolute inset-y-0 left-44 w-[39rem] overflow-hidden rounded-2xl transition-all group-hover:w-[36rem]">
            <div className="h-full w-full bg-cover bg-center">
              <img
                src="https://images.unsplash.com/photo-1487180144351-b8472da7d491?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTh8fG11c2ljfGVufDB8fDB8fHww"
                alt=""
                srcset=""
              />
            </div>
            <div className="h-full w-full bg-[#455055]/80 transition-all group-hover:bg-[#31383b]/80">
            </div>
          </div>

          <section className="absolute inset-0 flex flex-col justify-between p-4 text-white">
            <header className="space-y-1">
              <div className="text-3xl font-medium">Audio Visualizer</div>
            </header>
            <div className="flex space-x-2">
              <div className="flex items-center space-x-1">
                <div>
                  <audio />
                  <button>
                    <svg
                      className="h-w-14 w-14 cursor-pointer text-white transition-all hover:text-yellow-400"
                      fill="currentColor"
                      viewBox="0 0 20 20"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        fillRule="evenodd"
                        d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
                        clipRule="evenodd"
                      ></path>
                    </svg>
                  </button>
                </div>
                <canvas width={500} height={200} />

                <span className="h-5 w-2 rounded-full bg-red-500"></span>

                <span className="h-5 w-2 rounded-full bg-green-500"></span>
                <span className="h-5 w-2 rounded-full bg-yellow-500"></span>
              </div>
            </div>
          </section>
        </article>
      </main>
    </main>
  );
}

export default AudioApp;
```

From the code, we managed to generate a rectangular card with a play button (images sourced from [Unsplash](https://unsplash.com/)). Two essential elements in the code above for the app's effective operation are the `<audio>` element and the `<canvas>` element.

The `<audio>` element is utilized to embed sound content within the HTML document. The `<canvas>` element is employed for drawing graphics, animations, or other visual content on a web page.

We need a condition to display the form `input` tag and the audio player. If the user has uploaded a file, we show the audio player. Otherwise, we show the file `input` form.

### Initialization and State Setup for the Audio Player

We need to establish the initial conditions and manage the state required for the functionality of an audio player. This involves tasks like initializing the audio player and setting up state variables to track the audio file.

```javascript
const [files, setFiles] = useState(null); // to store the audio file
const [uploaded, setUploaded] = useState(false); // this constraint is to determine if the audio file is available
const canvasRef = useRef(null); // to target the canvas 
const audioRef = useRef(null); // store the audio data
const source = useRef(null);
const analyserRef = useRef(null);
```
Since `useRef` hooks create references to essential elements and nodes, we utilize them in manipulating our app.

`canvasRef`: This `useRef` hook creates a reference to a canvas element. It allows us to access and manipulate the `<canvas>` element's properties and methods within the React.js component. In this case, it's used to draw the visualizations of the audio data on the canvas.

`audioRef`: Similarly, this `useRef` hook creates a reference to an audio element. It enables us to access and control the audio playback, such as playing, pausing, or seeking, within the React component. In this case, it's used to play the audio file uploaded by the user.

`source`: This `useRef` hook creates a reference to a Web Audio API source node. In Web Audio API usage, a `source` node represents the audio source, such as an `<audio>` element or other audio sources. This reference may be used to connect the audio source to an audio context and perform operations like analyzing audio data.

`analyserRef`: This `useRef` hook creates a reference to an `AnalyserNode`, which is part of the Web Audio API. `AnalyserNode` is used for analyzing the audio data in real-time, such as extracting frequency or time-domain data. By creating a reference to the `AnalyserNode`, we can access and manipulate its properties and methods within the React component.

To hide the audio player when no audio file is selected, we use the `setUploaded` function.

```jsx
{!uploaded && (
  <div class="h-screen font-sans text-gray-900 bg-gray-300 border-box">
    <div class="flex justify-center w-full mx-auto sm:max-w-lg">
      <div class="flex flex-col items-center justify-center w-full h-auto my-20 bg-white sm:w-3/4 sm:rounded-lg sm:shadow-xl">
        <div class="mt-10 mb-10 text-center">
          <h2 class="text-2xl font-semibold mb-2">Upload your files</h2>
          <p class="text-xs text-gray-500">
            File should be of format .mp4, .avi, .mov or .mkv
          </p>
        </div>
        <form
          action="#"
          class="relative w-4/5 h-32 max-w-xs mb-10 bg-white bg-gray-100 rounded-lg shadow-inner"
        >
          <input
            type="file"
            id="file-upload"
            class="hidden"
            onChange={handleChange}
          />
          <label
            for="file-upload"
            class="z-20 flex flex-col-reverse items-center justify-center w-full h-full cursor-pointer"
          >
            <p class="z-10 text-xs font-light text-center text-gray-500">
              Upload your music here
            </p>
            <svg
              class="z-10 w-8 h-8 text-indigo-400"
              fill="currentColor"
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
            </svg>
          </label>
        </form>
      </div>
    </div>
  </div>
)}
```

In the `input` tag, we set an `onChange` attribute that listens for changes in the form. We pass a function called `handleChange` to handle this event.

Next, we create a `handleChange` function that handles the uploaded audio file.

```javascript
const handleChange = (e) => {
  setFiles(e.target.files[0]);
  setUploaded(true);
};
```

Now that the audio file is being saved with `setFiles`, we can include a condition that enables the audio player to start playing when a file is present.

```jsx
{/* check if the file is uploaded successfully */}
{files && (
    <main className="flex min-h-screen w-full items-center justify-center">
        <article className="group relative flex h-[12rem] w-[50rem] overflow-hidden rounded-2xl bg-[#3a4448]">
            <div className="absolute inset-y-0 left-0 w-48">
                <img
                    src="https://unsplash.it/id/1/640/425"
                    alt=""
                    className="h-full w-full object-cover object-center opacity-95"
                />

                <div className="invisible absolute inset-0 flex h-full w-full items-center justify-center bg-[#0c0c0c]/70 opacity-0 transition-all group-hover:visible group-hover:opacity-100"></div>
            </div>
            <div className="absolute inset-y-0 left-44 w-[39rem] overflow-hidden rounded-2xl transition-all group-hover:w-[36rem]">
                <div className="h-full w-full bg-cover bg-center">
                    <img src="https://unsplash.it/id/1/640/425" alt="" srcset="" />
                </div>
                <div className="h-full w-full bg-[#455055]/80 transition-all group-hover:bg-[#31383b]/80">
                    {" "}
                </div>
            </div>

            <section className="absolute inset-0 flex flex-col justify-between p-4 text-white">
                <header className="space-y-1">
                    <div className="text-3xl font-medium">Audio Visualizer</div>
                </header>
                <div className="flex space-x-2">
                    <div className="flex items-center space-x-1">

                        <div>
                            <audio ref={audioRef} src={window.URL.createObjectURL(files)} />
                            <button onClick={handleAudioPlay}>
                                <svg
                                    className="h-w-14 w-14 cursor-pointer text-white transition-all hover:text-yellow-400"
                                    fill="currentColor"
                                    viewBox="0 0 20 20"
                                    xmlns="http://www.w3.org/2000/svg"
                                >
                                    <path
                                        fillRule="evenodd"
                                        d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
                                        clip-rule="evenodd"
                                    ></path>
                                </svg>
                            </button>
                        </div>
                        <canvas ref={canvasRef} width={500} height={200} />

                        <span className="h-5 w-2 rounded-full bg-red-500"></span>

                        <span className="h-5 w-2 rounded-full bg-green-500"></span>
                        <span className="h-5 w-2 rounded-full bg-yellow-500"></span>
                    </div>
                </div>
            </section>
        </article>
    </main>
)}
```

From the code below, we passed the `canvasRef` to the `canvas` tag and `audioRef` to the `audio` element. The `window.URL.createObjectURL` generates a URL that can be used to reference the content of the selected file (named `files`) within the browser.

We created a button and passed a `handleAudioPlay` function, which plays the audio. 

Next, we create the `handleAudioPlay` function. 

```javascript
const handleAudioPlay = () => {
  const audioElement = audioRef.current;
  if (audioElement && audioElement.readyState >= 2) {
    // Check if the audio element is loaded and ready to play
    audioElement.play();
    const audioContext = new AudioContext();
    if (!source.current) {
      source.current = audioContext.createMediaElementSource(audioElement);
      const analyser = audioContext.createAnalyser();
      analyserRef.current = analyser;
      source.current.connect(analyser);
      analyser.connect(audioContext.destination);
    }
  }
};
```

## Visualization of the Audio Data

We create a `visualizeAudio` function responsible for rendering the audio data visualization on the `<canvas>` element and then pass it to the `handleAudioPlay`.

```javascript
function visualizeAudio() {
  const canvasContext = canvasRef.current.getContext("2d");

  const renderFrame = () => {
    const frequencyData = new Uint8Array(analyserRef.current.frequencyBinCount);
    analyserRef.current.getByteFrequencyData(frequencyData);

    const barWidth = 5; // bar width
    let startX = 0;

    canvasContext.clearRect(
      0,
      0,
      canvasRef.current.width,
      canvasRef.current.height
    );

    for (let i = 0; i < frequencyData.length; i++) {
      startX = i * 8;

      const gradient = canvasContext.createLinearGradient(
        0,
        0,
        canvasRef.current.width,
        canvasRef.current.height
      );
      gradient.addColorStop(0.2, "#ff0000"); // Red
      gradient.addColorStop(0.5, "#00ff00"); // Green
      gradient.addColorStop(1.0, "#0000ff"); // Blue

      canvasContext.fillStyle = gradient;
      canvasContext.fillRect(
        startX,
        canvasRef.current.height,
        barWidth,
        -frequencyData[i] // bar height
      );
    }

    requestAnimationFrame(renderFrame); // Call renderFrame recursively
  };

  renderFrame(); // Start the rendering loop
}
```

We first retrieve the 2D rendering context of the canvas using the `getContext()`, which enables drawing shapes, text, and images onto the canvas. This method allows us to create dynamic visualizations based on the audio data. Within the `renderFrame` function, the core visualization logic unfolds. It continuously updates the visualization based on the audio data in real-time using the `requestAnimationFrame`. Within the loop, bars are drawn on the canvas to represent the intensity of each frequency band. Each bar's height corresponds to the frequency's intensity, creating a visual representation of the audio's spectral content. Before rendering each new frame, the canvas is cleared to remove any remnants of the previous visualization using the` clearRect()`. This ensures that the visualization remains clean and does not overlap or distort over time.

This code is then passed to the `handleAudioPlay` function as `visualizeAudio`.

```javascript
const handleAudioPlay = () => {
  const audioElement = audioRef.current;
  if (audioElement && audioElement.readyState >= 2) {
    // Check if the audio element is loaded and ready to play
    audioElement.play();
    const audioContext = new AudioContext();
    if (!source.current) {
      source.current = audioContext.createMediaElementSource(audioElement);
      const analyser = audioContext.createAnalyser();
      analyserRef.current = analyser;
      source.current.connect(analyser);
      analyser.connect(audioContext.destination);
    }
    visualizeAudio(); // visualize audio
  }
};
```

Upon launching our application at http://localhost:3000/, the project becomes visible. When an audio file is uploaded, the input form disappears and is replaced by an audio player. The visualizer emerges upon clicking the play button.


![Audio Visualizer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okcjppb9zgqy6y1itg6u.gif)

## Conclusion

In conclusion, audio visualizers serve as captivating features that elevate website interactivity and user engagement. By synchronizing visual elements with audio playback, they offer users a visually immersive experience while enhancing their understanding and connection with audio content. We successfully created a basic audio visualizer with the assistance of the web audio API. The possibilities of the Web Audio API are vast, allowing for complex and engaging audio experiences tailored to specific projects.