From 510e5ccc90ec1d29fbf4677289aebede866c74ce Mon Sep 17 00:00:00 2001 From: Mark Hills Date: Mon, 5 Dec 2011 21:45:34 +0000 Subject: [PATCH] Updates --- Makefile | 3 ++ tempo.c | 142 ++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 111 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index 8ff73b8..3065349 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ +-include .config + CFLAGS += -Wall -MMD +LDLIBS += -lm tempo: tempo.o diff --git a/tempo.c b/tempo.c index 2ff9122..f777731 100644 --- a/tempo.c +++ b/tempo.c @@ -1,7 +1,13 @@ #include #include +#include #include #include +#include +#include + +#define BANNER "tempo 0.1 (C) Copyright 2011 Mark Hills " +#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; }