--- Simon Jenkins <[EMAIL PROTECTED]> wrote:ok:
Try this? [...]
Definitely closer! I'm embarassed to admit how long I've looked at this code and didn't see that I was initializing lowestDiffFound (was previousDiff) to 0, doh!
It's now returning 120.0 (g_minBpm) for every file, tho. So there's still a problem somewhere. Perhaps in phaseShift? I've re-attached the improved version. Thank you for your help.
~ Downsampling by 50??? That's *got* to require some filtering. In keeping with your need for speed - not to mention my lack of DSP guru-head - i've implemented the crudest imagineable.
~ fseek()-ing the pipe wasn't working. I've removed the first fseek(), but that means that g_startTime is no longer honoured and must be left at zero. The (ahem) "filter" i added means the second fseek() is no longer required.
~ A couple of math corrections.
~ Widened expected bpm range: It now looks for 100 - 199 bpm. You should probably narrow that back down again if you know your source is in a narrower range.
I actually ran it a couple of times and it seems to produce plausible results now though i don't have the exact bpms for the mp3s i tried. Also, my test mp3s contained looped samples and/or programmed drum parts. I'm not sure this algorithm will work so well (or at all for that matter) on less regular material.
Simon Jenkins (Bristol, UK)
/** * (C) 2004 Jay Dolan <[EMAIL PROTECTED]> * * Additional credits: * Simon Jenkins <[EMAIL PROTECTED]> * * bpm.c: Calculate the beats per minute of an MP3 file. Or try. * * This software is provided free of charge, and with no warranty. * For more information, see the GNU General Public License. */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
typedef FILE Wav;
int g_sampleRate = 44100; //expected input sample rate
int g_channels = 2; //expected input sample channles (stereo)
int g_downsampleFactor = 50; //original sample will be downsampled to 1/n
int g_startTime = 0; //start analyzing at n seconds
int g_endTime = 10; //stop analyzing at n seconds
int g_minBpm = 100; //minimum bpm to check for
int g_maxBpm = 199; //maximum bpm to check for
/**
* Returns a pointer to a wav file in memory, created by mpg123. The format of the wav file
* is raw (headerless) linear PCM, 16 bit, stereo, host byte order.
*/
Wav *getWav(char *p_fileName){
char command[1024];
Wav *wav;
int i;
if((wav = fopen(p_fileName, "r")) == NULL){ //ensure file exists
fprintf(stderr, "Unable to open file \"%s\"\n", p_fileName);
exit(0);
}
fclose(wav);
snprintf(command, 1024, "mpg123 -s \"%s\" 2> /dev/null", p_fileName); //construct command
if((wav = popen(command, "r")) == NULL){ //open pipe to command
fprintf(stderr, "Unable to execute command \"%s\"\n", command);
exit(0);
}
return wav;
}
/**
* Returns the average difference in amplitude per sample between p_audio, and p_audio shifted
* at p_shift samples. In theory, a small return value indicates that p_shift is nearing the samples
* per measure of p_audio.
*/
int phaseShift(short *p_audio, int p_samples, int p_shift){
int i, diff = 0;
for(i = 0; i < p_samples - p_shift; i++) {
diff += abs(p_audio[i] - p_audio[i + p_shift]);
}
return diff / (p_samples - p_shift);
}
/**
* Returns the beats per minute of p_wav as a float using phase shifting.
* A downsampled "copy" of p_wav is created for performance reasons.
* A phase shift is then applied over the audio data between g_startTime
* and g_endTime incrementally from g_minBpm to g_maxBpm. At each
* increment, the amplitude difference between the original and shifted
* version is compared to find the best-fit phase shift. This shift then
* yields the BPM.
*/
float getBpm(Wav *p_wav){
int bufSize = g_downsampleFactor * g_channels;
short buf[bufSize], *audio;
int i, j, average, shift, diff, minDiff; float k, bpm;
int sampleRate = g_sampleRate / g_downsampleFactor; //sample rate of buffer
int samples = (g_endTime - g_startTime) * sampleRate; //number of samples to be analyzed
audio = malloc(sizeof(short) * samples); //allocate buffer for amplitude mean
for(i = 0; i < samples; i++){ //read p_wav and construct audio
if(fread(buf, sizeof(short), bufSize, p_wav) != bufSize) //read buffer's worth of data
break;
average = 0;
for( j = 0; j < bufSize; j++ ) {
average += abs( buf[j] );
}
average /= bufSize;
audio[i] = (short) average;
}
if(i < samples){ //allow a smaller set of samples than desired, but warn
fprintf(stderr, "Warning: Using only %i samples\n", i);
samples = i;
}
for(k = g_minBpm; k <= g_maxBpm; k += .1){ //apply phase shift for every .1 bpm
shift = sampleRate * (60.0 / k) * 4; //calculate 4 beat shift for current k
diff = phaseShift(audio, samples, shift); //apply phase shift
if(k == g_minBpm || diff < minDiff){ //record minimum diff and bpm
bpm = k; minDiff = diff;
}
}
free(audio);
return bpm;
}
/**
* Returns 0 after printing the file's BPM to stdout.
*/
int main(int argc, char *args[]){
Wav *wav;
if(getopt(argc, args, "v") != -1){ //print version and exit
printf("bpm 0.1, [EMAIL PROTECTED]");
exit(0);
}
if(getopt(argc, args, "s:") != -1){ //start time specified, resolve end time
g_startTime = atoi(optarg);
if(getopt(argc, args, "e:") != -1)
g_endTime = atoi(optarg);
else g_endTime = g_startTime + 10;
}
if(g_startTime >= g_endTime){ //invalid options, show usage
fprintf(stderr, "Usage: %s [-s seconds [-e seconds]] file\n", args[0]);
exit(1);
}
wav = getWav(args[optind]); //convert the file to .wav
printf("%f\n", getBpm(wav)); //analyze .wav
pclose(wav); //close pipe
return 0;
}
