<div id="container">
    <div id="title">
        <div id="main-title">Audio reactive visualisation</div>
        <div id="sub-title">(with light and dark theme)</div>
    </div>
    <div id="message"></div>
    <div id="oscilloscope">
        <div id="overlay-msg">
            <div id="overlay-msg-text"></div>
        </div>
        <svg id="graph">
            <line id="line" x1="0" y1="50%" x2="100%" y2="50%" />
            <polyline id="wave" />
        </svg>
    </div>
    <div id="controls-container">
        <div id="controls">
            <button id="play-btn">
                <svg width="24" height="24">
                    <rect width="24" height="24" fill="none" rx="0" ry="0"/>
                    <path fill-rule="evenodd" clip-rule="evenodd" d="M7.54076 3.99049C6.87525 3.56266 6 4.0405 6 4.83167V19.1683C6 19.9595 6.87525 20.4373 7.54076 20.0095L18.6915 12.8412C19.3038 12.4475 19.3038 11.5525 18.6915 11.1588L7.54076 3.99049ZM7.96998 5.69395C7.6372 5.48047 7.2 5.71943 7.2 6.11479V17.8852C7.2 18.2806 7.6372 18.5195 7.96998 18.3061L17.144 12.4208C17.4507 12.2241 17.4507 11.7759 17.144 11.5792L7.96998 5.69395Z"/>
                </svg>
            </button>
            <button id="stop-btn">
                <svg width="24" height="24">
                    <rect width="24" height="24" fill="none" rx="0" ry="0"/>
                    <path fill-rule="evenodd" clip-rule="evenodd" d="M8 5C6.34315 5 5 6.34315 5 8V16C5 17.6569 6.34315 19 8 19H16C17.6569 19 19 17.6569 19 16V8C19 6.34315 17.6569 5 16 5H8ZM7.7 6.2C6.87157 6.2 6.2 6.87157 6.2 7.7V16.3C6.2 17.1284 6.87157 17.8 7.7 17.8H16.3C17.1284 17.8 17.8 17.1284 17.8 16.3V7.7C17.8 6.87157 17.1284 6.2 16.3 6.2H7.7Z"/>
                </svg>
            </button>
        </div>
    </div>
    <div id="light-mode">
        <div id="light-mode-hint-text">switch between light and dark mode ==&gt;</div>
        <label id="light-mode-label" for="light-mode-checkbox">
            <input id="light-mode-checkbox" type="checkbox"/>
            <svg id="sun" width="24" height="24">
                <rect width="24" height="24" fill="none" rx="0" ry="0"/>
                <path fill-rule="evenodd" clip-rule="evenodd" d="M12.3024 6.0957C11.9708 6.0957 11.7023 5.82715 11.7023 5.49561V2.6001C11.7023 2.26855 11.9708 2 12.3024 2C12.6339 2 12.9025 2.26855 12.9025 2.6001V5.49561C12.9025 5.82715 12.6339 6.0957 12.3024 6.0957ZM12.3024 8.55619C14.1865 8.55619 15.7194 10.0891 15.7194 11.9732C15.7194 13.8574 14.1865 15.3902 12.3024 15.3902C10.4182 15.3902 8.88535 13.8574 8.88535 11.9732C8.88535 10.0891 10.4182 8.55619 12.3024 8.55619ZM12.3024 7.35619C9.75247 7.35619 7.68533 9.4233 7.68533 11.9732C7.68533 14.5231 9.75247 16.5902 12.3024 16.5902C14.8523 16.5902 16.9194 14.5231 16.9194 11.9732C16.9194 9.4233 14.8523 7.35619 12.3024 7.35619ZM11.7023 21.5098C11.7023 21.8413 11.9708 22.1099 12.3024 22.1099C12.6339 22.1099 12.9025 21.8413 12.9025 21.5098V18.4761C12.9025 18.1445 12.6339 17.876 12.3024 17.876C11.9708 17.876 11.7023 18.1445 11.7023 18.4761V21.5098ZM16.8917 7.99658C16.7384 7.99658 16.5846 7.93799 16.4674 7.8208C16.233 7.58643 16.233 7.20654 16.4674 6.97217L18.9581 4.48145C19.1925 4.24707 19.5724 4.24707 19.8068 4.48145C20.0411 4.71582 20.0411 5.0957 19.8068 5.33008L17.316 7.8208C17.1989 7.93799 17.045 7.99658 16.8917 7.99658ZM4.79797 19.4902C4.91516 19.6074 5.06897 19.666 5.22229 19.666C5.37561 19.666 5.52942 19.6074 5.64661 19.4902L8.13733 16.9995C8.3717 16.7651 8.3717 16.3853 8.13733 16.1509C7.90295 15.9165 7.52307 15.9165 7.2887 16.1509L4.79797 18.6416C4.5636 18.876 4.5636 19.2559 4.79797 19.4902ZM21.6959 12.5859H18.7926C18.4611 12.5859 18.1925 12.3174 18.1925 11.9858C18.1925 11.6543 18.4611 11.3857 18.7926 11.3857H21.6959C22.0275 11.3857 22.296 11.6543 22.296 11.9858C22.296 12.3174 22.0275 12.5859 21.6959 12.5859ZM2.78967 12.5859H5.81213C6.14368 12.5859 6.41223 12.3174 6.41223 11.9858C6.41223 11.6543 6.14368 11.3857 5.81213 11.3857H2.78967C2.45813 11.3857 2.18958 11.6543 2.18958 11.9858C2.18958 12.3174 2.45813 12.5859 2.78967 12.5859ZM19.3824 19.666C19.2291 19.666 19.0753 19.6074 18.9581 19.4902L16.4674 16.9995C16.233 16.7651 16.233 16.3853 16.4674 16.1509C16.7018 15.9165 17.0817 15.9165 17.316 16.1509L19.8068 18.6416C20.0411 18.876 20.0411 19.2559 19.8068 19.4902C19.6896 19.6074 19.5358 19.666 19.3824 19.666ZM7.2887 7.8208C7.40588 7.93799 7.55969 7.99658 7.71301 7.99658C7.86633 7.99658 8.02014 7.93799 8.13733 7.8208C8.3717 7.58643 8.3717 7.20654 8.13733 6.97217L5.64661 4.48145C5.41223 4.24707 5.03235 4.24707 4.79797 4.48145C4.5636 4.71582 4.5636 5.0957 4.79797 5.33008L7.2887 7.8208Z"/>
            </svg>
            <svg id="moon" width="24" height="24">
                <rect width="24" height="24" fill="none" rx="0" ry="0"/>
                <path fill-rule="evenodd" clip-rule="evenodd" d="M18.3116 15.7176C18.276 15.7218 18.2403 15.7257 18.2045 15.7295C18.1145 15.7388 18.0241 15.7468 17.9332 15.7532C13.8065 16.0442 9.96093 13.0346 9.33828 8.9448C9.15658 7.75126 9.23351 6.58528 9.53112 5.51014C9.54069 5.47556 9.55049 5.44108 9.56052 5.40669C9.61073 5.23449 9.66663 5.0647 9.72807 4.89758C9.75955 4.81192 9.79249 4.72697 9.82687 4.64276C9.92978 4.39064 9.69603 4.12326 9.44525 4.2294C9.34387 4.27232 9.24353 4.31725 9.14429 4.36415C8.9787 4.44241 8.81618 4.52615 8.65694 4.61514C8.61419 4.63904 8.57167 4.66331 8.5294 4.68795C5.69068 6.3429 3.94189 9.6841 4.66214 13.3413C5.29296 16.5444 7.93153 19.1179 11.1492 19.6699C14.2472 20.2014 17.085 18.9895 18.8515 16.8533C18.8827 16.8157 18.9135 16.7777 18.944 16.7394C19.0584 16.5959 19.168 16.4485 19.2726 16.2973C19.3349 16.2073 19.3954 16.1159 19.4541 16.0233C19.5991 15.7945 19.3774 15.5225 19.113 15.5815C19.0237 15.6014 18.934 15.6197 18.8438 15.6366C18.6682 15.6694 18.4907 15.6965 18.3116 15.7176ZM17.0584 16.9669C15.5705 18.2248 13.542 18.8629 11.3521 18.4872C8.6248 18.0193 6.37412 15.8239 5.83953 13.1094C5.31501 10.4461 6.31749 7.98673 8.12032 6.44451C8.0064 7.31281 8.01319 8.21395 8.15195 9.1254C8.82202 13.5268 12.6995 16.8116 17.0584 16.9669Z"/>
            </svg>
        </label>
    </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap');

