[comp.sources.misc] v04i028: An FFT Program

sampson@killer.DALLAS.TX.US.UUCP (Steve Sampson) (08/15/88)

Posting-number: Volume 4, Issue 28
Submitted-by: "Steve Sampson" <sampson@killer.DALLAS.TX.US.UUCP>
Archive-name: fft.port

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  readme.doc fft.c gen_sine.c gen_pulse.c
# Wrapped by sampson@killer on Mon Aug 15 04:27:11 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f readme.doc -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"readme.doc\"
else
echo shar: Extracting \"readme.doc\" \(5214 characters\)
sed "s/^X//" >readme.doc <<'END_OF_readme.doc'
XReadme.doc  15 August 1988
X
X+----------------------------------------------------------------------------+
X| Thanks for the feedback on the original posting, here's an archive with all|
X| the known bugs fixed, and some improvements. [srs]			     |
X|									     |
X| This has compiled and run on Turbo-C V1.5, MS-C V5.0 and Unix SV3.1	     |
X|									     |
X| If your wondering what all the castings about, I had a hell of a time :-)  |
X| It works I ain't touching it (smile).  But yes, it's probably overkill.    |
X| The last killer bug was that Unix seems to truncate on an (int) cast, and  |
X| Turbo-C and MS-C seem to round.  At least that's what I decided, so there. |
X+----------------------------------------------------------------------------+
X
XI originally saw an FFT program in Byte Magazine many years ago.  I wrote
Xa version for BASIC that worked pretty good.  Then I thought I'd translate
Xit into C.  These programs are the result.
X
XNeeds a graphic interface.  I'm interested in any better graphic versions.
X
XThe original Byte Magazine program was designed for real data only.  In my
Xexperiments I needed to preserve both real and imaginary data.  If you feed
Xthe FFT real data only, then the output will be a mirror image, and you can
Xignore the left side.  Two signal generators are included.  One generates
Xsine waves (gen_sine) and the other generates pulses (gen_pulse).
X
XFor an example try:
X
X	gen_sine 16 in
X	1000
X	3000
X
XWhich will sample the 1 Khz data every 333 microseconds (1 / 3 Khz).
XNote: The sample frequency should be greater than 2 times the input
Xfrequency (Nyquist and all that...).
X
XThen run fft like so:
X
X	fft 16 in out
X
XAnd you should see a display like so:
X
X0	|=======				      (-1500.0 Hz)
X1	|=====					      (-1312.5 Hz)
X2	|====					      (-1125.0 Hz)
X3	|====					       (-937.0 Hz)
X4	|===					       (-750.0 Hz)
X5	|===					       (-562.5 Hz)
X6	|===					       (-375.0 Hz)
X7	|===					       (-187.5 Hz)
X8	|====		<-------   DC			(000.0 Hz)
X9	|====		<-------   Fundamental		(187.5 Hz)
X10	|======		<-------   Second Harmonic	(375.0 Hz)
X11	|========					(562.5 Hz)
X12	|==============					(750.0 Hz)
X13	|========================================================
X14	|============================		    (1125.0 Hz)	^
X15	|===========				    (1312.5 Hz)	|
X								|
X				[13 - 8 (center)] * 187.5 = 937.0 Hz
X
XThe fundamental display frequency is:
X
X	T  = Time Increment Between Samples
X	N  = Number Of Samples
X	Tp = N * T
X
X	Then F = 1 / Tp
X
X	In the example above, the time increment between samples is
X	1 / 3000 or 333 microseconds.  N = 16, so Tp = 5333 microseconds
X	and 1 / .005333 is 187.5 Hz.
X
X	Therefore each filter is a multiple of 187.5 Hertz.  Filter 8 in this
X	example is center, so that would be zero, 9 would be one, etc.
X
XIn this case the sample interval didn't work so good for the frequency and
Xshows the limitation of the Discrete Fourier Transform in representing a
Xcontinuous signal.  A better sample rate for 1000 Hz would be 4000 Hz,
Xin which case T = 250 ms, Tp = 4 ms, and F = 250 Hz.  1000 / 250 = 4.  The
Xpower should all be in filter 12 (8 + 4) in this case.
X
XLet's run it and see:
X
X	gen_sine 16 in
X	1000
X	4000
X
X	fft 16 in out
X
X0	|
X1	|
X2	|
X3	|
X4	|
X5	|
X6	|
X7	|
X8	|
X9	|
X10	|
X11	|
X12	|========================================================
X13	|
X14	|
X15	|
X
XWell what do you know...
X
XThe output data file data can be used by other programs as needed.
X
XBy using negative frequencies in gen_sine you can generate opening targets:
X
X	gen_sine 16 in
X	-1000
X	3000
X	fft 16 in out
X
XProduces:
X
X0	|=======
X1	|===========
X2	|============================
X3	|=======================================================
X4	|==============
X5	|========
X6	|======
X7	|====
X8	|====		<-------- Zero Hertz (DC)
X9	|===
X10	|===
X11	|===
X12	|===
X13	|====
X14	|====
X15	|=====
X
XYou can see in these examples where weighting functions would be nice.
X
XFor an example of what happens when the imaginary data is not input
X(ie, zeros put in) for a 1000 Hz frequency at 3000 Hz sample rate:
X
X0	|===============
X1	|==================
X2	|===================================
X3	|========================================================
X4	|===========
X5	|====
X6	|==
X7	|=				Trash this part
X---------------------------------------------------------------------
X8	|
X9	|=
X10	|==
X11	|====
X12	|===========
X13	|=======================================================
X14	|===================================
X15	|==================
X
XThe left side is redundant and can be deleted.  This is what the original
XByte Magazine article did (December 1978 Issue).
X
XFor generating pulses, a second program gen_pulse is provided.  It pre-loads
Ximaginary data with zeros.   For example:
X
X	gen_pulse 16 in
X	.000006
X	.0000008
X
XIs a radar with a 6 microsecond pulse and 800 nanosecond range gates.
X
X	fft 16 in out
X
XWill produce:
X
X0	|
X1	|=======
X2	|
X3	|========
X4	|
X5	|============
X6	|
X7	|===================================
X8	|========================================================
X9	|===================================
X10	|
X11	|============
X12	|
X13	|========
X14	|
X15	|=======
X
XThe more filters you use, the prettier it looks.  Good luck, have fun with it.
XSteve Sampson, CompuServe: 75136,626  Unix: sampson@killer.dallas.tx.us
END_OF_readme.doc
if test 5214 -ne `wc -c <readme.doc`; then
    echo shar: \"readme.doc\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f fft.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"fft.c\"
