Build a Music Visualizer

Animate real-time frequency bars synced to audio using the Web Audio API.

Introduction

30 Second Summary

Every music player, video game, and live concert screen has one thing in common: a visualizer that turns sound into movement. Those dancing bars and wavy lines are not magic. They are the result of reading audio data and drawing it on screen, frame by frame.

In this project, you will build a browser-based music visualizer that analyzes audio frequencies in real time and draws animated bars that react to whatever song you load.

What You'll Build

A single-file web page that lets you load any audio file, play or pause it, and watch colored frequency bars animate in sync with the music using the Web Audio API and Canvas 2D rendering.

By the end of this project, you'll have:

  • A working index.html file containing all HTML, CSS, and JavaScript in one place.
  • A file loader and play/pause control that lets you choose any audio file from your computer.
  • Real-time animated frequency bars drawn on a canvas element that change height and color based on what the music is doing.
  • Secret Mission: Add a waveform oscilloscope view with a toggle to switch between visualizations.

Are there any prerequisites?

None beyond basic computer use. All coding concepts are introduced from scratch. You just need VS Code installed and a browser (Chrome or Edge).

Set Up Your Project

Before you can visualize audio, you need a workspace to build in. Every web project starts with a folder to hold your files and an HTML file to load in the browser.

In this step, you will create a project folder, grab a sample audio file for testing, and set up a starter HTML page. By the end, you will have everything ready to start building.

In this step, get ready to:
  • Create a project folder and open it in VS Code.
  • Download a sample MP3 file for testing.
  • Create a starter HTML file and confirm it opens in your browser.
Create your project folder and open it in VS Code

You need a dedicated folder on your Desktop to keep all project files together. You will create this folder and open it in VS Code so you can start editing.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • In the dialog that opens, navigate to your Desktop.
  • Click the New Folder button at the top of the dialog.
  • Type music-visualizer as the folder name, then press Enter.
  • With the new music-visualizer folder selected, click Select Folder (Windows) or Open (macOS).

VS Code reloads with the music-visualizer folder open. You should see the folder name in the Explorer sidebar on the left.

Don't see the Explorer sidebar?

If the sidebar is hidden, press Cmd+Shift+E (macOS) or Ctrl+Shift+E (Windows) to toggle it open.

help me find the Explorer sidebar in VS Code

Download a sample audio file

Your visualizer needs an audio file to analyze. You will download a free sample MP3 to use for testing throughout this project.

  • Open your web browser and go to file-examples.com/index.php/sample-audio-files/.
  • Download any MP3 file from the list. The larger file sizes work best for a richer visualization.
  • Save or move the downloaded MP3 file into the music-visualizer folder on your Desktop.

After saving, check VS Code's Explorer sidebar. Your MP3 file should appear in the file list.

Use any MP3 you already have

If you already have an MP3 file on your computer, you can skip the download and just copy it into the music-visualizer folder instead.

File not showing in VS Code?

Make sure you saved the MP3 directly into the music-visualizer folder on your Desktop, not into a subfolder or your Downloads folder. If you saved it elsewhere, drag it from your Downloads folder into the music-visualizer folder.

help me get my MP3 file to show up in VS Code

Create index.html and open it in your browser

Every web page starts with an HTML file. VS Code has a built-in shortcut called Emmet that generates a starter HTML skeleton for you in one keystroke.

  • In VS Code's Explorer sidebar, hover over the MUSIC-VISUALIZER folder name.
  • Click the New File icon (the page icon with a plus sign).
  • Type index.html and press Enter.
  • In the empty file that opens, type ! (just an exclamation mark) and then press Tab.

Emmet expands the shortcut into a full HTML skeleton. Your file should now contain a complete starter structure with a head and body section.

What is Emmet?

Emmet is a shortcut system built into VS Code that expands short abbreviations into full code. Typing ! followed by Tab tells Emmet to generate a standard HTML5 page structure.

