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.
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).
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.
long_clip.mp3
:audiowaveform -i long_clip.mp3 -o long_clip.json --pixels-per-second 20 --bits 8
long_clip.mp3
:audiowaveform -i long_clip.mp3 -o long_clip.json --pixels-per-second 20 --bits 8 --split-channels
var wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
progressColor: 'purple'
normalize: true,
});
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)
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);
});