From: Rudolf Polzer Date: Tue, 11 Oct 2011 19:21:42 +0000 (+0200) Subject: add a primitive tuba loopnote maker for timidity to loopnote conversion X-Git-Tag: xonotic-v0.6.0~20 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=7cea0e93f3d5400f1a6463e0aa8aff9ee0f916d3;p=xonotic%2Fmediasource.git add a primitive tuba loopnote maker for timidity to loopnote conversion --- diff --git a/sound/weapons/loopfinder/findloop.c b/sound/weapons/loopfinder/findloop.c new file mode 100644 index 0000000..661f41d --- /dev/null +++ b/sound/weapons/loopfinder/findloop.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIN(a,b) ((a)<(b) ? (a) : (b)) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) + +void doFourier(double *input, int channels, sf_count_t len, fftw_complex *output) +{ + static sf_count_t len0; + static int channels0; + static double *windowedData; + static fftw_plan planlos; + static int planned = 0; + if(!planned) + { + len0 = len; + channels0 = channels; + windowedData = fftw_malloc(sizeof(double) * channels * len); + + int l = len; + fprintf(stderr, "Planning...\n"); + planlos = fftw_plan_many_dft_r2c(1, &l, channels, + windowedData, NULL, channels, 1, + output, NULL, channels, 1, + FFTW_MEASURE | FFTW_PRESERVE_INPUT | FFTW_UNALIGNED); + planned = 1; + } + assert(len0 == len); + assert(channels0 == channels); + + sf_count_t nmax = channels * len; + sf_count_t i; + for(i = 0; i < nmax; ++i) + { + double ang = (i/channels) * 2 * M_PI / (len - 1); + windowedData[i] = input[i] * ( + 0.42 + - 0.5 * cos(ang) + + 0.08 * cos(2 * ang) + ); // Blackman + } + + fftw_execute_dft_r2c(planlos, windowedData, output); +} + +double vectorDot(fftw_complex *v1, fftw_complex *v2, size_t length) +{ + size_t i; + double sum = 0; + for(i = 0; i < length; ++i) + sum += cabs(v1[i]) * cabs(v2[i]); + return sum; +} + +sf_count_t findMaximumSingle(double (*func) (sf_count_t), sf_count_t x0, sf_count_t x1, sf_count_t step) +{ + sf_count_t bestpos = x1; + double best = func(x1); + + sf_count_t i; + + for(i = x0 + step; i < x1; i += step) + { + double cur = func(i); + if(cur > best) + { + bestpos = i; + best = cur; + } + } + + return bestpos; +} + +sf_count_t findMaximum(double (*func) (sf_count_t), sf_count_t x0, sf_count_t xgm, sf_count_t x1) +{ + sf_count_t xg, xg2, xg0, xg20; + + xg0 = xg = MAX(x0, xgm - 65536); + xg20 = xg2 = MIN(xgm + 65536, x1); + + for(;;) + { + sf_count_t size = xg2 - xg; + if(size == 0) + break; + fprintf(stderr, "round:\n"); + sf_count_t bestguess = findMaximumSingle(func, xg, xg2, (xg2 - xg) / 16 + 1); + xg = MAX(x0, bestguess - size / 3); + xg2 = MIN(bestguess + size / 3, x1); + } + + if(xg - xg0 < (xg20 - xg0) / 16) + fprintf(stderr, "warning: best match very close to left margin, maybe decrease the guess?\n"); + + if(xg20 - xg < (xg20 - xg0) / 16) + fprintf(stderr, "warning: best match very close to right margin, maybe increase the guess?\n"); + + return xg; +} + +int main(int argc, char **argv) +{ + int chans; + SF_INFO infile_info; + memset(&infile_info, 0, sizeof(infile_info)); + infile_info.format = 0; + SNDFILE *infile = sf_open(argv[1], SFM_READ, &infile_info); + if(!infile) + err(1, "open"); + fprintf(stderr, "Input file has %ld frames, %d Hz, %d channels, format %08x, %d sections and is %s\n", + (long) infile_info.frames, + infile_info.samplerate, + infile_info.channels, + infile_info.format, + infile_info.sections, + infile_info.seekable ? "seekable" : "not seekable"); + chans = infile_info.channels; + + sf_count_t size = MIN(infile_info.frames, strtod(argv[3], NULL) * infile_info.samplerate); + sf_count_t guess = strtod(argv[4], NULL) * infile_info.samplerate; + int channels = infile_info.channels; + size_t fftsize = atoi(argv[2]); + size_t ndata = channels * (fftsize/2 + 1); + + /* + size_t ndata_lowpass = channels * (ndata / channels / 512); // 43 Hz + size_t ndata_highpass = channels * (ndata / channels / 4); // 5512 Hz + // simplifies the dot product and makes the target function more smooth + */ + size_t ndata_lowpass = 0; + //size_t ndata_highpass = channels * (ndata / channels / 4); // 5512 Hz + size_t ndata_highpass = ndata; + + if(size < guess + 2 * (sf_count_t) fftsize) + err(1, "sound file too small (maybe reduce fftsize?)"); + + if(guess < fftsize) + err(1, "guess too close to file start (maybe reduce fftsize?)"); + + fprintf(stderr, "Using %ld frames of %d channels, guess at %ld, %d FFT size -> %d FFT result size\n", (long) size, channels, (long) guess, (int) fftsize, (int) ndata); + + fftw_complex *data_end = fftw_malloc(sizeof(fftw_complex) * ndata); + fftw_complex *data_cur = fftw_malloc(sizeof(fftw_complex) * ndata); + + double *sbuf = malloc(sizeof(double) * (size * channels)); + if(size != sf_readf_double(infile, sbuf, size)) + errx(1, "sf_read_double"); + sf_close(infile); + + doFourier(sbuf + (size - fftsize) * chans, chans, fftsize, data_end); + fprintf(stderr, "end transformed.\n"); + + double sxx = vectorDot(data_end + ndata_lowpass, data_end + ndata_lowpass, ndata_highpass - ndata_lowpass); + if(sxx == 0) + errx(1, "ends with silence... use another end point"); + +#if 0 + { + sf_count_t i, j; + double sum[ndata]; + double diffsum[ndata]; + fftw_complex save[ndata]; + for(i = guess; i < size - fftsize; ++i) + { + doFourier(sbuf + i * channels, channels, fftsize, data_cur); + for(j = 0; j < ndata; ++j) + { + fftw_complex x = data_cur[j]; + if(i != 0) + { + fftw_complex y = save[j]; + sum[j] += cabs(x); + diffsum[j] += cabs(y - x); + } + save[j] = x; + } + fprintf(stderr, "at position %d\n", (int) i); + } + for(j = 0; j < ndata; ++j) + printf("%d %.9f %.9f\n", j, sum[j], diffsum[j]); + return 0; + } +#endif + + double similarityAt(sf_count_t i) + { + // A trampoline! Wheeeeeeeeeew! + doFourier(sbuf + i * channels, channels, fftsize, data_cur); + double sxy = vectorDot(data_end + ndata_lowpass, data_cur + ndata_lowpass, ndata_highpass - ndata_lowpass); + double syy = vectorDot(data_cur + ndata_lowpass, data_cur + ndata_lowpass, ndata_highpass - ndata_lowpass); + double v = syy ? ((sxy*sxy) / (sxx*syy)) : -1; + fprintf(stderr, "Evaluated at %.9f: %f\n", i / (double) infile_info.samplerate, v); + return v; + } + +#if 0 + { + sf_count_t i; + for(i = guess; i < size - 2 * fftsize; ++i) + { + printf("%.9f %f\n", i / (double) infile_info.samplerate, similarityAt(i)); + } + return 0; + } +#endif + + sf_count_t best = findMaximum(similarityAt, 0, guess - fftsize, size - 2 * fftsize); + fprintf(stderr, "Result: %.9f (sample %ld)\n", (best + fftsize) / (double) infile_info.samplerate, (long) (best + fftsize)); + + // Now write it! + + // 1. Crossfading end to our start sample + fprintf(stderr, "Crossfading...\n"); + sf_count_t i; + int j; + double p = 2; + for(j = 0; j < channels; ++j) + for(i = 0; i < fftsize; ++i) + { + double f1 = pow(fftsize - i, p); + double f2 = pow(i, p); + sbuf[(size - fftsize + i) * channels + j] = + ( + sbuf[(size - fftsize + i) * channels + j] * f1 + + + sbuf[(best + i) * channels + j] * f2 + ) / (f1 + f2); + } + + // 2. Open sound file + fprintf(stderr, "Opening...\n"); + SNDFILE *outfile = sf_open(argv[5], SFM_WRITE, &infile_info); + if(!outfile) + err(1, "open"); + + fprintf(stderr, "Writing...\n"); + // 1. Write samples from start to just before start of our end FFT + sf_writef_double(outfile, sbuf, size); + + fprintf(stderr, "Closing...\n"); + sf_close(outfile); + + printf("%ld %.9f\n", (long) (best + fftsize), (best + fftsize) / (double) infile_info.samplerate); + + return 0; +} diff --git a/sound/weapons/loopfinder/findloop.sh b/sound/weapons/loopfinder/findloop.sh new file mode 100755 index 0000000..ebbdb20 --- /dev/null +++ b/sound/weapons/loopfinder/findloop.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +infile=$1 +outfile=$2 +end=$3 +guess=$4 +fftsize=${5:-32768} + +gcc -o findloop findloop.c -lfftw3 -Wall -Wextra -ffast-math -lsndfile -O3 +./findloop "$infile" "$fftsize" "$end" "$guess" temp.wav | while read -r SAMPLES SECONDS; do + oggenc -q 3 -o "$outfile" -c LOOP_START=$SAMPLES temp.wav + rm -f temp.wav + echo "To repeat, seek to $SAMPLES" + vorbisgain "$outfile" +done diff --git a/sound/weapons/tuba_loopnote_maker.mid b/sound/weapons/tuba_loopnote_maker.mid new file mode 100644 index 0000000..14cc448 Binary files /dev/null and b/sound/weapons/tuba_loopnote_maker.mid differ diff --git a/sound/weapons/tuba_loopnote_maker.sh b/sound/weapons/tuba_loopnote_maker.sh new file mode 100755 index 0000000..2bdb3c1 --- /dev/null +++ b/sound/weapons/tuba_loopnote_maker.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +set -ex + +( + cd loopfinder + gcc -o findloop findloop.c -lfftw3 -Wall -Wextra -ffast-math -lsndfile -O3 +) + +synth=$1; shift +program=$1; shift +tubaid=$1; shift + +case "$synth" in + timidity) + timidity -Ow -EI$program "$@" -o out.wav tuba_loopnote_maker.mid + normalize out.wav + ;; + fluidsynth) + { + echo "program 0 $program" + } > in.cfg + fluidsynth -f in.cfg -n -i -l -T wav -F out.wav "$@" tuba_loopnote_maker.mid + normalize out.wav + ;; +esac + +start=0 +step=4 +loop=1.0 +len=1.5 +for note in -18 -12 -6 0 6 12 18 24; do + sox out.wav n$note.wav \ + trim $start $step \ + silence 1 1s 0 + + fn=tuba"$tubaid"_loopnote"$note".ogg + + # now find loop point + loopfinder/findloop n$note.wav 4096 $len $loop t$note.wav | while read -r SAMPLES SECONDS; do + oggenc -q9 -o "$fn" -c "LOOP_START=$SAMPLES" t$note.wav + done + + # next! + start=$(($start+$step)) +done