• HTML
  • JavaScript
  • CSS
  • Audio

HTML - Microphone Visualization

In this Article I will go over using HTML and JavaScript to create a simple Talking Avatar using the Mic and a two sprites.

Background

Since I am a twitch streamer, I though it would be fun to create an avatar that talks when I talk. I did not want to go full VTuber like avatar, but I do like making things in the browser so I though it would be fun to play with the Microphone and make a Avatar change when talking.

Using the navigator.mediaDevices.getUserMedia({ audio: true }) JavaScript API you can get permission to use the audio devices of the browser. Using the AudioContext API you can also get access to an Analyser of the AudioContext that can then be used to calculate the decibels currently being picked up by the mic.

Demo and Source

Here is a working demo, and below the demo is the source for the Demo.

(TODO: Create demo page and iframe into page)

HTML Source

<div class="avatar-container">
    <img
        id="avatar-sprite"
        style="width: initial"
        src="//images.ctfassets.net/7fbllf92k2sv/6sbbmvhK5qaI82fUcTDVpn/3228e7725bca14a3baddee90074eab3f/Idle.png"
    />
</div>
<button id="button">Start Listening</button>

JavaScript Source

window.onload = function () {
    // Below are some parameters you can tweak to customize your Avatar
    // This threshold is for how many decibels the mic should pickup to trigger the talking sprite
    const LOUD_VOLUME_THRESHOLD = 30;
    // How often we should check the Threshold
    const INTERVAL_TIME = 100;
    // These are the Idle and Talk Sprite we will use
    const idleSprite = "//images.ctfassets.net/7fbllf92k2sv/6sbbmvhK5qaI82fUcTDVpn/3228e7725bca14a3baddee90074eab3f/Idle.png";
    const talkSprite = "//images.ctfassets.net/7fbllf92k2sv/2nIKRhrau67Z1ze5F89ptC/56f37b0d12d69cae12609af7861112ea/Talk.png";

    // Grab our Avatar Sprite so we can easily update the src of the image to simulate talking!
    const avatarSprite = document.getElementById("avatar-sprite");

    // The logic we should run when the Mic is permission is allowed 
    const soundAllowed = function (stream) {
        // We create an AudioContext and feed it our stream we received from mediaDevice request.
        const audioContext = new AudioContext();
        const audioStream = audioContext.createMediaStreamSource(stream);
        const analyser = audioContext.createAnalyser();
        const fftSize = 1024;
        analyser.fftSize = fftSize;
        audioStream.connect(analyser);

        const bufferLength = analyser.frequencyBinCount;
        const frequencyArray = new Uint8Array(bufferLength);
        document.getElementById("button").innerHTML = "Listening...";

        // We create an interval that checks the decibels and updates the Avatar to our threshold. 
        setInterval(() => {
            analyser.getByteFrequencyData(frequencyArray);
            // Loop the frequencyArray adding all the values together to get our total decibels.
            let total = 0;
            for (let i = 0; i < 255; i++) {
                var x = frequencyArray[i];
                total += x * x;
            }
            const rms = Math.sqrt(total / bufferLength);
            let db = 20 * (Math.log(rms) / Math.log(10));
            db = Math.max(db, 0);

            if (db >= LOUD_VOLUME_THRESHOLD) {
                // We are being Loud, so make our Avatar Talk
                avatarSprite.src = talkSprite;
            } else {
                // We are quite, so Idle our Avatar
                avatarSprite.src = idleSprite;
            }
        }, INTERVAL_TIME);
    };

    const start = () => {
        // Here we use the navigator.mediaDevices API to get the UserMedia for Audio
        navigator.mediaDevices
            .getUserMedia({ audio: true })
            .then(soundAllowed)
            // If the user denies the Mic permission, we show an error message
            // Will also trigger in the case of any error.
            .catch((error) => {
                document.getElementById("button").innerHTML =
                    "You must allow your microphone.";

                // To help debugging we display the full 'error' in the console
                console.log(error);
            });
    };

    // Enable the button, in the case autostart is not found in the URL
    document.getElementById("button").onclick = function () {
        start();
    };

    // A little hook that will autostart listening if the URL contains 'autostart'
    if (window.location.toString().includes("autostart")) {
        start();
    }
};
Cody's logo image, it is an abstract of a black hole with a white Event Horizon.

Cody Merritt Anhorn

A Engineer with a passion for Platform Architecture and Tool Development.