Basic JavaScript spectrum analyzer
Create a basic, fundamental, spectrum analyzer using only vanilla JavaScript.
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.
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
- create a new
AudioContextinstance - create a new analyzer instance using
AudioContext.createAnalyser() - create the data array using the
frequencyBinCountproperty of the analyzer instance
Create & connect to the audio source
- obtain the stream of the device’s audio input (microphone) using the browser’s
getUserMediamethod - connect the source to the stream using
AudioContextinstance’screateMediaStreamSource - 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.
Happy listening, and happy coding!