This saves you from manually typing out the boilerplate tags every time you start a new page.

  • Save the file by pressing Ctrl+S (Windows) or Cmd+S (macOS).

Now open the file in your browser to confirm it works.

  • Open File Explorer (Windows) or Finder (macOS) and navigate to the music-visualizer folder on your Desktop.
  • Right-click index.html and choose Open with > Google Chrome or Microsoft Edge.

You should see a completely blank white page in your browser. This is correct. The HTML skeleton has no visible content yet.

Seeing code text instead of a blank page?

If your browser displays the raw HTML tags as text, make sure the file is named index.html with the .html extension. Without the extension, the browser treats it as plain text instead of a web page.

help me open my HTML file correctly in the browser

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html and your project is ready for the next step.

ⓧ I'd like to double check the full code

Your index.html should look exactly like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Your workspace is ready. Next up, you will build out the page layout with a title, controls, and a canvas for the visualizer to draw on.

Build the Page Layout

Your project folder is ready and your blank HTML file opens in the browser. Now it's time to turn that empty page into the interface for your music visualizer.

A music visualizer needs a few key pieces: a way to load an audio file, a button to play and pause it, and a drawing surface where the visualization will appear. In this step, you'll build all of those elements and style them with a dark background that makes the colorful frequency bars pop.

In this step, get ready to:
  • Add HTML elements for file loading, playback controls, and a canvas.
  • Style the page with a dark theme and centered layout.
  • Verify the layout displays correctly in your browser.
Add the page structure

Your visualizer page needs four elements: a title, a file picker to load audio, a Play button to control playback, and a canvas element where the visualization will be drawn.

  • In VS Code, open index.html from the Explorer sidebar on the left.
  • Select all the existing content by pressing Ctrl+A (Windows) or Cmd+A (macOS).
  • Delete the selected text, then paste the following HTML structure:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Music Visualizer</title>
</head>
<body>
    <!-- Page title -->
    <h1>Music Visualizer</h1>

    <!-- Controls: file picker and play button -->
    <div id="controls">
        <input type="file" id="file-input" accept="audio/*">
        <button id="play-btn" disabled>Play</button>
    </div>

    <!-- Canvas where the visualization will be drawn -->
    <canvas id="visualizer" width="800" height="400"></canvas>

    <!-- Hidden audio element that plays the loaded file -->
    <audio id="audio-player"></audio>
</body>
</html>

What does this HTML do?

  • <input type="file" accept="audio/*"> creates a file picker that only accepts audio files (MP3, WAV, OGG, etc.).
  • <button disabled> creates a button that starts grayed out. You'll enable it with JavaScript once a file is loaded.
  • <canvas width="800" height="400"> is your drawing surface. The frequency bars will be drawn here every frame.
  • <audio> is an invisible audio player. JavaScript will route its audio through the Web Audio API for analysis.
  • Save index.html by pressing Ctrl+S (Windows) or Cmd+S (macOS).
  • Refresh your browser. You should see the heading "Music Visualizer", a file chooser, and a grayed-out Play button. The canvas is invisible for now because it has no border or background yet.

Don't see the heading or button?

  • Make sure you saved the file. Look for the white dot on the index.html tab in VS Code. If it's there, the file is unsaved.
  • Check that you replaced ALL the old content. There shouldn't be two <!DOCTYPE html> lines or duplicate <html> tags.
  • My page is blank or has broken elements.
Add CSS styling

The page works, but it looks plain. A dark background will make your colorful visualization bars stand out, and centering everything will keep the layout clean.

  • In index.html, find the <title>Music Visualizer</title> line in the head section.
  • Directly below that line, add this entire <style> block:
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background-color: #1a1a2e;
            color: #ffffff;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        h1 {
            margin-bottom: 20px;
            font-size: 24px;
        }

        #controls {
            margin-bottom: 20px;
            display: flex;
            gap: 12px;
            align-items: center;
        }

        #file-input {
            color: #ffffff;
        }

        #play-btn {
            padding: 10px 24px;
            font-size: 16px;
            background-color: #e94560;
            color: #ffffff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }

        #play-btn:hover {
            background-color: #ff6b6b;
        }

        #play-btn:disabled {
            background-color: #555555;
            cursor: not-allowed;
        }

        canvas {
            border: 2px solid #333333;
            border-radius: 8px;
        }
    </style>

