11

I am trying to produce a wave form (https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API) with howler.js . I see the dataArray looping through the draw function. However it only draws a straight line because the v variable always returns 1. I based the code off a pretty common MDN example, this leads me to believe maybe the way I am getting the howler data is incorrect.

HTML

<div id="play">play</div>
<canvas id="canvas"></canvas>

JS

let playing = false
    const playBtn = document.getElementById('play')
    const canvas = document.getElementById('canvas')
    const canvasCtx = canvas.getContext('2d')
    const WIDTH = canvas.width
    const HEIGHT = canvas.height
    let drawVisual = null

    /*
        files
        https://s3-us-west-2.amazonaws.com/s.cdpn.io/481938/Find_My_Way_Home.mp3
    */

    /*
    streams
        'http://rfcmedia.streamguys1.com/MusicPulse.mp3'
    */

    let analyser = null
    let bufferLength = null
    let dataArray = null

    const howler = new Howl({
        html5: true,
        format: ['mp3', 'aac'],
        src:
            'https://s3-us-west-2.amazonaws.com/s.cdpn.io/481938/Find_My_Way_Home.mp3',
        onplay: () => {
            analyser = Howler.ctx.createAnalyser()
            Howler.masterGain.connect(analyser)
            analyser.connect(Howler.ctx.destination)
            analyser.fftSize = 2048
            analyser.minDecibels = -90
            analyser.maxDecibels = -10
            analyser.smoothingTimeConstant = 0.85
            bufferLength = analyser.frequencyBinCount
            dataArray = new Uint8Array(bufferLength)
            canvasCtx.clearRect(0, 0, WIDTH, HEIGHT)

            const draw = () => {
                drawVisual = requestAnimationFrame(draw)
                analyser.getByteTimeDomainData(dataArray)
                canvasCtx.fillStyle = '#000'
                canvasCtx.fillRect(0, 0, WIDTH, HEIGHT)
                canvasCtx.lineWidth = 2
                canvasCtx.strokeStyle = 'limegreen'
                canvasCtx.beginPath()

                let sliceWidth = (WIDTH * 1.0) / bufferLength
                let x = 0

                for (let i = 0; i < bufferLength; i++) {
                    let v = dataArray[i] / 128.0
                    let y = (v * HEIGHT) / 2

                    if (i === 0) {
                        canvasCtx.moveTo(x, y)
                    } else {
                        canvasCtx.lineTo(x, y)
                    }

                    x += sliceWidth
                }

                canvasCtx.lineTo(canvas.width, canvas.height / 2)
                canvasCtx.stroke()
            }

            draw()
        }
    })

    playBtn.addEventListener('click', () => {
        if (!playing) {
            howler.play()
            playing = true
        }
    })
NicholasByDesign
  • 781
  • 1
  • 11
  • 33
  • Could you please put a working example to a jsfiddle or somewhere? Can not try your code now as the HTML is missing. Maybe you simply fail to load the file for some reason. Do you get errors in the console? – antont Jan 02 '20 at 02:10
  • Sure can but the only two elements that exist are and
    – NicholasByDesign Jan 02 '20 at 03:19

2 Answers2

5

To get it working:

  1. Remove html5: true
  2. There is a CORS setup isssue with your audio source. What are your bucket CORS settings? Access to XMLHttpRequest at 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/481938/Find_My_Way_Home.mp3' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The CORS issue leads to your dataArray being full of 128 basically meaning no sound even though the music is playing.

With that I got your visualizer to work. (You can bypass CORS on chrome "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security)

  • 3
    While disabling web security works for testing purpose, this is not ideal. If OP has CORS issue right now, chances are they are going to have CORS issue while in production. They should try to fix it instead of avoiding it. – Nicolas Jan 07 '20 at 15:20
  • Is there any way to implement an analyzer while using `html5: true`, for streaming media for example? – xfx Mar 06 '21 at 22:52
  • whats the reason for removing `html5: true` ? – Petros Kyriakou Feb 17 '22 at 13:21
  • This didn't seem to fix livestream visualizations for mp3, here's a link to an example that did https://stackoverflow.com/questions/44479560/creating-an-audio-visualizer-using-html5 – Barak Binyamin Dec 27 '22 at 08:54
1

Here is the code for the waveform:

const data = audioBuffer.getChannelData(0)
context.beginPath()
const last = data.length - 1
for (let i = 0; i <= last; i++) {
  context.lineTo(i / last * width, height / 2 - height * data[i])
}
context.strokeStyle = 'white'
context.stroke()

How to get this audioBuffer from howler? I'm not suggesting to try it because howler may not use web audio api. And no way in doc, only digging source code. Instead, here is the code to load this buffer directly:

const url = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/481938/Find_My_Way_Home.mp3'
const getAudioBuffer = async (url) => {
  const context = new AudioContext
  result = await new Promise(resolve => {
    request = new XMLHttpRequest
    request.open "GET", url, true
    request.responseType = 'arraybuffer'
    request.onload = () => resolve(request.response)
    request.send()
  }
  return await context.decodeAudioData(result)
}
audioBuffer = getAudioBuffer(url)
audioBuffer.getChannelData(0) // it can have multiple channels, each channel is Float32Array

But! This is waveform without animation, track is downloaded and waveform draw.

In your example you are trying to make something animated, using that code above it's possible to make something like window moving from start to end according to playback position.

So my answer is not answer, no animation, no howler, but hope it helps :)

user2541867
  • 344
  • 4
  • 12