* {
    box-sizing: border-box;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}

*:focus {
    outline: none;
}

body {
    --fgcolor: #DDD;
    --bgcolor: #222;
    margin: 0;
    padding: 0;
    background: var(--bgcolor);
    color: var(--fgcolor);
    font-family: 'Indie Flower', cursive;
    overscroll-behavior: none;
}

svg {
    color: var(--fgcolor);
    fill: currentColor;
}

#container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100vw;
    height: 90vh;
}

#title, #message, #oscilloscope, #controls {
    text-align: center;
}

#message, #oscilloscope, #controls {
    border: 3px solid currentcolor;
    border-radius: 10px;
}

#message {
    margin: 10px 0;
    padding: 10px;
}

#title {
    margin-bottom: 10px;
}

#main-title {
    font-size: 3em;
    font-weight: bold;
}

#sub-title {
    font-size: 1.5em;
}

#message {
    border-color: red;
    color: red;
    font-size: 1.5em;
    font-weight: bold;
    display: none;
}

#oscilloscope {
    width: 95%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
}

#overlay-msg {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.5);
}

#overlay-msg-text {
    font-size: 2em;
    font-weight: bold;
}

#graph {
    width: 100%;
    height: 100%;
    margin: 10px 0;
}

#graph #line {
    stroke: var(--fgcolor);
    stroke-opacity: 0.25;
}