What do these styles do?

  • The * reset removes default browser margins and padding so you start with a clean slate.
  • Flexbox on body centers everything vertically and horizontally on the page.
  • The dark background (#1a1a2e) creates contrast so the bright visualization bars will pop.
  • The Play button has three states: a red active state, a brighter hover state, and a gray disabled state for when no file is loaded.
  • Save index.html and refresh your browser.
  • You should see a dark page with white text, the title "Music Visualizer" centered at the top, a file chooser and a grayed-out Play button below it, and a dark rectangle with a subtle border at the bottom (that's your canvas).

Page still has a white background?

  • Make sure the <style> block is inside the <head> section (between <head> and </head>), not inside <body>.
  • Check that the opening <style> and closing </style> tags are both present. A missing closing tag will break all styles.
  • My styles aren't applying correctly.
Verify the full layout

Before moving on, confirm that every element is in place and styled correctly.

  • Hover your mouse over the Play button. It should stay gray because it's disabled (no file loaded yet).
  • Look for the canvas area below the controls. It appears as a dark rectangle with a thin border and rounded corners.

Why is the Play button disabled?

The disabled attribute prevents the learner from clicking Play before loading a file. In the next step, JavaScript will remove this attribute once an audio file is selected.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before continuing.

ⓧ I'd like to double check the full code

Here's the complete index.html file as it should look right now:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Music Visualizer</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background-color: #1a1a2e;
            color: #ffffff;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        h1 {
            margin-bottom: 20px;
            font-size: 24px;
        }

        #controls {
            margin-bottom: 20px;
            display: flex;
            gap: 12px;
            align-items: center;
        }

        #file-input {
            color: #ffffff;
        }

        #play-btn {
            padding: 10px 24px;
            font-size: 16px;
            background-color: #e94560;
            color: #ffffff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }

        #play-btn:hover {
            background-color: #ff6b6b;
        }

        #play-btn:disabled {
            background-color: #555555;
            cursor: not-allowed;
        }

        canvas {
            border: 2px solid #333333;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>Music Visualizer</h1>
    <div id="controls">
        <input type="file" id="file-input" accept="audio/*">
        <button id="play-btn" disabled>Play</button>
    </div>
    <canvas id="visualizer" width="800" height="400"></canvas>
    <audio id="audio-player"></audio>
</body>
</html>

Your page layout is complete. The interface is ready for users to load audio and control playback. Next up, you'll connect the Web Audio API so the browser can actually analyze the audio frequencies in real time.

Connect the Web Audio API

Your page has all the visual elements in place, but nothing happens when you interact with them. In this step, you'll wire up the Web Audio API so that selecting an audio file and clicking Play actually produces sound.

The Web Audio API works like a signal chain. You route audio from a source through processing nodes (like an analyser) to a destination (your speakers). Setting up this chain is what makes real-time frequency analysis possible in the next step.

In this step, get ready to:
  • Grab references to all the HTML elements your JavaScript needs.
  • Handle file selection so the audio element knows what to play.
  • Wire up the Play button to create an audio processing chain and toggle playback.
Add a script tag and grab DOM references

Before your JavaScript can interact with the page, it needs references to the HTML elements you created in the previous step. You also need to declare variables that will hold the audio processing objects once the user clicks Play.

  • In index.html, find the closing </body> tag.
  • Add an empty <script></script> block directly above </body> (below the <audio> element).
  • Inside the script tag, add the DOM references and variable declarations by pasting this code:
const fileInput = document.getElementById("file-input");
const playBtn = document.getElementById("play-btn");
const audio = document.getElementById("audio-player");
const canvas = document.getElementById("visualizer");
const canvasCtx = canvas.getContext("2d");

// Audio processing variables (filled when user clicks Play)
let audioCtx;
let analyser;
let source;
let dataArray;
let isPlaying = false;

What does this code do?

The first five lines grab references to the HTML elements you built in the previous step. canvasCtx gets the 2D drawing context from the canvas, which you'll use to render graphics later.

The let variables are intentionally left empty. They'll hold the AudioContext, AnalyserNode, and other audio objects once the user clicks Play for the first time.

  • Save index.html.
  • You won't see a change in the browser yet. The next chunk uses these references to respond to user actions.
Handle file selection

When a user picks an audio file, you need to create a temporary URL for it and point the hidden audio element at that URL. This avoids CORS restrictions that would block loading a remote file.

  • In index.html, add the following code below the let isPlaying = false; line inside your script tag:
fileInput.addEventListener("change", function(event) {
    // Get the first file the user selected
    const file = event.target.files[0];
    if (file) {
        // Create a temporary URL pointing to the file on disk
        const url = URL.createObjectURL(file);
        audio.src = url;
        // Enable the Play button now that we have audio loaded
        playBtn.disabled = false;
    }
});

What does this code do?

When the user picks a file, the browser fires a change event. Your listener grabs the selected file from event.target.files[0].

URL.createObjectURL() creates a temporary in-memory URL that points directly to the file on the user's machine. This lets the <audio> element load local files without any server or network requests.

  • Save index.html and refresh your browser.
  • Click **Choose File** and select your sample MP3. The Play button should change from gray (disabled) to red (enabled).

Play button still gray after selecting a file?

  • Make sure you saved index.html after adding the code.
  • Check that the addEventListener block is inside the <script> tag, not outside it.
  • Open DevTools with **F12** and check the **Console** tab for any red error messages.

Help me debug why my file input isn't enabling the Play button.

Wire up the Play button

This is the core of the audio setup. On the first click, the code creates an AudioContext and builds the audio routing chain. On every click after that, it toggles between play and pause.

  • Add the Play button event listener below the file input listener you just wrote:
playBtn.addEventListener("click", function() {
    // First click: set up the audio processing chain
    if (!audioCtx) {
        audioCtx = new AudioContext();
        analyser = audioCtx.createAnalyser();
        // 256 gives us 128 frequency bars (fftSize / 2)
        analyser.fftSize = 256;
        // Route the audio element through the analyser to the speakers
        source = audioCtx.createMediaElementSource(audio);
        source.connect(analyser);
        analyser.connect(audioCtx.destination);
        // Create an array to hold frequency data each frame
        dataArray = new Uint8Array(analyser.frequencyBinCount);
        draw();
    }

    // Toggle play/pause
    if (isPlaying) {
        audio.pause();
        playBtn.textContent = "Play";
    } else {
        // resume() handles browser autoplay restrictions
        audioCtx.resume();
        audio.play();
        playBtn.textContent = "Pause";
    }
    isPlaying = !isPlaying;
});

What does this code do?

The if (!audioCtx) block only runs once. It creates the audio processing chain: source (your audio file) flows through the AnalyserNode (which reads frequency data) and then out to audioCtx.destination (your speakers).

Setting fftSize to 256 means frequencyBinCount equals 128. That gives you 128 data points to work with when drawing bars in the next step.

The audioCtx.resume() call is required because modern browsers block audio from playing automatically. Since the user clicked a button, the browser trusts the request and allows audio to start.

Why create the AudioContext inside a click handler?

Browsers enforce an autoplay policy that blocks audio unless a user gesture (like a click) triggers it. Creating the AudioContext inside the click handler satisfies this requirement automatically.

If you created it outside the handler, the context would start in a suspended state and might not resume reliably.

You'll see a console error on first click

The code calls draw() at the end of the setup block, but you haven't written the draw() function yet. Your browser console will show a ReferenceError: draw is not defined message.

This is expected. The audio routing is already set up by the time the error occurs, so clicking Play a second time will start playback successfully. You'll fix this in the next step when you write the draw() function.

  • Save index.html and refresh your browser.
  • Select your MP3 file using the file picker.
  • Click **Play** once (this sets up the audio chain and triggers the console error).
  • Click **Play** again. You should hear your audio playing through your speakers, and the button text changes to "Pause".
  • Click **Pause** to stop playback. The button text changes back to "Play".

No sound when you click Play?

  • Check that your computer's volume is turned up and not muted.
  • Make sure you clicked Play twice at this stage (the first click triggers the setup error, the second click starts playback).
  • Open DevTools (**F12**) and check the **Console** tab. If you see errors other than the expected draw is not defined, check your code for typos in the element IDs.

Help me debug why my audio isn't playing in my Web Audio API music visualizer.

✔️ Awesome, I've got everything!

Great. Double check you've saved index.html before moving on.

ⓧ I'd like to double check the full code

Here is the complete index.html file at this point:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Music Visualizer</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background-color: #1a1a2e;
            color: #ffffff;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        h1 {
            margin-bottom: 20px;
            font-size: 24px;
        }

        #controls {
            margin-bottom: 20px;
            display: flex;
            gap: 12px;
            align-items: center;
        }

        #file-input {
            color: #ffffff;
        }

        #play-btn {
            padding: 10px 24px;
            font-size: 16px;
            background-color: #e94560;
            color: #ffffff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }

        #play-btn:hover {
            background-color: #ff6b6b;
        }

        #play-btn:disabled {
            background-color: #555555;
            cursor: not-allowed;
        }

        canvas {
            border: 2px solid #333333;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>Music Visualizer</h1>
    <div id="controls">
        <input type="file" id="file-input" accept="audio/*">
        <button id="play-btn" disabled>Play</button>
    </div>
    <canvas id="visualizer" width="800" height="400"></canvas>
    <audio id="audio-player"></audio>

    <script>
        const fileInput = document.getElementById("file-input");
        const playBtn = document.getElementById("play-btn");
        const audio = document.getElementById("audio-player");
        const canvas = document.getElementById("visualizer");
        const canvasCtx = canvas.getContext("2d");

        let audioCtx;
        let analyser;
        let source;
        let dataArray;
        let isPlaying = false;

        fileInput.addEventListener("change", function(event) {
            const file = event.target.files[0];
            if (file) {
                const url = URL.createObjectURL(file);
                audio.src = url;
                playBtn.disabled = false;
            }
        });

        playBtn.addEventListener("click", function() {
            if (!audioCtx) {
                audioCtx = new AudioContext();
                analyser = audioCtx.createAnalyser();
                analyser.fftSize = 256;
                source = audioCtx.createMediaElementSource(audio);
                source.connect(analyser);
                analyser.connect(audioCtx.destination);
                dataArray = new Uint8Array(analyser.frequencyBinCount);
                draw();
            }

            if (isPlaying) {
                audio.pause();
                playBtn.textContent = "Play";
            } else {
                audioCtx.resume();
                audio.play();
                playBtn.textContent = "Pause";
            }
            isPlaying = !isPlaying;
        });
    </script>
</body>
</html>

Your audio chain is connected and playing sound. Next up, you'll write the draw() function that reads frequency data from the analyser and renders animated bars on the canvas.

Draw the Frequency Bars

Your audio is playing, but you can't see it yet. The AnalyserNode you connected in the last step is already capturing frequency data behind the scenes. Now you need a way to read that data and turn it into something visual.

In this step, you will write a draw() function that runs 60 times per second, reads the current frequency values, and paints colored bars on the canvas. Each bar represents a frequency band, and its height shows how loud that frequency is right now.

In this step, get ready to:
  • Write a continuous animation loop that reads frequency data every frame.
  • Draw colored bars on the canvas that react to the audio in real time.
  • Test the visualizer with your sample MP3 file.
Write the draw function

The draw() function does three things on every frame: reads the latest frequency data from the analyser, clears the canvas, and draws a bar for each frequency value. It calls itself in a loop using requestAnimationFrame, which tells the browser to run the function again before the next screen repaint.

  • In index.html, scroll to the bottom of your <script> tag. Find the closing }); of the play button event listener.
  • Add the following draw() function below it, before the closing </script> tag:
