Michael Joseph Aubry
Michael Joseph Aubry

Reputation: 13402

How to create an audio spectrum using JavaScript and HTML Canvas?

Ok, so I know how to animate a canvas using AnalyserNode. I made a demo of how I implemented it.

This is my demo -> https://codesandbox.io/s/heuristic-lovelace-bmwxo?file=/src/Visualizer.js

What I am trying to understand is how do I make this look similar to this -> https://s3.us-west-1.amazonaws.com/storycreator.rendered/cka4ubx6d0dgb0114ws1rll7p?t=1590039817915

This audio spectrum was generated in After Effects using the audio spectrum effect.

I am using new Uint8Array(analyser.frequencyBinCount) the frequency feedback from the audio api. What is AE using under the hood to create the spectrum effect and is there a difference between spectrum and frequency in this context?

Here is the full code for JavaScript frequency

import React, { useEffect, useRef } from "react";

let frequencyArray = [];
let analyser;

const Visualizer = () => {
  const canvasRef = useRef(null);
  const requestRef = useRef(null);

  const handleInit = () => {
    initAudio();
    requestRef.current = requestAnimationFrame(drawCanvas);
  };

  const initAudio = () => {
    const audio = new Audio();
    audio.src =
      "https://s3.us-west-2.amazonaws.com/storycreator.uploads/ck9kpb5ss0xf90132mgf8z893?client_id=d8976b195733c213f3ead34a2d95d1c1";
    audio.crossOrigin = "anonymous";
    audio.load();

    const context = new (window.AudioContext || window.webkitAudioContext)();
    analyser = context.createAnalyser();
    const source = context.createMediaElementSource(audio);

    source.connect(analyser);
    analyser.connect(context.destination);

    frequencyArray = new Uint8Array(analyser.frequencyBinCount);
    audio.play();
  };

  // draw the whole thing
  const drawCanvas = () => {
    if (canvasRef.current) {
      const canvas = canvasRef.current;
      const ctx = canvas.getContext("2d");
      const radius = 200;
      const bars = Math.round(canvas.width);

      ctx.clearRect(0, 0, canvas.width, canvas.height);
      analyser.getByteFrequencyData(frequencyArray);

      for (var i = 0; i < bars; i++) {
        const height = frequencyArray[i] * 0.25;

        drawLine(
          {
            i,
            bars,
            height,
            radius
          },
          canvas,
          ctx
        );
      }

      requestRef.current = requestAnimationFrame(drawCanvas);
    }
  };

  // dray lines around the circle
  const drawLine = (opts, canvas, ctx) => {
    const { i, radius, bars, height } = opts;
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const lineWidth = 10;

    // draw the bar
    ctx.strokeStyle = "#ddd";
    ctx.lineWidth = lineWidth;
    ctx.lineCap = "round";
    ctx.beginPath();
    ctx.moveTo(i, centerY);
    ctx.lineTo(i, centerY + height);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(i, centerY);
    ctx.lineTo(i, centerY - height);
    ctx.stroke();
  };

  return (
    <>
      <button onClick={handleInit}>Start Visualizer</button>
      <canvas
        ref={canvasRef}
        style={{ background: "#f5f5f5" }}
        width={window.innerWidth}
        height={window.innerHeight}
      />
    </>
  );
};

export default Visualizer;

Upvotes: 5

Views: 4058

Answers (1)

Nemo
Nemo

Reputation: 93

I think analyzer.getByteTimeDomainData() would be more appropriate.

A slightly modified version of your code(so that I could test it offline and because I am not familiar with React):

let frequencyArray = [];
let analyser;
let request;
var flag=0;
var height=0;

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const bars = Math.round(canvas.width);
const lineWidth = 3;


var centerX = canvas.width / 2;
var centerY = canvas.height / 2;


const audio = new Audio();
audio.src =
  "https://s3.us-west-2.amazonaws.com/storycreator.uploads/ck9kpb5ss0xf90132mgf8z893?client_id=d8976b195733c213f3ead34a2d95d1c1";
audio.crossOrigin = "anonymous";
audio.load();

const context = new (window.AudioContext || window.webkitAudioContext)();
analyser = context.createAnalyser();
const source = context.createMediaElementSource(audio);

source.connect(analyser);
analyser.connect(context.destination);

frequencyArray = new Uint8Array(analyser.frequencyBinCount);

document.getElementById('button').addEventListener('click', function() {
  context.resume().then(() => {
    console.log('Playback resumed successfully');
  });
});

  function begin()
  {
     audio.play();
     requestAnimationFrame(drawCanvas);
  };

  function end()
  {
    cancelAnimationFrame(request);
    audio.pause();
  };

audio.addEventListener("ended", close);

function close()
{
  if(flag==0)
  {
    flag=1;
  }
  else
  {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      flag=0;
  }
}


  const drawCanvas = () => {

      ctx.clearRect(0, 0, canvas.width, canvas.height);
      analyser.getByteTimeDomainData(frequencyArray);

      for (var i = 0; i < bars; i+=3) {
       height = frequencyArray[i];
       
       if(height<100)
       {
        height*=0.05;
       }
       else
       {
        if(height<200 && height>100)
        {
          height=(height-100)+(100*0.05)
        }
        else
        {
          height=(height-200)*0.2+(100*1.05);
        }
       }

        drawLine(
          {
            i,
            bars,
            height
          },
          canvas,
          ctx
        );
      }

      if(flag==0)
      {
      request = requestAnimationFrame(drawCanvas);
      }
      else
      {
        flag=2;
        close();
      }
  };


  const drawLine = (opts, canvas, ctx) => {
    const { i, bars, height } = opts;

    // draw the bar
    ctx.strokeStyle = "#212121";
    ctx.lineWidth = lineWidth;
    ctx.lineCap = "round";
    ctx.beginPath();
    ctx.moveTo(i, centerY);
    ctx.lineTo(i, centerY + height);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(i, centerY);
    ctx.lineTo(i, centerY - height);
    ctx.stroke();


};
<!DOCTYPE html>
<html>
<head>

<body>

<button id="button" onClick=begin()>Start</button>
<button onClick=end()>End</button>
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML canvas tag.</canvas>

<script src = "wave.js">

</script>

</body>
</html>

Intermittently some sharp spikes emerge which looks bad. Perhaps someone else can fix that.

Upvotes: 3

Related Questions