This commit is contained in:
Mark Hills 2011-12-05 21:45:34 +00:00
parent e324be7268
commit 510e5ccc90
2 changed files with 111 additions and 34 deletions

View File

@ -1,4 +1,7 @@
-include .config
CFLAGS += -Wall -MMD
LDLIBS += -lm
tempo: tempo.o

142
tempo.c
View File

@ -1,7 +1,13 @@
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#define BANNER "tempo 0.1 (C) Copyright 2011 Mark Hills <mark@pogo.org.uk>"
#define NAME "tempo"
#define RATE 44100 /* of input data */
@ -13,27 +19,21 @@
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
/*
* Sample from the metered energy with interpolation
* Sample from the metered energy
*
* No need to interpolate and it makes a tiny amount of difference; we
* take a random sample of samples, any errors are averaged out.
*/
static double sample(float nrg[], size_t len, double offset)
{
double f, a, b;
size_t n;
double n;
size_t i;
n = (size_t)offset;
n = floor(offset);
i = (size_t)n;
f = offset - n;
if (f == 0.0)
return (n < len) ? nrg[n] : 0.0;
/* Linear interpolation */
a = (n < len) ? nrg[n] : 0.0;
n++;
b = (n < len) ? nrg[n] : 0.0;
return (1.0 - f) * a + f * b;
return (n >= 0.0 && n < (double)len) ? nrg[i] : 0.0;
}
/*
@ -43,22 +43,28 @@ static double sample(float nrg[], size_t len, double offset)
double autodifference(float nrg[], size_t len, double interval)
{
size_t n;
double mid, v, diff = 0.0;
static const int beats[] = { -32, -16, -8, -4, -4, -4, -2, -2, -1,
1, 2, 2, 4, 4, 4, 8, 16, 32 };
double mid, v, diff;
static const double beats[] = { -32, -16, -8, -4, -2, -1, 1, 2, 4, 8, 16, 32 },
nobeats[] = { -0.5, -0.25, 0.25, 0.5 };
mid = drand() * len;
v = sample(nrg, len, mid);
diff = 0.0;
for (n = 0; n < ARRAY_SIZE(beats); n++) {
int beat;
double y;
beat = beats[n];
y = sample(nrg, len, mid + beat * interval);
y = sample(nrg, len, mid + beats[n] * interval);
diff += fabs(y - v);
}
for (n = 0; n < ARRAY_SIZE(nobeats); n++) {
double y;
y = sample(nrg, len, mid + nobeats[n] * interval);
diff -= fabs(y - v);
}
return diff;
}
@ -96,7 +102,8 @@ double interval_to_bpm(double interval)
double scan_for_bpm(float nrg[], size_t len,
double slowest, double fastest,
unsigned int steps,
unsigned int samples)
unsigned int samples,
bool graph)
{
double step, interval, trough, height;
unsigned int s;
@ -107,17 +114,16 @@ double scan_for_bpm(float nrg[], size_t len,
height = INFINITY;
for (;;) {
for (interval = fastest; interval <= slowest; interval += step) {
double t;
interval += step;
if (interval > slowest)
break;
t = 0.0;
for (s = 0; s < samples; s++)
t += autodifference(nrg, len, interval);
if (graph)
printf("%lf\t%lf\n", interval_to_bpm(interval), t);
/* Track the lowest value */
if (t < height) {
@ -129,13 +135,70 @@ double scan_for_bpm(float nrg[], size_t len,
return interval_to_bpm(trough - (step / 2));
}
void usage(FILE *f)
{
fprintf(f, "Usage: " NAME " [options]\n"
"Analyse the tempo (in beats-per-minute, BPM) of incoming audio\n\n"
" -g Output autodifference graph to stdout\n"
" -f Print format for final BPM value (default \"%%0.1f\")\n"
" -v Print progress information to stderr\n"
" -h Display this help message and exit\n\n");
fprintf(f, "Incoming audio is raw audio at %dHz, mono, 32-bit float; eg.\n"
" sox file.mp3 -t raw -r %d -e float -c 1 | ./" NAME "\n",
RATE, RATE);
}
int main(int argc, char *argv[])
{
float *nrg = NULL;
size_t len = 0, buf = 0;
off_t n = 0;
double bpm;
float v = 0.0;
float bpm, v = 0.0;
const char *format = "%0.1f";
bool verbose = false;
for (;;) {
int c;
c = getopt(argc, argv, "vf:gh");
if (c == -1)
break;
switch (c) {
case 'v':
verbose = true;
break;
case 'f':
format = optarg;
break;
case 'g':
format = NULL;
break;
case 'h':
usage(stdout);
return 0;
default:
return EX_USAGE;
}
}
argv += optind;
argc -= optind;
if (argc > 0) {
fprintf(stderr, "%s: Too many arguments\n", NAME);
return EX_USAGE;
}
if (verbose) {
fputs(BANNER "\n", stderr);
fprintf(stderr, "Metering incoming audio\n");
}
for (;;) {
float z;
@ -164,21 +227,32 @@ int main(int argc, char *argv[])
if (len == buf) {
size_t n;
fprintf(stderr, "Reallocating at %zd\n", len);
n = buf + BLOCK;
nrg = realloc(nrg, n * sizeof(*nrg));
assert(nrg != NULL);
if (nrg == NULL) {
perror("realloc");
return -1;
}
buf = n;
}
nrg[len++] = v;
}
bpm = scan_for_bpm(nrg, len, 60.0, 180.0, 400, 1000);
printf("BPM = %f\n", bpm);
if (verbose)
fprintf(stderr, "Sampling tempo range\n");
bpm = scan_for_bpm(nrg, len, 60.0, 180.0, 400, 1000, (format == NULL));
if (format != NULL) {
printf(format, bpm);
putc('\n', stdout);
}
free(nrg);
if (verbose)
fprintf(stderr, "Done\n");
return 0;
}