Search code examples
javascriptwavweb-audio-apipcmwavesurfer.js

Getting PCM data from wavesurfer.js backend/web audio api


I am using wavesurfer.js to create a multitrack player online and want to export a remixed version of the combined tracks with levels panning etc.

First I have an array of audioFiles and use this to create an array of wavesurfer elements.

for(var i=0; i<audiofiles.length; i++){
    spectrum[i] = WaveSurfer.create({
        
    });
}

I then create a buffer for each of these from wavesurfer backend

for(var i=0; i<audiofiles.length; i++){
    
    var ctx = spectrum[i].backend.ac;
    var length = spectrum[i].getDuration() * sample_rate * 2;
    var ctx_buffer = ctx.createBuffer(2, length, ctx.sampleRate);

    // pass raw pcm buffer to download function
}

And then finally I got some help with the download function here Downloading audio from web that has been modified with wavesurfer.js

My issue at this point is that what I'm passing to the download function doesn't seem to be in the correct format. I'm new to working with Audio and not sure what I'm doing.

If I pass the ctx_buffer variable to the function in the other question (and use it instead of the buffer variable there which is got directly from a pcm file) I will get a successful download but the file is empty, although it is the correct length (leaving out the *2 in the length above will have an empty file exactly half the length of my original).

There is an exportPCM() function in wavesurfer.js here https://wavesurfer-js.org/docs/methods.html but I'm not sure how this works either.

Edit

buttons.save_all.addEventListener("click", function(){
    document.getElementById("download_icon").className = "fas fa-spinner loader";
    document.getElementById("download_text").innerText = "Loading...";
    
    var length = spectrum[0].getDuration()*44100*2;
    for(var i=0; i<audiofiles.length; i++){
        var ctx = spectrum[i].backend.ac;
        var sample_rate = ctx.sampleRate;
        var length = spectrum[i].getDuration()*sample_rate*2;
        
        var ctx_buffer = ctx.createBuffer(2, length, ctx.sampleRate);

        download_init(ctx_buffer,length,sample_rate).catch(showError);
    }
}, false);

function showError(e) {
    console.log(`ERROR: ${e}`);
}

var x = 0;
async function download_init(ctx_buffer,length,sample_rate) {
    // const buffer = await (await fetch(url)).arrayBuffer()
    
    const wavBytes = getWavBytes(ctx_buffer, {
        numFrames: ctx_buffer.length,
        numChannels: 1,
        sampleRate: sample_rate,
        isFloat: false
    });
    console.log(wavBytes);
    
    blobs[x] = URL.createObjectURL(
        new Blob([wavBytes], { type: 'audio/wav' })
    )
    
    document.getElementById("btn_download").href = blobs[0];
    document.getElementById("btn_download").setAttribute('download', 'download.wav');
    
    document.getElementById("btn_save_all").hidden = true;
    document.getElementById("btn_download").hidden = false;
    
    x++;
}

Solution

  • Given that you currently have an array of AudioBuffer objects, you can interleave the Float32Array PCM data contained within each AudioBuffer, and then use that interleaved PCM to create a RIFF/Wav file to download. If each AudioBuffer is a track, then all of the left/right channels in the array must be combined separately and interleaved at the end. Here's how to start with one AudioBuffer track:

    Convert AudioBuffer to ArrayBuffer / Blob for WAV Download