else
echo shar: Extracting \"fft.c\" \(7978 characters\)
sed "s/^X//" >fft.c <<'END_OF_fft.c'
X/*
X *	fft.c
X *
X *	C Version 1.6 by Steve Sampson, Public Domain, 15 August 1988
X *
X *	This program is based on the work by W. D. Stanley and S. J. Peterson,
X *	Old Dominion University.  It produces a Frequency Domain display from
X *	the Time Domain data input using the Fast Fourier Transform.
X *
X *	The Real data is generated by the in-phase (I) channel and the
X *	Imaginary data is produced by the quadrature-phase (Q) channel of
X *	a Doppler Radar receiver.  The middle filter is zero Hz.  Closing
X *	targets are displayed to the right, and Opening targets to the left.
X *
X *	Note: With Imaginary data set to zero the output is a mirror image.
X *
X *	Usage:	fft samples input output
X *		1. Samples is a power of two.
X *		2. Input is (samples * sizeof(double)) characters.
X *		3. Output is (samples * sizeof(double)) characters.
X *		4. Standard error is help or debugging messages.
X *
X *	Define SPEED to produce a program that pre-computes the sin/cos tables,
X *		this takes more memory however.
X *
X *	Define SIMULATE to substitute a quicky magnitude routine, Has a max
X *		12% error.
X *
X *	Define SYSV to create a Unix(tm) System V operating system version,
X *		else it defaults to MS-DOS(tm) and Turbo-C(tm) V1.5
X *		and MS-C V5.0.
X *
X *	See also: readme.doc, gen_pulse.c, and gen_sine.c.
X */
X
X/* Includes */
X
X#include <stdio.h>
X#include <malloc.h>	/* Rename Turbo-C alloc.h to malloc.h */
X#include <math.h>
X
X
X/* Defines */
X
X#define	TWO_PI	((double)2.0 * 3.14159265358979324)
X#define	Chunk	((unsigned)(samples * sizeof(double)))
X
X#ifdef	SYSV
X#define	RMODE	"r"
X#define	WMODE	"w"
X#else
X#define	RMODE	"rb"
X#define	WMODE	"wb"
X#endif
X
X
X/* Globals, Forward declarations */
X
Xstatic int	samples, power, permute();
Xstatic double	*real, *imag, maxn, magnitude();
Xstatic void	fft(), max_amp(), display(), err_report();
X
X#ifdef SPEED
Xstatic double	*sine, *cosine;
Xstatic void	build_trig();
X#endif
X
Xstatic FILE	*fpi, *fpo;
X
X
X/* The program */
X
Xmain(argc, argv)
Xint	argc;
Xchar	*argv[];
X{
X	if (argc != 4)
X		err_report(0);
X
X	samples = abs(atoi(argv[1]));
X	power   = (int)(log10((double)samples) / log10((double)2.0));
X
X	/* Allocate memory for the variable arrays */
X
X	if ((real = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(1);
X
X	if ((imag = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(2);
X
X#ifdef SPEED
X	if ((sine = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(3);
X
X	if ((cosine = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(4);
X#endif
X	/* open the data files */
X
X	if ((fpi = fopen(argv[2], RMODE)) == (FILE *)NULL)
X		err_report(5);
X
X	if ((fpo = fopen(argv[3], WMODE)) == (FILE *)NULL)
X		err_report(6);
X
X	/* read in the data from the input file */
X
X	fread((char *)real, sizeof(double), (unsigned)samples, fpi);
X	fread((char *)imag, sizeof(double), (unsigned)samples, fpi);
X	fclose(fpi);
X
X#ifdef SPEED
X	build_trig();
X#endif
X	fft();
X	display();
X
X	/* write the frequency domain data to the standard output */
X
X	fwrite((char *)real, sizeof(double), (unsigned)samples, fpo);
X	fwrite((char *)imag, sizeof(double), (unsigned)samples, fpo);
X	fclose(fpo);
X
X	free((char *)real);
X	free((char *)imag);
X
X#ifdef SPEED
X	free((char *)sine);
X	free((char *)cosine);
X#endif
X
X	exit(0);
X}
X
X
Xstatic void err_report(n)
Xint	n;
X{
X	switch (n)  {
X	case 0:
X		fprintf(stderr, "Usage: fft samples\n");
X		fprintf(stderr, "Where samples is a power of two\n");
X		break;
X	case 1:
X		fprintf(stderr, "fft: Out of memory getting real space\n");
X		break;
X	case 2:
X		fprintf(stderr, "fft: Out of memory getting imag space\n");
X		free((char *)real);
X		break;
X#ifdef SPEED
X	case 3:
X		fprintf(stderr, "fft: Out of memory getting sine space\n");
X		free((char *)real);
X		free((char *)imag);
X		break;
X	case 4:
X		fprintf(stderr, "fft: Out of memory getting cosine space\n");
X		free((char *)real);
X		free((char *)imag);
X		free((char *)sine);
X		break;
X#endif
X	case 5:
X		fprintf(stderr,"fft: Unable to open data input file\n");
X		free((char *)real);
X		free((char *)imag);
X#ifdef SPEED
X		free((char *)sine);
X		free((char *)cosine);
X#endif
X		break;
X	case 6:
X		fprintf(stderr,"fft: Unable to open data output file\n");
X		fclose(fpi);
X		free((char *)real);
X		free((char *)imag);
X#ifdef SPEED
X		free((char *)sine);
X		free((char *)cosine);
X#endif
X	}
X
X	exit(1);
X}
X
X
Xstatic void fft()
X{
X	register int	loop, loop1, loop2;
X	unsigned	i1;			/* going to right shift this */
X	int		i2, i3, i4, y;
X	double		a1, a2, b1, b2, z1, z2;
X#ifndef SPEED
X	double		v;
X
X	v  = TWO_PI * ((double)1.0 / (double)samples);
X#endif
X	i1 = samples / 2;
X	i2 = 1;
X
X	/* perform the butterflies */
X
X	for (loop = 0; loop < power; loop++)  {
X		i3 = 0;
X		i4 = i1;
X
X		for (loop1 = 0; loop1 < i2; loop1++)  {
X			y = permute(i3 / (int)i1);
X#ifdef SPEED
X			z1 =  cosine[y];
X			z2 = -sine[y];
X#else
X			z1 =  cos(v * (double)y);
X			z2 = -sin(v * (double)y);
X#endif
X			for (loop2 = i3; loop2 < i4; loop2++)  {
X
X				/* Don't depend on complex precedences */
X
X				a1 = real[loop2];
X				a2 = imag[loop2];
X
X				b1  = z1 * real[loop2 + i1];
X				b1 -= z2 * imag[loop2 + i1];
X
X				b2  = z2 * real[loop2 + i1];
X				b2 += z1 * imag[loop2 + i1];
X
X				real[loop2] = a1 + b1;
X				imag[loop2] = a2 + b2;
X
X				real[loop2 + i1] = a1 - b1;
X				imag[loop2 + i1] = a2 - b2;
X			}
X
X			i3 += (i1 << 1);
X			i4 += (i1 << 1);
X		}
X
X		i1 >>= 1;
X		i2 <<= 1;
X	}
X}
X
X/*
X *	Display the frequency domain.
X *
X *	The filters are aranged so that DC is in the middle filter.
X *	Thus -Doppler is on the left, +Doppler on the right.
X */
X
Xstatic void display()
X{
X	register int	c, loop;
X	int		n, x;
X
X	max_amp();
X	n = samples / 2;
X
X	for (loop = n; loop < samples; loop++)  {
X		x = (int)(magnitude(loop) * (double)56.0 / maxn);
X		printf("%d\t|", loop - n);
X		c = 0;
X		while (++c <= x)
X			putchar('=');
X
X		putchar('\n');
X	}
X
X	for (loop = 0; loop < n; loop++)  {
X		x = (int)(magnitude(loop) * (double)56.0 / maxn);
X		printf("%d\t|", loop + n);
X		c = 0;
X		while (++c <= x)
X			putchar('=');
X
X		putchar('\n');
X	}
X}
X
X/*
X *	Find maximum amplitude
X */
X
Xstatic void max_amp()
X{
X	register int	loop;
X	double		mag;
X
X	maxn = (double)0.0;
X	for (loop = 0; loop < samples; loop++)  {
X		if ((mag = magnitude(loop)) > maxn)
X			maxn = mag;
X	}
X}
X
X/*
X *	Calculate Power Magnitude
X */
X
X#ifdef SIMULATE
X/*
X *	Simulate sqrt(i^2 + q^2).  Do this by adding half the smaller to
X *	the larger.  See also excellent description by Bob Leedom in the
X *	June 1979 Byte Magazine Technical Forum P. 188.  I originally saw
X *	The algorithm used in both an FAA Moving Target Detector (MTD) and
X *	a Westinghouse radar Envelope Detector.  Both in Hardware of course,
X *	where the binary shifting worked very fast.  Don't need high accuracy
X *	to tell you the storm is gonna remove you from Kansas (eh - Toto)...
X */
X
Xstatic double magnitude(n)
Xint	n;
X{
X	double	i, q;
X
X	n = permute(n);
X
X	i = fabs(real[n]);	/* mask the sign bit */
X	q = fabs(imag[n]);
X
X	if (i > q)
X		return (i + q / (double)2.0);	/* shift right 1 and add */
X	else
X		return (q + i / (double)2.0);
X}
X#else
X
X/*
X *	Do it the normal way
X */
X
Xstatic double magnitude(n)
Xint	n;
X{
X	double	i, q;
X
X	n = permute(n);
X	i = real[n] * real[n];
X	q = imag[n] * imag[n];
X
X	return sqrt(i + q);
X}
X#endif
X
X/*
X *	Bit reverse the number
X *
X *	Change 11100000b to 00000111b or vice-versa
X */
X
Xstatic int permute(index)
Xint	index;
X{
X	register int	loop;
X	unsigned	n1;
X	int		result;
X
X	n1 = samples;
X	result = 0;
X
X	for (loop = 0; loop < power; loop++)  {
X		n1 >>= 1;
X		if (index < n1)
X			continue;
X
X		/* Unix has a rounding error that MS-DOS compilers don't */
X		/* so go ahead and add some bits (.05) */
X
X		result += (int)((double).05 + pow((double)2.0, (double)loop));
X		index -= n1;
X	}
X
X	return result;
X}
X
X#ifdef SPEED
X/*
X *	Pre-compute the sine and cosine tables
X */
X
Xstatic void build_trig()
X{
X	register int	loop;
X	double		angle, increment;
X
X	angle     = (double)0.0;
X	increment = TWO_PI * ((double)1.0 / (double)samples);
X
X	for (loop = 0; loop < samples; loop++)  {
X		sine[loop]   = sin(angle);
X		cosine[loop] = cos(angle);
X		angle       += increment;
X	}
X}
X#endif
X
X/* EOF */
END_OF_fft.c
if test 7978 -ne `wc -c <fft.c`; then
    echo shar: \"fft.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f gen_sine.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"gen_sine.c\"
else
echo shar: Extracting \"gen_sine.c\" \(1964 characters\)
sed "s/^X//" >gen_sine.c <<'END_OF_gen_sine.c'
X/*
X *	gen_sine.c
X *
X *	C Version 1.6 by Steve Sampson, Public Domain, 15 August 1988
X *
X *	This program is used to generate time domain sinewave data
X *	for fft.c.  If you want an opening target - negate the test frequency
X *
X *	Usage: gen_sine samples output
X */
X
X#include <stdio.h>
X#include <malloc.h>
X#include <math.h>
X
X#define	TWO_PI	((double)2.0 * 3.14159265358979324)
X#define Chunk	((unsigned)(samples * sizeof(double)))
X
X#ifdef	SYSV
X#define	WMODE	"w"
X#else
X#define	WMODE	"wb"
X#endif
X
Xstatic double	sample, freq, time, *real, *imag;
Xstatic int	loop, samples;
Xstatic void	err_report();
Xstatic FILE	*fp;
X
X
Xmain(argc, argv)
Xint	argc;
Xchar	*argv[];
X{
X	if (argc != 3)
X		err_report(0);
X
X	samples = abs(atoi(argv[1]));
X
X	if ((real = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(1);
X
X	if ((imag = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(2);
X
X	printf("Input The Test Frequency (Hz) ? ");
X	scanf("%lf", &freq);
X	printf("Input The Sampling Frequency (Hz) ? ");
X	scanf("%lf", &sample);
X	sample = (double)1.0 / sample;
X
X	time = (double)0.0;
X	for (loop = 0; loop < samples; loop++)  {
X		real[loop] =  sin(TWO_PI * freq * time);
X		imag[loop] = -cos(TWO_PI * freq * time);
X		time += sample;
X	}
X
X	if ((fp = fopen(argv[2], WMODE)) == (FILE *)NULL)
X		err_report(3);
X
X	fwrite((char *)real, sizeof(double), (unsigned)samples, fp);
X	fwrite((char *)imag, sizeof(double), (unsigned)samples, fp);
X	fclose(fp);
X
X	putchar('\n');
X
X	free((char *)real);
X	free((char *)imag);
X
X	exit(0);
X}
X
X
Xstatic void err_report(n)
Xint	n;
X{
X	switch (n)  {
X	case 0:
X		fprintf(stderr,"Usage: gen_sine samples output_file\n");
X		fprintf(stderr,"Where samples is a power of two\n");
X		break;
X	case 1:
X		fprintf(stderr,"gen_sine: Out of memory getting real space\n");
X		break;
X	case 2:
X		fprintf(stderr,"gen_sine: Out of memory getting imag space\n");
X		free((char *)real);
X		break;
X	case 3:
X		fprintf(stderr,"gen_sine: Unable to create write file\n");
X	}
X
X	exit(1);
X}
X
X/* EOF */
END_OF_gen_sine.c
if test 1964 -ne `wc -c <gen_sine.c`; then
    echo shar: \"gen_sine.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f gen_pulse.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"gen_pulse.c\"
else
echo shar: Extracting \"gen_pulse.c\" \(1827 characters\)
sed "s/^X//" >gen_pulse.c <<'END_OF_gen_pulse.c'
X/*
X *	gen_pulse.c
X *
X *	C Version 1.6 by Steve Sampson, Public Domain, 15 August 1988
X *
X *	This program is used to generate time domain pulse data	for fft.c.
X *
X *	Usage: gen_pulse samples output
X */
X
X#include <stdio.h>
X#include <malloc.h>
X
X#define	Chunk	((unsigned)(samples * sizeof(double)))
X
X#ifdef	SYSV
X#define	WMODE	"w"
X#else
X#define	WMODE	"wb"
X#endif
X
Xstatic double	sample, pw, time, *real, *imag;
Xstatic int	loop, samples;
Xstatic void	err_report();
Xstatic FILE	*fp;
X
X
Xmain(argc, argv)
Xint	argc;
Xchar	*argv[];
X{
X	if (argc != 3)
X		err_report(0);
X
X	samples = abs(atoi(argv[1]));
X
X	if ((real = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(1);
X
X	if ((imag = (double *)malloc(Chunk)) == (double *)NULL)
X		err_report(2);
X
X	printf("Input The Pulse Width (Seconds) ? ");
X	scanf("%lf", &pw);
X	printf("Input The Sampling Time (Seconds) ? ");
X	scanf("%lf", &sample);
X
X	time = (double)0.0;
X	for (loop = 0; loop < samples; loop++)  {
X		if (time < pw)
X			real[loop] = (double)1.0;
X		else
X			real[loop] = (double)0.0;
X
X		imag[loop] = (double)0.0;
X		time += sample;
X	}
X
X	if ((fp = fopen(argv[2], WMODE)) == (FILE *)NULL)
X		err_report(3);
X
X	fwrite((char *)real, sizeof(double), (unsigned)samples, fp);
X	fwrite((char *)imag, sizeof(double), (unsigned)samples, fp);
X	fclose(fp);
X
X	putchar('\n');
X
X	free((char *)real);
X	free((char *)imag);
X
X	exit(0);
X}
X
X
Xstatic void err_report(n)
Xint	n;
X{
X	switch (n)  {
X	case 0:
X		fprintf(stderr,"Usage: gen_pulse samples output_file\n");
X		fprintf(stderr,"Where samples is a power of two\n");
X		break;
X	case 1:
X		fprintf(stderr,"gen_pulse: Out of memory getting real space\n");
X		break;
X	case 2:
X		fprintf(stderr,"gen_pulse: Out of memory getting imag space\n");
X		free((char *)real);
X		break;
X	case 3:
X		fprintf(stderr,"gen_pulse: Unable to create write file\n");
X	}
X
X	exit(1);
X}
X
X/* EOF */
END_OF_gen_pulse.c
if test 1827 -ne `wc -c <gen_pulse.c`; then
    echo shar: \"gen_pulse.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0

^^