1

I'm creating a program to interact with music, and I was trying to synchronize the drum beats or the bass sounds with CSS, so in every high frequency sound, the background would change just in time.

My first idea was create an spectrum of the audio frequency, to understand how does it interacts with the sound played. In this case, I'm using Web Audio API analyserNode.fftSize to get all the points of the frequency in a audio file and create the spectrum with <canvas>

const audioCtx = new AudioContext();

    //Create audio source
    //Here, we use an audio file, but this could also be e.g. microphone input
    const audioEle = new Audio();
    audioEle.src = "good-day.mp3"; //insert file name here
    audioEle.autoplay = true;
    audioEle.preload = "auto";
    const audioSourceNode = audioCtx.createMediaElementSource(audioEle);

    //Create analyser node
    const analyserNode = audioCtx.createAnalyser();
    analyserNode.fftSize = 256;
    const bufferLength = analyserNode.frequencyBinCount;
    const dataArray = new Float32Array(bufferLength); // this is where all the frequencies are stored

Then, using this dataArray with all the frequencies of that moment, I run a condition that says: if that frequency is above X, change background to blue:

      if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
        await changeBgColor();
      }

The final results seems to work, but isn't perfect, since that if condition runs 60 times per second and sometimes doesn't catch the bass playing because of a voice or another sound in the audio file that mess with the frequency of the bass.

I don't know if there is something that really can be useful for this. I tried to use ToneJS and wadJS but I couldn't get anywhere with these libs.

I wonder what could be the best way to make these CSS iteration with sounds.

The song used for test this code was: Ice Cube - It Was A Good Day (good-day.mp3)

Full code:

index.html

<!DOCTYPE html>
<body id="bd">
  <script>
    const audioCtx = new AudioContext();

    //Create audio source
    //Here, we use an audio file, but this could also be e.g. microphone input
    const audioEle = new Audio();
    audioEle.src = "good-day.mp3"; //insert file name here
    audioEle.autoplay = true;
    audioEle.preload = "auto";
    const audioSourceNode = audioCtx.createMediaElementSource(audioEle);

    //Create analyser node
    const analyserNode = audioCtx.createAnalyser();
    analyserNode.fftSize = 256;
    const bufferLength = analyserNode.frequencyBinCount;
    const dataArray = new Float32Array(bufferLength);

    //Set up audio node network
    audioSourceNode.connect(analyserNode);
    analyserNode.connect(audioCtx.destination);

    //Create 2D canvas
    const canvas = document.createElement("canvas");
    canvas.style.position = "absolute";
    canvas.style.top = 0;
    canvas.style.left = 0;
    canvas.width = window.innerWidth;
    canvas.height = 300;
    document.body.appendChild(canvas);
    const canvasCtx = canvas.getContext("2d");
    canvasCtx.clearRect(0, 0, canvas.width, canvas.height);

    async function draw() {
      const changeBgColor = () => {
        document.getElementById("bd").style["background-color"] = "blue";
      };

      const sleep = () => {
        // setTimeout(() => null, 1000)
        console.log("bass sound");
      };

      //Schedule next redraw
      requestAnimationFrame(draw);

      //Get spectrum data
      analyserNode.getFloatFrequencyData(dataArray);

      //Draw black background
      canvasCtx.fillStyle = "rgb(0, 0, 0)";
      canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

      //Draw spectrum
      const barWidth = (canvas.width / bufferLength) * 2.5;
      let posX = 0;
      document.getElementById("bd").style["background-color"] = "red";

      if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
        await changeBgColor();
      }

      for (let i = 0; i < bufferLength; i++) {
        const barHeight = (dataArray[i] + 100) * 2;
        //   console.log(barHeight)

        canvasCtx.fillStyle = `rgb(${Math.floor(
          barHeight + 100
        )}, 255, ${Math.floor(barHeight + 200)})`;

        canvasCtx.fillRect(
          posX,
          canvas.height - barHeight / 2,
          barWidth,
          barHeight / 2
        );
        posX += barWidth + 1;
      }
    }

    draw();
  </script>
</body>

My reference to this code was a doc in MDN about AnalyserNode

R01machin
  • 33
  • 3

1 Answers1

1

Here is quick demo on how to retrieve the music frequencies into an array and pass it to the CSS.

It's very basic, there is many problems, but maybe a good start for further proper animations.

enter image description here

const audio = document.getElementById('music');
audio.load();
audio.play();

const ctx = new AudioContext();
const audioSrc = ctx.createMediaElementSource(audio);
const analyser = ctx.createAnalyser();

audioSrc.connect(analyser);
analyser.connect(ctx.destination);

analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const frequencyData = new Uint8Array(bufferLength);

setInterval(async () => {
  analyser.getByteFrequencyData(frequencyData);
  let dataArray = Object.values(frequencyData);
  //if (Math.max(...dataArray) + 100 > 70) { // +- 70 is a good number for that specific song
    window.requestAnimationFrame(
      await changeBgColor(dataArray)
    )
  //}
}, 100);

const changeBgColor = (dataArray) => {
  console.log(dataArray)
  let color1 = dataArray.slice(0,3).map((a) => a / 100) // Smaller numbers are darker
  let color2 = dataArray.slice(3,6)
  let color3 = dataArray.slice(6,9)
  document.body.style.background = `linear-gradient(90deg, rgba(${color1}, 1) 0%, rgba(${color2},0.2) 35%, rgba(${color3},1) 100%)`
}
<audio id="music" src="https://cdn.glitch.com/02dcea11-9bd2-4462-ac38-eeb6a5ad9530%2F331_full_beautiful-minds_0171_preview.mp3?1522829295082" crossorigin="use-URL-credentials" controls="true"></audio>
<hr>
<span id="console"></span>
NVRM
  • 11,480
  • 1
  • 88
  • 87
  • 1
    that's really cool, I definitely gonna use, but one thing that I wanted to know is: what kinda of problems that way of make CSS interactions can have? You said that "there is many problems" – R01machin Apr 06 '22 at 21:43
  • Great! What i meant is here the style is modified inline, this modify the DOM and can turn slow overtime, also there is a `setInterval()` that can be avoided. The colors are not vivid! For fast animations, yes use canvas as you shown. Notice media elements have events to do that, for example "timeupdate" https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/timeupdate_event . Here is two more links for you, good luck!!: web audio filters: https://stackoverflow.com/a/48528432/2494754 , and proper use of requestAnimation(): https://stackoverflow.com/a/57571687/2494754 . – NVRM Apr 07 '22 at 20:57