function draw() {
    // Schedule the next frame to create a continuous loop
    requestAnimationFrame(draw);

    // Fill dataArray with current frequency values (0-255)
    analyser.getByteFrequencyData(dataArray);

    // Clear the canvas with the dark background color
    canvasCtx.fillStyle = "rgb(26, 26, 46)";
    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

    // Calculate bar dimensions based on how many frequency bins exist
    const bufferLength = analyser.frequencyBinCount;
    const barWidth = (canvas.width / bufferLength) * 2.5;
    let x = 0;

    // Draw one bar for each frequency value
    for (let i = 0; i < bufferLength; i++) {
        const barHeight = dataArray[i];

        // Shift color based on bar height (louder = more red)
        const red = barHeight + 50;
        const green = 50;
        const blue = 150;
        canvasCtx.fillStyle = "rgb(" + red + ", " + green + ", " + blue + ")";
        canvasCtx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight / 2);

        x += barWidth + 1;
    }
}

What does this code do?

This function creates a smooth animation by requesting the browser to call it again before each screen repaint. That gives you roughly 60 frames per second.

  • analyser.getByteFrequencyData(dataArray) fills the array with the current frequency intensity values. Each value is a number from 0 (silent) to 255 (loudest).
  • The canvas is cleared every frame with the page's dark background color, so old bars don't pile up.
  • barWidth uses a multiplier of 2.5 because most higher frequencies return zero audio. Without the multiplier, you would see mostly empty space.
  • The color shifts from dark purple (quiet) toward red (loud) based on each bar's height. This makes bass-heavy hits visually pop.

