Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pabloko/1add07391f6bdccc9de99d1a9fa17ddf to your computer and use it in GitHub Desktop.
Save pabloko/1add07391f6bdccc9de99d1a9fa17ddf to your computer and use it in GitHub Desktop.
(PHP) Pre-processing MP3 Audio waveforms with big size

What?

When want to display audio waveform from audio file, you can find browser libraries like WavesurferJs, Squiggl, or whatever, everyone works the same:

  1. Load audio (in full) file and get its AudioContext
  2. Get all the raw PCM samples from one channel in Float32Array (huge) with something like ctx.decodeAudioData().getChannelData(0)
  3. Use drawing routine and average samples

Doing this process on the users browser is quite slow and wasteful in a magnitude proportional to the audio file size, as you have to fully load it then get huge amount of samples calculated in the drawing stage.

So one option to overcome this is to dump a shorter version of PCM data on a static file with an amount of samples enough to render the waveform and having a reasonable size by averaging the samples.

How?

We will replace the 2 first stages of waveform generation from Javascript Audio APIs, so the first is to emulate what this process does, getting a full channel float32 samples from audio file, for this task we can use SoX, if you dont have it yet apt install sox apt install libsox-fmt-mp3 #to add mp3 lame support So lets get some buffer: sox wf.mp3 -c 1 -t f32 - > wf.bin We can load this file with ajax and read a Float32Array

So we have now a file that weights a lot more than original mp3 file (8mb mp3->72mb waveform) So lets average it on, for example 1000 samples, enough to render the waveform and that will weight 4kb (lighter than your average image)

To me seems fair size and you can still gzip the file with gzcompress to serve it even smaller.

The script works launching a sox process with shell_exec like the described above, then reading the samples from stdout to a variable, converting it to float32 samples with unpack and averaging the samples. Script will produce a file with extension .waveform with packed averaged measurements.

<?php
$nb_samples=1000; //4kb waveform
$in_file = 'wf.mp3'; //input file
function result($k,$v=null) { //ending routine
global $in_file;
die(json_encode(['result'=>$k,'resultvalue'=>$v,'file'=>$in_file], JSON_PRETTY_PRINT));
}
if (!file_exists($in_file)) result('error','notfound');
$out=shell_exec('sox "'.$in_file.'" -c 1 -t f32 -'); //get 1x full channel
$pcm=unpack('f*',$out); //read float32 pcm samples from sox buffer
$pcmo=[];$nc_samples=count($pcm);$nb_steps=floor($nc_samples/$nb_samples);
function add_pcm_sample($i) { //Add a sample to output
global $pcm,$pcmo,$nb_samples,$nc_samples,$nb_steps; $fl = 0.0;
for($k=$i*$nb_steps; $k<($i*$nb_steps)+$nb_steps; $k++)
$fl+=pow($pcm[$k+1],2);
$pcmo[]=sqrt($fl/$nc_samples);
}
for ($i=0; $i < $nb_samples; $i++) //Get each output sample
add_pcm_sample($i);
file_put_contents($in_file.".waveform", pack('f*',...$pcmo));
result('ok');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment