Jay Dolan wrote:

--- Simon Jenkins <[EMAIL PROTECTED]> wrote:

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.

ok:

~ 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;
}



Reply via email to