Sitemap

Basic JavaScript spectrum analyzer

4 min readNov 13, 2024

--

Create a basic, fundamental, spectrum analyzer using only vanilla JavaScript.

Press enter or click to view image in full size
Photo by Jonas Jacobsson on Unsplash

Keywords: spectrum analyzer, JavaScript, AudioContext

Introduction

I have always been a big fan of music. Not just listening to songs, but also the art and science behind them. Ever since I started learning to play musical instruments almost four decades ago, I’m always been in fascinated to how our brain evolved to be emotionally susceptible to sound waves, frequencies, and harmonies. Music is beautiful math that flows through the air.

Once I got my hands on computers, music was on of the first things that I wanted to explore with this technology. I dwelled in various music applications, including players, visualizers, sequencers, and synthesizers. So it was only natural that I created Gamelan almost 15 years ago, & then joined the exciting music startup Ohdio a few years later.

In this article, I wanted to show you how to use one of modern web’s amazing API, the AudioContext. This API let’s us create wonderful things that involved sound on the web platform.

Press enter or click to view image in full size
Gamelan, one of my proudest moment

The code

In this article, I am going to show you how to create an audio spectrum analyzer using AudioContext. In the example app, we will use this API to get frequency data from our computer’s microphone stream.

We’ll start with some basic HTML for the controls and display. Then we will delve into the main JavaScript code.

Let’s start coding!

1 — Basic HTML

The HTML will serve two purposes:

  • display the spectrum analysis
  • control the input & output

For the display, we will use a basic canvas element on which we will use to draw bitmap-based graphics on the fly.

<canvas
id="canvas"
class="canvas"
width="640"
height="320"
></canvas>

We will use the id attribute to refer to this element from the script.

For the controls, we will need two buttons: 1) to start drawing the spectrum analysis on the canvas, and 2) to stop the drawing process

<button id="start_button">Mic on</button>
<button id="stop_button" disabled>Mic off</button>

Those are the main HTML elements we need. Now let’s move on the JavaScript side.

2 — Audio context

The process here in a nutshell:

Get Arie M. Prasetyo’s stories in your inbox

Join Medium for free to get updates from this writer.

Create the analyzer

  1. create a new AudioContext instance
  2. create a new analyzer instance using AudioContext.createAnalyser()
  3. create the data array using the frequencyBinCount property of the analyzer instance

Create & connect to the audio source

  1. obtain the stream of the device’s audio input (microphone) using the browser’s getUserMedia method
  2. connect the source to the stream using AudioContext instance’s createMediaStreamSource
  3. connect the source to the analyzer
ACTX = new AudioContext()

ANALYSER = ACTX.createAnalyser()

ANALYSER.fftSize = 2048

DATA = new Uint8Array(ANALYSER.frequencyBinCount)

const stream = await navigator.mediaDevices.getUserMedia({ audio: true })

SOURCE = ACTX.createMediaStreamSource(stream)

SOURCE.connect(ANALYSER)

4 — Draw the data

Now that we have a variable that contains frequency data, we can start building the analyzer display. For the simplest example, we will draw rectangles to display the frequencies on the canvas.

What happens here is that we will iterate through each frequency (the indices in the array variable DATA). This will represent the X-axis value, from 0 to 1023.

Then in each index [0…1023], we get the frequency amplitude value. This will represent the Y-axis value. The higher an amplitude, the higher the rectangle for a frequency (represented by the X-axis value).

Then we draw a rectangle for each frequency. Thus for each rectangle, x represents the frequency; y represents the frequency’s amplitude.

function draw() {
if (!isPlaying) return

CTX = canvasEl.getContext("2d")
CTX.clearRect(0, 0, canvasEl.width, canvasEl.height)

// fill DATA with the current frequency data
ANALYSER.getByteFrequencyData(DATA)

// iterate through each frequency
for (let i = 0; i < DATA.length; i++) {
// normalize the amplitude value
const value = DATA[i] / 1024

// get the height based on the amplitude value
const y = canvasEl.height - canvasEl.height * value

// draw white rectangles
CTX.fillStyle = `white`

// fillRect(x, y, w, h); x value is represented by i
CTX.fillRect(i, y, 2, 8)
}

// make a loop to continue drawing on the next frame
requestAnimationFrame(draw)
}

4 — Connecting the controls to the analyzer

Lastly, we simply connect the HTML controls to the analyzer and draw methods we’ve created.

// start button event listener
startBtn.addEventListener("click", async e => {
e.preventDefault()

startBtn.disabled = true
stopBtn.disabled = false

micOn()
})


// start button event listener
stopBtn.addEventListener("click", e => {
e.preventDefault()

startBtn.disabled = false
stopBtn.disabled = true

micOff()
})


/**
* Play the from mic
*/
async function micOn() {
isPlaying = true
draw()
}


/**
* Stop the audio from mic
*/
function micOff() {
isPlaying = false
CTX.clearRect(0, 0, canvasEl.width, canvasEl.height)
}

That’s it, we’re done. Good job!

Conclusion

There are so many ways we can continue building upon this example, which could serve as the basis for future developments. We can of course add a music player and connect it to the analyzer. We can modify the styles of the analyzer, using creative libraries like P5 or Three.js and create dynamic artworks in the process. Me personally, I’m thinking of connecting the analyzer data to WebGL shaders.

You can check out the live demo and (extended) source code on my CodePen here.

Press enter or click to view image in full size
Live demo on CodePen

Happy listening, and happy coding!

--

--