#graph #wave {
    stroke: var(--fgcolor);
    stroke-opacity: 0.75;
    fill: none;
}

#controls-container {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: center;
    margin: 10px 0;
}

#controls {
    padding: 10px;
}

#controls button {
    background-color: var(--bgcolor);
    border: 2px outset var(--fgcolor);
    border-radius: 10px;
}

#controls button:active {
    position: relative;
    top: 1px;
    left: 1px;
    border: 2px inset var(--fgcolor);
}

#controls button:active svg {
    /*fill: red;*/
}

#light-mode {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    width: 95%;
    margin-bottom: 0px;
}

#light-mode-hint-text {
    margin-right: 10px;
}

#light-mode-label {
    cursor: pointer;
    border: 1px solid currentcolor;
    border-radius: 10px;
    padding: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#light-mode-checkbox {
    display: none;
}

#light-mode-checkbox:checked ~ #sun {
    display: none;
}

#light-mode-checkbox:checked ~ #moon {
    display: inline;
}

#sun {
    display: inline;
}

#moon {
    display: none;
}
var analyser = null;
var firstRun = true;
var running = false;

const oscElem = document.querySelector('#oscilloscope');
const svgElem = document.querySelector('#graph');
const waveElem = document.querySelector('#wave');

setMessage('Press Play to start.');

const playBtn = document.querySelector('#play-btn');
playBtn.addEventListener('click', (e) => {
    e.preventDefault();
    running = true;
    setMessage('');
    if(firstRun)
        init();
    else
        render();
});

const stopBtn = document.querySelector('#stop-btn');
stopBtn.addEventListener('click', (e) => {
    e.preventDefault();
    running = false;
    setMessage('Press Play to start.')
});

const modeBtn = document.querySelector('#light-mode-checkbox');
modeBtn.addEventListener('change', (e) => {
    const body = document.body;
    const fgColor = getComputedStyle(body).getPropertyValue('--fgcolor');
    const bgColor = getComputedStyle(body).getPropertyValue('--bgcolor');
    body.style.setProperty('--fgcolor', bgColor);
    body.style.setProperty('--bgcolor', fgColor);
});

function init() {
    firstRun = false;

    initMediaDevices();
    
    // set up forked web audio context, for multiple browsers
    // ('window.' is needed otherwise Safari explodes)
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    if(audioCtx.state === "suspended") {
        audioCtx.resume();
    }

    analyser = audioCtx.createAnalyser();
    analyser.fftSize = 2048;
    
    //main block for doing the audio recording
    if(navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then( stream => {
                source = audioCtx.createMediaStreamSource(stream);
                source.connect(analyser);
            })
            .catch( e => {
                setErrorMessage("Access to the microfone failed. (" + e.name + ": " + e.message + ")");
            });
    } else {
        setErrorMessage('getUserMedia() not supported on your browser!');
    }
    
    /*
    const osc = audioCtx.createOscillator();
    osc.type = 'sine';
    osc.frequency.value = 1000;
    osc.start();

    osc.connect(analyser);
    */
    
    render();
}

function render() {
    var bufferLength = analyser.frequencyBinCount;
    var dataArray = new Uint8Array(bufferLength);
    
    analyser.getByteTimeDomainData(dataArray);
    
    const clientWidth = svgElem.clientWidth;
    const clientHeight = svgElem.clientHeight;
    
    waveElem.points.clear();

    for(let i = 0; i < bufferLength; i++) {
        const value = dataArray[i];
        
        let point = waveElem.points.appendItem(svgElem.createSVGPoint());

        point.x = i * (clientWidth / bufferLength);
        point.y = value * (clientHeight / 256);
    }
    
    if(running)
        requestAnimationFrame(render);
}

function setMessage(msg) {
    const msgElem = document.querySelector('#overlay-msg');
    if(msg.length > 0)
        msgElem.style.removeProperty('display');
    else
        msgElem.style.display = 'none';
    
    const msgTextElem = document.querySelector('#overlay-msg-text');
    msgTextElem.innerText = msg;
}

function setErrorMessage(msg) {
    const msgElem = document.querySelector('#message');
    msgElem.innerText = msg;
    if(msg.length > 0)
        msgElem.style.removeProperty('display');
    else
        msgElem.style.display = 'none';
}

function initMediaDevices() {
    // Older browsers might not implement "mediaDevices" at all, so we set an empty object first
    if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
    }

    // Some browsers partially implement "mediaDevices". We can't just assign an object
    // with "getUserMedia()" as it would overwrite existing properties.
    // Here, we will just add the "getUserMedia()" property if it's missing.
    if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function(constraints) {

            // First get ahold of the legacy "getUserMedia()", if present
            var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

            // Some browsers just don't implement it - return a rejected promise with an error
            // to keep a consistent interface
            if (!getUserMedia) {
                return Promise.reject(new Error('getUserMedia() is not implemented in this browser'));
            }

            // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
            return new Promise(function(resolve, reject) {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        }
    }
}

window.addEventListener('resize', (e) => {
    waveElem.points.clear();
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.