Create JavaScript Waveform Visualization With Howler.js
Asked Answered
T

2

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
        }
    })
Treat answered 20/12, 2019 at 21:51 Comment(2)
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?Aldehyde
Sure can but the only two elements that exist are <canvas id="canvas"> and <div id="play">Treat
D
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)

Deflate answered 2/1, 2020 at 4:17 Comment(4)
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.Dufresne
Is there any way to implement an analyzer while using html5: true, for streaming media for example?Skull
whats the reason for removing html5: true ?Plating
This didn't seem to fix livestream visualizations for mp3, here's a link to an example that did #44480060Nonce
V
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 :)

Vendor answered 7/1, 2020 at 1:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.