DEV Community

Tiger
Tiger

Posted on

Building a Frictionless Vocal Range Test: A Deep Dive into Web Audio API and Astro

Vocal Range Test

I’m not a professional singer. My vocal performances are strictly limited to the shower. But I’ve always been curious about my actual vocal range.

Whenever I looked online to test it, I hit the same barriers: sketchy mobile apps with forced sign-ups, or dry music theory articles without any interactive elements. I just wanted a clean, fast web tool to find my lowest and highest notes. So, I decided to build one: Vocal Range Test.

What started as a weekend project quickly turned into a deep rabbit hole of Web Audio APIs, music theory math, and audio signal processing. Here is a breakdown of the tech stack, the challenges, and how I built it.

Vocal Range test

The Stack: Astro + React + Tailwind

For a tool like this, friction is the enemy. If the page takes 3 seconds to load, the user is gone. That’s why I chose Astro for the core framework. It allows me to ship a nearly zero-JS static shell, while hydrating only the interactive audio components using React 19.

For styling, Tailwind CSS v4 kept the UI clean and responsive. When rendering dynamic graphics (like the vocal range result cards), I used html-to-image to let users download and share their results natively without needing a backend.

The Audio Engine: Tone.js and Tonal

I needed to generate precise, latency-free reference notes for users to match. Instead of hosting hundreds of MP3 files, I used Tone.js. It wraps the Web Audio API beautifully, allowing me to build a custom synthesizer that produces clean piano-like tones on the fly.

import * as Tone from 'tone';
import { Note } from 'tonal';

// Initialize a basic synth
const synth = new Tone.Synth({
  oscillator: { type: 'triangle' },
  envelope: { attack: 0.05, decay: 0.1, sustain: 0.3, release: 1 }
}).toDestination();

// Play a note using Tonal to handle the frequency math
const playReferenceNote = async (noteName) => {
  await Tone.start(); // Resume audio context
  const freq = Note.freq(noteName); 
  synth.triggerAttackRelease(freq, "1n");
};
Enter fullscreen mode Exit fullscreen mode

But Tone.js is only half the story. To actually calculate the user's vocal range, I relied heavily on Tonal. It’s an incredible music theory library that handles all the heavy lifting: converting between note names (C4, E3), calculating semitone distances, and figuring out the user's specific vocal type (Tenor, Soprano, Alto, etc.) based on their lowest and highest inputs.

Advanced Audio Analysis: Meyda and Essentia.js

This is where things got complicated. My initial idea was to just use the microphone and automatically detect the user's pitch.

For this, I integrated Meyda, a fantastic audio feature extraction library. Meyda allows you to calculate the RMS (volume), spectral centroid, and pitch chroma in real-time from the mic stream. I also experimented with Essentia.js, which is an absolute powerhouse for audio analysis (bringing C++ level audio processing to the browser via WebAssembly).

However, I quickly ran into the classic Web Audio problem: Background Noise and Room Acoustics.

If a user is singing into a cheap laptop mic in a noisy room, the pitch detection algorithms can easily get confused by octave errors or background hum. I realized that forcing a real-time mic analysis was leading to a frustrating UX.

So, I pivoted. I built a "Guided Audio" flow. The app plays a note, the user listens and tries to match it, and then self-reports if it was comfortable or strained. Meyda and Essentia.js are still running in the background for my "Advanced Mode," but the core user journey is human-centric, not algorithm-centric.

algorithm

Visualizing the Music: AlphaTab

To make the experience educational, I didn't want to just show text. I wanted users to see the music. For rendering guitar tabs or sheet music representations of the scales they were testing, I used @coderline/alphatab. It takes music notation data and renders it beautifully to HTML5 canvas, making the theory side of vocal ranges much more accessible.

Why Build This?

Honestly, I built this to learn. But since launching it, I’ve received messages from voice coaches who use it as a quick warm-up tool, and from bedroom musicians who use it to find the perfect key for their next cover.

Combining a lightweight framework like Astro with powerful music libraries like Tone.js and Tonal means you can build genuinely useful music tools directly in the browser.

You can check out the tool here: Vocal Range Test.

If you’ve ever built audio tools for the web, I’d love to hear how you handled the microphone noise issues or real-time pitch detection in the comments!


Top comments (1)

Collapse
 
eason87 profile image
Tiger •

If you have any thoughts, feel free to share in the comments section!