FAQ

Can the audio start playing before the waveform is drawn?

Yes, if you use the backend: 'MediaElement' option. See here: http://wavesurfer-js.org/example/audio-element/. The audio will start playing as you press play. A thin line will be displayed until the whole audio file is downloaded and decoded to draw the waveform.

Can drawing be done as file loads?

No. Web Audio needs the whole file to decode it in the browser. You can however load pre-decoded waveform data to draw the waveform immediately. See here (the "Pre-recoded Peaks" section).

Can I make the audio start playing automatically on iOS?

Nope. It's a known issue that iOS won't allow you to play the audio programmatically. It won't play unless the user clicks on the page. It's a power/bandwidth-saving feature of iOS Safari.

How to generate waveform data on the server?

You can use the audiowaveform program. For example, let's generate peaks for a MP3 file called 'long_clip.mp3'.

Generate JSON-formatted peaks data from the file long_clip.mp3:

audiowaveform -i long_clip.mp3 -o long_clip.json --pixels-per-second 20 --bits 8

To generate waveforms for each audio channel separately, add the '--split-channels' flag long_clip.mp3:

audiowaveform -i long_clip.mp3 -o long_clip.json --pixels-per-second 20 --bits 8 --split-channels

Normalization

audiowaveform will create non-normalized peak data. Wavesurfer.js expects peak data between 0 and 1. There are two ways to normalize:

Client-side

The easiest way to normalize the peak data is by enabling `normalize: true` in the WaveSurfer settings. The peak data will be transformed client-side.


var wavesurfer = WaveSurfer.create({
  container: '#waveform',
  waveColor: 'violet',
  progressColor: 'purple'
  normalize: true,
});

Server-side

You can also pre-normalize the peak data instead of letting the client do it on every load. This could improve performance somewhat, which might be important for large audio files.

You can do this normalization with the pyhon script below:

python scale-json.py long_clip.json

import sys
import json


def scale_json(filename):
    with open(filename, "r") as f:
        file_content = f.read()

    json_content = json.loads(file_content)
    data = json_content["data"]
    channels = json_content["channels"]
    # number of decimals to use when rounding the peak value
    digits = 2

    max_val = float(max(data))
    new_data = []
    for x in data:
        new_data.append(round(x / max_val, digits))
    # audiowaveform is generating interleaved peak data when using the --split-channels flag, so we have to deinterleave it
    if channels > 1:
        deinterleaved_data = deinterleave(new_data, channels)
        json_content["data"] = deinterleaved_data
    else:
        json_content["data"] = new_data
    file_content = json.dumps(json_content, separators=(',', ':'))

    with open(filename, "w") as f:
        f.write(file_content)


def deinterleave(data, channelCount):
    # first step is to separate the values for each audio channel and min/max value pair, hence we get an array with channelCount * 2 arrays
    deinterleaved = [data[idx::channelCount * 2] for idx in range(channelCount * 2)]
    new_data = []

    # this second step combines each min and max value again in one array so we have one array for each channel
    for ch in range(channelCount):
        idx1 = 2 * ch
        idx2 = 2 * ch + 1
        ch_data = [None] * (len(deinterleaved[idx1]) + len(deinterleaved[idx2]))
        ch_data[::2] = deinterleaved[idx1]
        ch_data[1::2] = deinterleaved[idx2]
        new_data.append(ch_data)
    return new_data


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python scale_json.py file.json")
        exit()
    filename = sys.argv[1]
    scale_json(filename)

Loading the peak data

You can now load the long_clip.json file with the peaks data and pass it to wavesurfer.js:


fetch('../long_clip.json')
.then(response => {
    if (!response.ok) {
        throw new Error("HTTP error " + response.status);
    }
    return response.json();
})
.then(peaks => {
    console.log('loaded peaks! sample_rate: ' + peaks.sample_rate);

    // load peaks into wavesurfer.js
    wavesurfer.load(mediaElt, peaks.data);
})
.catch((e) => {
    console.error('error', e);
});

Fork me on GitHub