katspaugh / wavesurfer.js

Audio waveform player
https://wavesurfer.xyz
BSD 3-Clause "New" or "Revised" License
8.75k stars 1.62k forks source link

Region playback documentation #3913

Open danijel3 opened 1 week ago

danijel3 commented 1 week ago

The auto playback/loop feature described in the documentation sucks. It's an important feature if you want to set up the region boundaries precisely and need to playback the regions multiple times while doing fine adjustments.

The problem with the example in the demos is that it always overshoots the ending by a random amount and using region events simply doesn't work.

Looking through the source code, I found that WebAudio backend supports a slightly more accurate method of pausing the audio at region end. Granted this method doesn't work for HTML audio element backend, but I think it would be worth documenting this feature somewhere, so people don't have to look for it in the sourcecode.

My solution requires the appropriate backend in the Wavesrufer constructor:

wavesurfer = WaveSurfer.create({
    container: '#segmentation-container',
    url: audio_url,
    backend: "WebAudio",
    ...
})

Next I create a playBetween method like this:

function playBetween(start, end) {
    wavesurfer.media.currentTime = start
    wavesurfer.media.play()
    wavesurfer.media.stopAt(end)
  }

Which I can use in various contexts, for example:

    regions.on('region-clicked', (r, e) => {
      e.stopPropagation() // prevent triggering a click on the waveform
      playBetween(r.start, r.end)
    })
eddiesosera commented 1 week ago

Thanks that was useful I was experiencing the same issue with looping regions. I'll let you know once I try it.

danijel3 commented 1 week ago

Yea, this isn't really perfect for looping, but you could implement something yourself. To see how, you need to look at the implementation of the stopAt function: https://github.com/katspaugh/wavesurfer.js/blob/2682460402457def3326e5e0bf33a0e1a3a8a799/src/webaudio.ts#L135-L148

It works similarly to the example case, but instead of looking for wavesurfer events, it is using a slightly more accurate buffer node events. It's worth noting this is still far from perfect. The safest, most perfect approach would simply copy the portion of the buffer to a separate location (say another audio node) and simply play/loop that.

EDIT: Upon reviewing the documentation, it's actually quite accurate:

https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode/stop

You could go with copying the buffer if you were extremely paranoid, but for most use cases, this is quite sufficient. The only small bug I notice is the playhead is sometimes a bit off graphically, but the audio is actually pretty accurate.

Colorful-Toujours commented 6 days ago

Has loop?

danijel3 commented 6 days ago

Again, this functionality (unlike stopAt) isn't implemented anywhere in the project. I got something to work, using this function:

 function loopBetween(start, end) {
  if (!wavesurfer.media.paused) return

  wavesurfer.media.paused = false

  wavesurfer.media.bufferNode?.disconnect()
  wavesurfer.media.bufferNode = wavesurfer.media.audioContext.createBufferSource()
  if (wavesurfer.media.buffer) {
    wavesurfer.media.bufferNode.buffer = wavesurfer.media.buffer
  }
  wavesurfer.media.bufferNode.playbackRate.value = wavesurfer.media._playbackRate
  wavesurfer.media.bufferNode.connect(wavesurfer.media.gainNode)

  wavesurfer.media.bufferNode.loop = true
  wavesurfer.media.bufferNode.loopStart = start
  wavesurfer.media.bufferNode.loopEnd = end

  wavesurfer.media.bufferNode.start(wavesurfer.media.audioContext.currentTime, start)
}

However, this won't advance the playhead, even if the audio does loop. You can check out the documentation here: https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/loop

Colorful-Toujours commented 6 days ago

Hello, can you try to load large files through range slicing in wavesuier?

Colorful-Toujours commented 5 days ago

Again, this functionality (unlike stopAt) isn't implemented anywhere in the project. I got something to work, using this function:

 function loopBetween(start, end) {
  if (!wavesurfer.media.paused) return

  wavesurfer.media.paused = false

  wavesurfer.media.bufferNode?.disconnect()
  wavesurfer.media.bufferNode = wavesurfer.media.audioContext.createBufferSource()
  if (wavesurfer.media.buffer) {
    wavesurfer.media.bufferNode.buffer = wavesurfer.media.buffer
  }
  wavesurfer.media.bufferNode.playbackRate.value = wavesurfer.media._playbackRate
  wavesurfer.media.bufferNode.connect(wavesurfer.media.gainNode)

  wavesurfer.media.bufferNode.loop = true
  wavesurfer.media.bufferNode.loopStart = start
  wavesurfer.media.bufferNode.loopEnd = end

  wavesurfer.media.bufferNode.start(wavesurfer.media.audioContext.currentTime, start)
}

However, this won't advance the playhead, even if the audio does loop. You can check out the documentation here: https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/loop

However, the pointer of the wavesufer waveform does not run synchronously and is not linked to the waveform. Although audio playback is implemented