-1

I am working o a music visualizer project.

On my HTML page, I have a div that has been styled into a yellow circle using CSS.

With JAVASCRIPT, I would like to collect the frequency data of an audio file, and with that data, change the color of the circle to pink whenever the frequency hits a specific number - a number that will represent the transients in the audio. Basically, a yellow circle that changes to pink following the beat of the song.

The HTML:

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <title>CSS Flower</title>
    <style>
      /* centering */
      body {
        margin: 0;
        height: 100vh;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      /* proportion and perspective */
      #scene {
        width: 200px;
        height: 200px;
        perspective: 600px;
        perspective-origin: center top;
      }

      .flower {
        width: 100%;
        height: 100%;
        position: relative;
        transform-style: preserve-3d;
      }

      #center {
        position: absolute;
        width: 100px;
        height: 100px;
        border-radius: 50px;
        background: yellow;  // THIS IS YELLOW CIRCLE, AND PARAMETER I WANT TO CHANGE
        transform: rotateX(180deg) translateX(50px) translateZ(-140px);
      }

    </style>
  </head>

  <body>
    <input type="file" id="file-input" accept="audio/*,video/*,image/*" />
    <canvas id="canvas"></canvas>
    <div id="scene">
      <div class="flower">
        <div class="center" id="center"></div>
      </div>
    </div>
    <audio id="audio" controls></audio>
    <script src="audio.js"></script>
  </body>
</html>

The JS code that I have now :

window.onload = function() {

  const file = document.getElementById("file-input");
  const canvas = document.getElementById("canvas");
  const audio = document.getElementById("audio");

  file.onchange = function() {

    const files = this.files; 
    console.log('FILES[0]: ', files[0])
    audio.src = URL.createObjectURL(files[0]); 

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext("2d");
    const flower = document.getElementById('center');



    const context = new AudioContext(); 
    let src = context.createMediaElementSource(audio); 
    const analyser = context.createAnalyser(); 

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

    analyser.fftSize = 1024;

    const bufferLength = analyser.frequencyBinCount; 

    const dataArray = new Uint8Array(bufferLength); 

    const WIDTH = canvas.width;
    const HEIGHT = canvas.height;

    function renderFrame() {
      requestAnimationFrame(renderFrame); // Takes callback function to invoke before rendering


      analyser.getByteFrequencyData(dataArray); // Copies the frequency data into dataArray
      // Results in a normalized array of values between 0 and 255
      // Before this step, dataArray's values are all zeros (but with length of 8192)

      for (let i = 0; i < bufferLength; i++) {
        console.log("data array: ", dataArray)

//from here I can see the different frequencies collected in the array, but I don't know how to proceed
      }

    audio.play();
    renderFrame();
  };
};
};

I can't figure out how to formulate the FOR loop, and then how to connect this information with the CSS parameter.

Any help would be extremely appreciated! I have been trying to figure it out for days, with no luck. As you can probably tell by my code, I am a complete beginner!

All the best,

Jade

JCroo
  • 1

1 Answers1

2

Just do average of frequencies. Here is working example:

const file = document.getElementById("file-input");
const canvas = document.getElementById("canvas");
const audio = document.getElementById("audio");

var threshold = 230;  //Point where turn the "flower" pink
var frequencies = 10; //Number of records to count (You want to look for lower frequencies)

file.onchange = function() {

    const files = this.files;
    //console.log('FILES[0]: ', files[0])
    audio.src = URL.createObjectURL(files[0]);

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext("2d");
    const flower = document.getElementById('center');



    const context = new AudioContext();
    let src = context.createMediaElementSource(audio);
    const analyser = context.createAnalyser();

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

    analyser.fftSize = 1024;

    const bufferLength = analyser.frequencyBinCount;

    const dataArray = new Uint8Array(bufferLength);

    const WIDTH = canvas.width;
    const HEIGHT = canvas.height;

    function renderFrame() {
        requestAnimationFrame(renderFrame); // Takes callback function to invoke before rendering


        analyser.getByteFrequencyData(dataArray); // Copies the frequency data into dataArray
        // Results in a normalized array of values between 0 and 255
        // Before this step, dataArray's values are all zeros (but with length of 8192)

        let sum = 0;

        for(let i = 0; i < bufferLength; i++) {
            if(i < frequencies) sum += dataArray[i];

            //Do some other stuff
        }

        //Change CSS according to threshold
        let avg = sum / frequencies;
        flower.style.backgroundColor = avg > threshold ? "pink" : "yellow";
        flower.style.transform = `scale(${avg / 255})`;
        flower.innerText = ~~avg;

    };

    //!!This functions have to be called outside of the rendering function (renderFrame)!!
    audio.play();
    renderFrame();
};


/* Sliders */
const sliderF = document.querySelector(".slider.f");
const sliderFvalue = document.querySelector(".slider-value.f");
const sliderT = document.querySelector(".slider.t");
const sliderTvalue = document.querySelector(".slider-value.t");

sliderF.oninput = function() {
  sliderFvalue.innerText = this.value;
  frequencies = +this.value;
}

sliderT.oninput = function() {
  sliderTvalue.innerText = this.value;
  threshold = +this.value;
}
/* centering */
body {
  margin: 0;
}

canvas {
  position: absolute;
  pointer-events: none;
}

#audio {
  width: 100vw;
}

#center {
  width: 100px;
  height: 100px;
  border-radius: 50px;
  background-color: yellow;
  text-align: center;
  line-height: 100px;
}
      
.slider-value {
  display: inline-block;
  width: 25px;
}

.slider {
  width: 90vw;
}
<input type="file" id="file-input" accept="audio/*,video/*,image/*" /><br>
<canvas id="canvas"></canvas>
<audio id="audio" controls></audio>
<div class="center" id="center"></div>

<!-- Frequences Slider --->
<input type="range" min="0" max="500" value="10" step="1" class="slider f">
<span class="slider-value f">10</span>

<!-- Threshold Slider --->
<input type="range" min="0" max="255" value="230" step="1" class="slider t">
<span class="slider-value t">230</span>

You just need to adjust sliders to make it perfect, but it might be better to use some lowpass EQ so you would get even closer results.

loumadev
  • 371
  • 3
  • 12