Why does draw() already get called in the play button?

In the previous step, you added draw(); inside the if (!audioCtx) block. That single call kicks off the animation loop the first time the learner clicks Play. Once draw() runs, it schedules itself forever via requestAnimationFrame(draw).

  • Save index.html.
  • Open the file in your browser (or refresh if it's already open).
  • Click the file chooser and select your sample MP3.
  • Click Play. You should see colored bars animating in real time across the canvas. Bass-heavy parts of the song produce taller bars on the left side.
  • Click Pause. The bars should drop to zero and the canvas goes dark again.

Bars not showing up?

  • Make sure you placed the draw() function AFTER the play button listener's closing }); but before the </script> tag.
  • Open your browser's Developer Tools (press F12) and check the Console tab for red error messages. A typo in a variable name like canvasCtx or dataArray will show up there.
  • Confirm you loaded an audio file first and clicked Play. The bars only appear once audio is actively playing.

Why aren't my frequency bars showing on the canvas?

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete index.html file at this point. Compare yours against it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Music Visualizer</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background-color: #1a1a2e;
            color: #ffffff;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        h1 {
            margin-bottom: 20px;
            font-size: 24px;
        }

        #controls {
            margin-bottom: 20px;
            display: flex;
            gap: 12px;
            align-items: center;
        }

        #file-input {
            color: #ffffff;
        }

        #play-btn {
            padding: 10px 24px;
            font-size: 16px;
            background-color: #e94560;
            color: #ffffff;
            border: none;
            border-radius: 6px;
            cursor: pointer;
        }

        #play-btn:hover {
            background-color: #ff6b6b;
        }

        #play-btn:disabled {
            background-color: #555555;
            cursor: not-allowed;
        }

        canvas {
            border: 2px solid #333333;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>Music Visualizer</h1>
    <div id="controls">
        <input type="file" id="file-input" accept="audio/*">
        <button id="play-btn" disabled>Play</button>
    </div>
    <canvas id="visualizer" width="800" height="400"></canvas>
    <audio id="audio-player"></audio>

    <script>
        const fileInput = document.getElementById("file-input");
        const playBtn = document.getElementById("play-btn");
        const audio = document.getElementById("audio-player");
        const canvas = document.getElementById("visualizer");
        const canvasCtx = canvas.getContext("2d");

        let audioCtx;
        let analyser;
        let source;
        let dataArray;
        let isPlaying = false;

        fileInput.addEventListener("change", function(event) {
            const file = event.target.files[0];
            if (file) {
                const url = URL.createObjectURL(file);
                audio.src = url;
                playBtn.disabled = false;
            }
        });

        playBtn.addEventListener("click", function() {
            if (!audioCtx) {
                audioCtx = new AudioContext();
                analyser = audioCtx.createAnalyser();
                analyser.fftSize = 256;
                source = audioCtx.createMediaElementSource(audio);
                source.connect(analyser);
                analyser.connect(audioCtx.destination);
                dataArray = new Uint8Array(analyser.frequencyBinCount);
                draw();
            }

            if (isPlaying) {
                audio.pause();
                playBtn.textContent = "Play";
            } else {
                audioCtx.resume();
                audio.play();
                playBtn.textContent = "Pause";
            }
            isPlaying = !isPlaying;
        });

        function draw() {
            requestAnimationFrame(draw);

            analyser.getByteFrequencyData(dataArray);

            canvasCtx.fillStyle = "rgb(26, 26, 46)";
            canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

            const bufferLength = analyser.frequencyBinCount;
            const barWidth = (canvas.width / bufferLength) * 2.5;
            let x = 0;

            for (let i = 0; i < bufferLength; i++) {
                const barHeight = dataArray[i];

                const red = barHeight + 50;
                const green = 50;
                const blue = 150;
                canvasCtx.fillStyle = "rgb(" + red + ", " + green + ", " + blue + ")";
                canvasCtx.fillRect(x, canvas.height - barHeight / 2, barWidth, barHeight / 2);

                x += barWidth + 1;
            }
        }
    </script>
</body>
</html>

Your visualizer is alive. Those bars are reading real audio data and painting it 60 times per second. Next up, take on the Secret Mission and add a waveform oscilloscope view that shows your audio as a wavy line instead of bars.

Secret mission

Add a Waveform Oscilloscope Toggle

Your frequency bars show which frequencies are loud at any moment. But what if you could also see the raw shape of the audio wave itself? In this secret mission, you'll add a second visualization mode and a button to switch between them.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for future use or delete them entirely.

Resources you used:

  • The music-visualizer folder on your Desktop (contains index.html and your sample MP3 file).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your visualizer or show it to someone.

  • Your music-visualizer folder stays on your Desktop. Double-click index.html anytime to open it in your browser.
  • You can share the single index.html file with anyone. They just need to open it in a browser to use it.

Pause - I'll come back to this later

Close VS Code to free up memory. Your files stay safe on disk.

  • Close VS Code by pressing Cmd+Q (macOS) or Alt+F4 (Windows).
  • Your music-visualizer folder remains on your Desktop. Reopen it in VS Code whenever you want to continue.

Delete - I don't want to use this again

Remove all project files from your machine.

  • Close VS Code.
  • Delete the music-visualizer folder from your Desktop. On Windows, right-click the folder and select Delete. On macOS, drag the folder to the Trash.
  • Empty your Trash or Recycle Bin to free up disk space.

That's a Wrap!

That's a Wrap!

Nice work! You just built a real-time music visualizer from scratch using nothing but HTML, CSS, and JavaScript.

You've learned how to:

  • Route audio through the Web Audio API using the node-graph pattern (source > analyser > destination) that mirrors professional game audio engines.
  • Convert live audio into frequency data using an AnalyserNode and render it as animated bars on a Canvas 2D element.
  • Use requestAnimationFrame to create smooth 60fps animation loops that stay in sync with audio playback.
  • Secret Mission: Added a waveform oscilloscope mode with a toggle button, demonstrating time-domain audio data alongside frequency analysis.

Ready to quiz yourself?