inos3910 / audio-visualizer-by-howlerjs

howler.jsを使ってオーディオビジュアライザーを作ってみる
5 stars 1 forks source link

Could help me out and answer few questions about implementing audio visualizer into Vue? #1

Closed tutaru99 closed 3 years ago

tutaru99 commented 3 years ago

Hello, I need help with the implementation of the visualizer into my own project - radio.

The biggest issue I'm having right now - getByteFrequencyData() returns a full array of 0 no matter what.

Thanks, have a good one. (Feel free to contact me through email, it's on my GitHub page) Code:

 testAnalyzer() {
      var audio = new Howl({
        src: ['https://pool.anison.fm:9000/AniSonFM(320)'],
        html5: true,
        volume: this.volume,
      });

        audio.play();
        const audioCtx = Howler.ctx;
        const audioSourceNode = audioCtx.createMediaElementSource(audio._sounds[0]._node);
        console.log('Audio CTX', audioCtx);

        const analyser = audioCtx.createAnalyser();
        console.log('Audio Source Node - ', audioSourceNode);

        analyser.fftsize = 512;
        const bufferLength = analyser.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);

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

        analyser.getByteFrequencyData(dataArray)
        console.log('Frequency Data - ', dataArray)
    }

Output: output

inos3910 commented 3 years ago

Hello! Thank you for your question.

Isn't the first frame silent?

Even in the sample code, there is no sound in the first frame, so the values ​​of the array that can be obtained first are all 0. At the same time as the music starts playing, requestAnimationFrame is used to execute getByteFrequencyData every frame to get an array of waveform data at the moment when the sound is sounding.

  // Draw SVG for audio visualizer
  drawAudioVisualizer(){

    // Animation ends when gain reaches 0
    if(this.gainNode.gain.value === 0){
      if(this.drawTimer){
        window.cancelAnimationFrame(this.drawTimer);
        return;
      }
    }

    // Set 0 to 1. Drawing becomes smoother when it is closer to 0
    this.analyserNode.smoothingTimeConstant = 0.1;

    // FFT size
    this.analyserNode.fftSize = 1024;

    // Store the waveform data in the frequency domain in an array of arguments
    this.analyserNode.getByteFrequencyData(this.freqs);

    // How long the SVG width will be with respect to the waveform data
    const barWidth = this.svg.width.baseVal.value * 1.5 / this.analyserNode.frequencyBinCount;

    // Apply width calculation result to SVG path
    this.drawSvgPath(barWidth);

    // Draw every frame
    this.drawTimer = window.requestAnimationFrame(this.drawAudioVisualizer.bind(this));

  }

As mentioned above, the function is recursively called to work with the animation.

Also, I think fftsize is fftSize.

tutaru99 commented 3 years ago

Hey! Thanks for the quick reply and help!

I've tried to implement your code however I couldn't make it work. I've changed my code quite a bit, but I think the problem I'm having is that I'm not fully/or at all connected to the howler. I keep getting 0 on the getByteFrequencyData.

2 Main functions that I'm using is startRadio() and drawAudioVisualizer() even though drawAudioVisualizer() right now is used just to show frequency data.

startRadio(stationSrc, stationID) {
      this.radioStarted = true;
      this.arrayID = stationID;
      this.stations[stationID].playing = true;
      this.sound = new Howl({
        src: stationSrc,
        html5: true,
        volume: this.volume,
      });
      // Create an analyser node in the Howler WebAudio context
      var analyser = Howler.ctx.createAnalyser();
      var dataArray = new Uint8Array(analyser.frequencyBinCount);

      Howler.ctx.createGain = Howler.ctx.createGain || Howler.ctx.createGainNode;
      var gainNode = Howler.ctx.createGain();
      gainNode.gain.setValueAtTime(1, Howler.ctx.currentTime);
      Howler.masterGain.connect(analyser);
      analyser.connect(gainNode);
      gainNode.connect(Howler.ctx.destination);

      // Starting Playing Audio
      this.soundID = this.sound.play();
      console.log("Radio Started Playing");

      // Defining variables so they can be used globally
      this.analyser = analyser;
      this.gainNode = gainNode;
      this.frequency = dataArray;
      // Call function to show frequency data
      this.drawAudioVisualizer();
    },

Function to get frequency data when audio is playing

// Start getting Frequency Data
  drawAudioVisualizer(){
    // Animation ends when gain reaches 0
    if(this.gainNode.gain.value === 0){
      if(this.drawTimer){
        window.cancelAnimationFrame(this.drawTimer);
        return;
      }
    }
    // Set 0 to 1. Drawing becomes smoother when it is closer to 0
    this.analyser.smoothingTimeConstant = 0.1;
    // FFT size
    this.analyser.fftSize = 1024;
    // Store the waveform data in the frequency domain in an array of arguments
    this.analyser.getByteFrequencyData(this.frequency);
    // Draw every frame
    this.drawTimer = window.requestAnimationFrame(this.drawAudioVisualizer.bind(this));
    console.log(this.frequency)
  },

Console Output on frequency console2

As shown in the picture above, the recursive function starts (and works?) however I keep getting 0 for frequency - getByteFrequencyData even when audio is playing. This leads me to believe that I'm not connected to the parts of howler that I need to..? Perhaps you can spot what I'm missing in the code? I believe the problem lies in the startRadio() {} function.

Thanks for taking the time to help me out.

inos3910 commented 3 years ago

Maybe there is a problem with the option settings in Howler.js.

html5: true

By specifying this option, Howler.js will play using HTML5 Audio instead of the Web Audio API.

Web Audio API https://webaudio.github.io/web-audio-api/

HTML5 Audio https://html.spec.whatwg.org/multipage/media.html#the-audio-element

HTML5 Audio and Web Audio API are completely different, so // Create an analyzer node in the Howler WebAudio context The code after the comment line above will not work.

Even in my sample code, setting html5: true makes a sound but the visualizer doesn't work.

tutaru99 commented 3 years ago

So after a lot of testing and searching, I came to a conclusion that I simply cannot use live stream audio from the internet radio station and in turn get its frequency. As you mentioned above html5: true is the culprit of the problem.

Without it - and with minor code adjustment + a local song - var sound = new Howl({ src: require('@/assets/audio.mp3')}); I start getting frequency just fine. However again I want frequency from Live Streaming Radio which requires me to use html5: true in order to make it work. Therefore I came to realization that I simply can't do this the way I want.. :D

Thanks for helping me and explaining this audio concept!

inos3910 commented 3 years ago

If you want to handle streaming sound sources You may need to use AudioContext.createMediaStreamSource(). https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamSource

I don't know how much it can be done with Howler.js, but some people use the plain Web Audio API to visualize the audio picked up from the microphone.

https://github.com/aadebdeb/web-audio-gl-sample

I hope it will be helpful. Good luck!