]> git.xonotic.org Git - xonotic/mediasource.git/commitdiff
add a primitive tuba loopnote maker for timidity to loopnote conversion
authorRudolf Polzer <divverent@xonotic.org>
Tue, 11 Oct 2011 19:21:42 +0000 (21:21 +0200)
committerRudolf Polzer <divverent@xonotic.org>
Tue, 11 Oct 2011 19:21:42 +0000 (21:21 +0200)
sound/weapons/loopfinder/findloop.c [new file with mode: 0644]
sound/weapons/loopfinder/findloop.sh [new file with mode: 0755]
sound/weapons/tuba_loopnote_maker.mid [new file with mode: 0644]
sound/weapons/tuba_loopnote_maker.sh [new file with mode: 0755]

diff --git a/sound/weapons/loopfinder/findloop.c b/sound/weapons/loopfinder/findloop.c
new file mode 100644 (file)
index 0000000..661f41d
--- /dev/null
@@ -0,0 +1,256 @@
+#include <stdio.h>
+#include <complex.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <fftw3.h>
+#include <unistd.h>
+#include <err.h>
+#include <assert.h>
+#include <sndfile.h>
+#include <sys/types.h>
+
+#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 (executable)
index 0000000..ebbdb20
--- /dev/null
@@ -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 (file)
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 (executable)
index 0000000..2bdb3c1
--- /dev/null
@@ -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