How to Create an Audio Visualizer using Nextjs

How to Create an Audio Visualizer using Nextjs

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.

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.

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:

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.

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 below the form.

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). 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.

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.

{!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.

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.

{/* 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.

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.

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 theclearRect(). 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.

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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *