tomwalters@0: /* tomwalters@0: FFT program adapted from routines given in Numerical Recipes. tomwalters@0: tomwalters@0: The output framewidth = ( input framewidth + padding ) / 2 spectral points. tomwalters@0: tomwalters@0: Padding with zeroes is recommended to counter end-effects (See NR, p416). tomwalters@0: It is also necessary since framewidth + padding must be a power of 2 tomwalters@0: (sample points at given rate). tomwalters@0: This is because the fft method is "radix-2", and because the output is tomwalters@0: symmetrical so that just half the points are unique. tomwalters@0: Therefore each input frame is padded with zeroes to the next power of 2 tomwalters@0: larger than the input framewidth. tomwalters@0: tomwalters@0: If necessary, extra padding can be enforced using the padding option to tomwalters@0: add extra zeroes, padding to a larger power of 2. tomwalters@0: The amount of extra padding is "exponential", expanding the basic size to: tomwalters@0: ( framewidth + padding ) * 2**n tomwalters@0: where the padding option is n. tomwalters@0: (n=0 by default, so that no extra padding is added. When n=1 then padding is tomwalters@0: added to double the size, and when n=2 the size is quadrupled, etc.). tomwalters@0: tomwalters@0: Frames are selected from the input stream using the "frames" option, tomwalters@0: which has syntax: [[-]frame=a[-b]]. Input frames are numbered 1,2,... tomwalters@0: For example: tomwalters@0: frame=a Select just the a'th frame. tomwalters@0: frame=a-b Select frames from the a'th to b'th inclusive. tomwalters@0: The strings "min" and "max" can be used as specifiers, meaning eg: tomwalters@0: frame=min Select the first frame. tomwalters@0: frame=max Select the last frame. tomwalters@0: frame=a-max Select frames from the a'th to the last inclusive. tomwalters@0: frame=min-b Select frames from the first to the b'th inclusive. tomwalters@0: The default selects all frames, (ie frame=min-max). tomwalters@0: tomwalters@0: Several forms of fft processing are provided, according to the "spectrum" tomwalters@0: option: log/magnitude/phase/complex/inverse/verbose. tomwalters@0: tomwalters@0: Log and magnitude are both magnitude spectra (log is log10 of magnitude). tomwalters@0: Phase is the phase spectrum. tomwalters@0: Complex is the full complex spectrum, in pairs. tomwalters@0: Inverse transform reads framewidth numbers which are interpreted as tomwalters@0: pairs, (ie framewidth/2 complex numbers), tomwalters@0: and outputs the inverse transform scaled by 1/framewidth. tomwalters@0: Verbose prints the spectrum in ASCII on the stdout. tomwalters@0: tomwalters@0: tomwalters@0: Examples: tomwalters@0: tomwalters@0: 1. To print the input and output frame sizes in sample points, eg for a tomwalters@0: subsequent plotting program, use the size option: tomwalters@0: tomwalters@0: fft ... size=on tomwalters@0: tomwalters@0: 2. An fft of a waveform sampled at 10kHz, computed within a frame of 12.8ms, tomwalters@0: plotting the 2nd frame in a sequence of frames with half-frame overlap. tomwalters@0: tomwalters@0: fft samp=10kHz width=12.8ms frstep=6.4ms frame=2 file | x11plot tomwalters@0: tomwalters@0: 3. An animated plot of successive fft spectra of a waveform sampled at 10kHz, tomwalters@0: each computed within a frame of 12.8ms, and shifted by 2 sample points. tomwalters@0: tomwalters@0: fft samp=10kHz width=12.8ms frstep=2p file | x11play -n64 tomwalters@0: tomwalters@0: 4. Using the complex output from fft, and inverse transform without windowing tomwalters@0: to recover original input. tomwalters@0: tomwalters@0: fft samp=10kHz frame=2 spec=complex window=off file > foo tomwalters@0: fft samp=10kHz frame=1 spec=inverse window=off foo | x11plot tomwalters@0: tomwalters@0: */ tomwalters@0: tomwalters@0: #include tomwalters@0: #include tomwalters@0: #include "options.h" tomwalters@0: #include "units.h" tomwalters@0: #include "strmatch.h" tomwalters@0: #include "sigproc.h" tomwalters@0: tomwalters@0: char applic[] = "fast Fourier transform of contiguous frames.\n i/p and o/p data in binary shorts." ; tomwalters@0: tomwalters@0: static char *helpstr, *debugstr, *sampstr, *widthstr, *padstr, *wstr, *sstr, *sizestr ; tomwalters@0: static char *startstr, *shiftstr, *framestr, *smagstr, *slogstr, *sphasestr, *echostr ; tomwalters@0: tomwalters@0: static Options option[] = { tomwalters@0: { "help" , "off" , &helpstr , "help" , DEBUG }, tomwalters@0: { "debug" , "off" , &debugstr , "debugging switch" , DEBUG }, tomwalters@0: { "samplerate", "20kHz" , &sampstr , "samplerate " , VAL }, tomwalters@0: { "start" , "0" , &startstr , "Start point in i/p file." , VAL }, tomwalters@0: { "frames" , "1-max" , &framestr , "Select frames inclusively" , VAL }, tomwalters@0: { "frstep" , "16ms" , &shiftstr , "Step between input frames." , VAL }, tomwalters@0: { "width" , "32ms" , &widthstr , "Width of input frames." , VAL }, tomwalters@0: { "padding" , "0" , &padstr , "Exponential frame padding" , SVAL }, tomwalters@0: { "window" , "on" , &wstr , "Hamming window" , SETFLAG }, tomwalters@0: { "spectrum" , "magnitude" , &sstr , "log/magnitude/phase/complex/inverse/verbose" , SETFLAG }, tomwalters@0: { "scalemag" , "0.08" , &smagstr , "scale magnitude spectrum" , SVAL }, tomwalters@0: { "scalelog" , "10" , &slogstr , "scale log spectrum" , SVAL }, tomwalters@0: { "scalephase", "100" , &sphasestr , "scale phase spectrum" , SVAL }, tomwalters@0: { "size" , "off" , &sizestr , "print input/output frame size in samples" , SETFLAG }, tomwalters@0: { "echo" , "off" , &echostr , "echo buffered input without processing" , SVAL }, tomwalters@0: ( char * ) 0 } ; tomwalters@0: tomwalters@0: tomwalters@0: int samplerate ; tomwalters@0: int width ; tomwalters@0: int step ; tomwalters@0: int zeroes ; tomwalters@0: int dowindow ; tomwalters@0: double scale ; tomwalters@0: int echo ; tomwalters@0: tomwalters@0: float *vec, *window ; tomwalters@0: short *obuf ; tomwalters@0: tomwalters@0: int allbins ; tomwalters@0: int halfbins ; tomwalters@0: tomwalters@0: #define MAG 1 /* magnitude spectrum */ tomwalters@0: #define PHASE 2 /* phase spectrum */ tomwalters@0: #define INV 3 /* inverse transform */ tomwalters@0: #define VERB 4 /* verbose (ASCII) */ tomwalters@0: #define LOG 5 /* log magnitude */ tomwalters@0: #define COMP 6 /* complex spectrum */ tomwalters@0: tomwalters@0: int output ; tomwalters@0: tomwalters@0: main (argc, argv) tomwalters@0: int argc; tomwalters@0: char **argv; tomwalters@0: { tomwalters@0: FILE *fp ; tomwalters@0: short *buf ; tomwalters@0: int a, b ; tomwalters@0: int isign ; tomwalters@0: tomwalters@0: fp = openopts( option,argc,argv ) ; tomwalters@0: if ( !isoff( helpstr ) ) tomwalters@0: helpopts( helpstr, argv[0], applic, option ) ; tomwalters@0: tomwalters@0: samplerate = to_Hz( sampstr ) ; tomwalters@0: dowindow = ison( wstr ) ; tomwalters@0: echo = ison( echostr ) ; tomwalters@0: width = to_p( widthstr, samplerate ) ; tomwalters@0: step = to_p( shiftstr, samplerate ) ; tomwalters@0: zeroes = ( getpower( width ) << atoi( padstr ) ) - width ; tomwalters@0: tomwalters@0: allbins = width + zeroes ; tomwalters@0: halfbins = allbins / 2 ; tomwalters@0: tomwalters@0: if ( iststr( sstr, "magnitude" ) ) { tomwalters@0: output = MAG ; tomwalters@0: scale = atof( smagstr ) ; tomwalters@0: isign = 1 ; tomwalters@0: } tomwalters@0: else if ( iststr( sstr, "log" ) ) { tomwalters@0: output = LOG ; tomwalters@0: scale = atof( slogstr ) ; tomwalters@0: isign = 1 ; tomwalters@0: } tomwalters@0: else if ( iststr( sstr, "phase" ) ) { tomwalters@0: output = PHASE ; tomwalters@0: scale = atof( sphasestr ) ; tomwalters@0: isign = 1 ; tomwalters@0: } tomwalters@0: else if ( iststr( sstr, "complex" ) ) { tomwalters@0: output = COMP ; tomwalters@0: isign = 1 ; tomwalters@0: } tomwalters@0: else if ( iststr( sstr, "inverse" ) ) { tomwalters@0: output = INV ; tomwalters@0: scale = 1./(double)width ; tomwalters@0: isign = (-1) ; tomwalters@0: } tomwalters@0: else if ( iststr( sstr, "verbose" ) ) { tomwalters@0: output = VERB ; tomwalters@0: isign = 1 ; tomwalters@0: } tomwalters@0: else { tomwalters@0: fprintf(stderr,"unknown spectrum \n"); tomwalters@0: exit( 1 ) ; tomwalters@0: } tomwalters@0: tomwalters@0: /* frame size printout */ tomwalters@0: tomwalters@0: if ( ison( sizestr ) ) { tomwalters@0: fprintf(stderr,"fft sizes in sample points:\n" ) ; tomwalters@0: fprintf(stderr," input frame size = %d (framewidth=%d + padding=%d)\n", width+zeroes, width, zeroes ) ; tomwalters@0: fprintf(stderr," output frame size = %d \n", (width + zeroes)/2 ) ; tomwalters@0: exit( 0 ) ; tomwalters@0: } tomwalters@0: tomwalters@0: /* parse bounds on number of frames */ tomwalters@0: tomwalters@0: if ( selector( framestr, &a, &b ) == 0 ) { tomwalters@0: fprintf(stderr,"fft: bad frame selector [%s]\n", framestr ) ; tomwalters@0: exit( 1 ) ; tomwalters@0: } tomwalters@0: tomwalters@0: /* Allocate working space */ tomwalters@0: tomwalters@0: obuf = (short *)malloc( halfbins * sizeof(short) ) ; tomwalters@0: vec = (float *)malloc( allbins * sizeof(float) ) ; tomwalters@0: if ( dowindow ) tomwalters@0: window = hamming( width ) ; tomwalters@0: tomwalters@0: tomwalters@0: /* Compute fft for each frame of width shorts in the input stream */ tomwalters@0: tomwalters@0: if ( seekbytes( fp, (int)( to_p( startstr, samplerate ) * sizeof(short) ) ) == 0 ) { tomwalters@0: fprintf(stderr,"improper seek\n") ; tomwalters@0: exit( 1 ) ; tomwalters@0: } tomwalters@0: tomwalters@0: while ( ( buf = getframe( fp, a, width, step ) ) != (short *)0 && ( a<=b || b==0 ) ) { tomwalters@0: if ( echo ) fwrite( buf, sizeof(short), width, stdout ) ; tomwalters@0: else process( buf, isign ) ; tomwalters@0: a++ ; tomwalters@0: } tomwalters@0: if ( a<=b && b>0 ) tomwalters@0: fprintf(stderr,"warning: not enough frames for request\n"); tomwalters@0: tomwalters@0: fclose( fp ) ; tomwalters@0: } tomwalters@0: tomwalters@0: tomwalters@0: process( buf, isign ) tomwalters@0: short *buf ; tomwalters@0: int isign ; tomwalters@0: { tomwalters@0: short *bptr = buf ; tomwalters@0: short *endptr = buf + width ; tomwalters@0: short *optr = obuf ; tomwalters@0: short *endbin = obuf + halfbins ; tomwalters@0: float *wptr = window ; tomwalters@0: float *vptr = vec ; tomwalters@0: float *endvec = vec + allbins ; tomwalters@0: tomwalters@0: if ( dowindow ) tomwalters@0: while ( bptr < endptr ) tomwalters@0: *vptr++ = *bptr++ * *wptr++ ; tomwalters@0: else tomwalters@0: while ( bptr < endptr ) tomwalters@0: *vptr++ = *bptr++ ; tomwalters@0: tomwalters@0: while ( vptr < endvec ) /* padding */ tomwalters@0: *vptr++ = 0 ; tomwalters@0: tomwalters@0: fft( vec, width+zeroes, isign ) ; tomwalters@0: tomwalters@0: vptr = vec ; tomwalters@0: switch ( output ) { tomwalters@0: tomwalters@0: case COMP : while ( vptr < endvec ) tomwalters@0: *optr++ = (short)( *vptr++ ) ; tomwalters@0: fwrite( obuf, sizeof(short), allbins, stdout ) ; tomwalters@0: break; tomwalters@0: case PHASE : phase( vec, allbins ) ; tomwalters@0: while ( optr < endbin ) tomwalters@0: *optr++ = (short)( scale * *vptr++ ) ; tomwalters@0: fwrite( obuf, sizeof(short), halfbins, stdout ) ; tomwalters@0: break; tomwalters@0: case MAG : mag( vec, allbins ) ; tomwalters@0: while ( optr < endbin ) tomwalters@0: *optr++ = (short)( scale * *vptr++ ) ; tomwalters@0: fwrite( obuf, sizeof(short), halfbins, stdout ) ; tomwalters@0: break; tomwalters@0: case LOG : mag( vec, allbins ) ; tomwalters@0: while ( optr < endbin ) tomwalters@0: *optr++ = (short)( scale * 10 * log10(*vptr++) ) ; tomwalters@0: fwrite( obuf, sizeof(short), halfbins, stdout ) ; tomwalters@0: break; tomwalters@0: case INV : while ( vptr < endvec ) tomwalters@0: *optr++ = (short)( scale * *vptr++ ) ; tomwalters@0: fwrite( obuf, sizeof(short), allbins, stdout ) ; tomwalters@0: break; tomwalters@0: case VERB : print_complex_spectrum( (complex *)vec, halfbins, samplerate ) ; tomwalters@0: break; tomwalters@0: tomwalters@0: } tomwalters@0: } tomwalters@0: tomwalters@0: tomwalters@0: /* tomwalters@0: Pretty-print complex k-spectrum on the stdout. tomwalters@0: */ tomwalters@0: tomwalters@0: print_complex_spectrum(C,k,samplerate) tomwalters@0: complex *C; tomwalters@0: int k, samplerate; tomwalters@0: { tomwalters@0: float res; /* frequency resolution. */ tomwalters@0: int i; tomwalters@0: tomwalters@0: res = (float)(samplerate>>1) / (float)k; tomwalters@0: for (i=0; i