Metropoli BBS
VIEWER: okspctrm.c MODE: TEXT (ASCII)
/***************************************************************************
 *		  Copyright (C) 1994  Charles P. Peterson                  *
 *	     4007 Enchanted Sun, San Antonio, Texas 78244-1254             *
 *              Email: Charles_P_Peterson@fcircus.sat.tx.us                *
 *                                                                         *
 *		  This is free software with NO WARRANTY.                  *
 *	      See gfft.c, or run program itself, for details.              *
 *		      Support is available for a fee.                      *
 ***************************************************************************
 *
 * Program:     gfft--General FFT analysis
 * File:        okspctrm.c
 * Purpose:     OK! Do an fft spectrum analysis
 * Author:      Charles Peterson (CPP)
 * History:     24-July-1993 CPP; Created.
 *               6-Feb-95 CPP (1.31); Progress Requester
 *
 * Comment:
 *
 *    As used here, a 'spectrum analysis' is one which starts with real
 *    samples, and ends up with real magnitudes of some sort (possibly with
 *    some sort of 'windowing' applied to the input which may be analyzed
 *    in 'segments' which might be padded and/or overlapped, and possibly
 *    with normalization applied to the output).  The output magnitudes may
 *    either be a 'power' spectrum or an 'amplitude' or 'voltage' spectrum
 *    (the latter being the positive square root of the former.)  *
 *
 *    (Ordinary fft, in contrast, may begin with complex input values and
 *    usually returns complex output values.  Usually the input to an
 *    ordinary fft is not segmented, and doing so may present additional
 *    problems.  Most frequently, ordinary fft is actually part of some
 *    larger problem, such as convolution or spectrum analysis.)
 *
 *    This function has gotten a little out of hand, primarily because of
 *    all the possible combinations of options that are applied here.
 *    Possibly 'Interleave' might be left out next time unless someone
 *    notes a use for it.  (It didn't turn out to be as useful as I
 *    expected.  When curves made with different amounts of interleave are
 *    spliced together, they don't meet, even in 'PSDensity' mode.)  Likewise
 *    for 'Pad,' an option which I've never seen do anything but make
 *    matters much worse.  But somehow, some people seem to think that it's
 *    useful, or at least harmless.  It isn't either, in my experience.
 *    Leaving out those two options would reduce this size of this function
 *    substantially.  
 *
 *    But, don't expect any such streamlining to make a signficant (or
 *    even measureable) difference in performance.  My lprof tests indicate
 *    that this entire beast takes less than 1% of the total time under
 *    typical conditions.  Considering that there is real work going on
 *    here--even if you don't use the more esoteric options--that is quite
 *    reasonable.  (As expected, cfft takes around 70% of the time under
 *    typical conditions, which isn't unreasonable either.)
 *
 *    Anyway, if you want to see my best programming and comment writing,
 *    go to cfft.c, the function which actually does the fft, though it
 *    has gotten harrier since I added an accelerated mode.
 *
 */
#define INITIAL_READ_PROGRESS 5
#define ENDING_WRITE_PROGRESS 45

#define USE_MEMMOVE 
/* Also uses MEMCPY, where applicable */
/* Measured with lprof, only about 0.3% improvement overall. */
/* But, even with 1 bin, didn't make matters worse either. */

#include <time.h>  /* time and difftime */
#include <string.h> /* memmove and memcpy */

#include "gfft.h"
#include "settings.h"
#include "wbench.h"    /* progress requester stuff */

double LoopTimeElapsed = 0.0;

double Percent_Per_Loop = 1.0;
double Initial_Progress = (double) INITIAL_READ_PROGRESS;
double Ending_Progress = (double) ENDING_WRITE_PROGRESS; /* Must not be 0 */

static BIN_TYPE *bins = NULL;
static ULONG number_bins = 0;
static int segment_count = 0;

void do_re_output (void)
{
    if (bins && number_bins && segment_count)
    {
	ok_write (bins, (long) number_bins, (long) segment_count);
    }
    else
    {
	error_message (CANT_RE_OUTPUT);
	RAISE_ERROR (NOTHING_SPECIAL);    /* longjmp outa here */
    }
}


ULONG ok_spectrum (BOOLEAN do_it_for_real)
{
    static float *indata = NULL;
    static float *overlapdata = NULL;
    static float *interleavedata = NULL;  /* e.g. Aseg Bseg Aseg Bseg ... */
    ULONG actually_read = 0;
    ULONG number_samples = 1;
    ULONG number_interleave_samples = 1;
    ULONG half_samples = 0;
    int more_data = TRUE;
    int error_in_loop = FALSE;
    int overlap = Overlap;     /* May be changed if overlap impossible */
    int last_partial_segment_used = FALSE;
    ULONG loop_count = 0;
    ULONG i;
    ULONG j;
    ULONG ai;
    ULONG aj;
    time_t time_beginning;
    time_t time_ending;
    ULONG total_actually_read;

    number_bins = 0;

    Sample_Sum_Of_Squares = 0.0;
    Sample_Frame_Count = 0;

    total_actually_read = ok_read (NULL, NULL);

    if (NumberBins == MAX_BINS)
    {
	if (!total_actually_read)
	{
	    error_message (NO_DATA_PRESENT);
	    RAISE_ERROR (NOTHING_SPECIAL);  /* longjmp outa here! */
	}
	else
	{
	    ULONG next_power;
	    ULONG total_in_leaf = total_actually_read / Interleave;
	    if (!total_in_leaf)
	    {
		error_message (INSUFFICIENT_DATA);
		RAISE_ERROR (NOTHING_SPECIAL);  /* longjmp outa here! */
	    }
	    next_power = get_pos_power_2 (total_in_leaf);
	    if (Pad || next_power == total_in_leaf)
	    {
		number_bins = next_power / 2; /* pad if required*/
	    }
	    else
	    {
		number_bins = next_power / 4; /* truncate if required */
	    }
	    number_samples = number_bins * 2 * Interleave;
	    bins_d_message (total_actually_read, number_bins);
	}
    }
    else
    {
	number_bins = (ULONG) NumberBins;
	if (number_bins > 0)
	{
	    number_samples = number_bins * 2 * Interleave;
	}
	else
	{
	    number_samples = 1;
	}
    }
/*
 * Now we have number_samples and number_bins
 */
    if (!do_it_for_real) return number_bins;

    segment_count = 0;

    time_beginning = time (NULL);

    half_samples = number_samples / 2;
    if (number_samples < 2)
    {
/*
 * Can't overlap segments of length 1
 */
	overlap = FALSE;
    }
    if (bins)
    {
	gfree (bins);
    }
    if (indata)
    {
	gfree (indata);
    }
    if (overlapdata)
    {
	gfree (overlapdata);
	overlapdata = NULL;
    }
    if (interleavedata)
    {
	gfree (interleavedata);
	interleavedata = NULL;
    }
      
/*
 * Note that there is one extra bin for 0 Hz; bins must be pre-zeroed
 */
    bins = gcalloc ((number_bins + 1), (long) sizeof(BIN_TYPE), 
		    NOTHING_SPECIAL);
    indata = gmalloc (number_samples * sizeof(float), 
		      NOTHING_SPECIAL);
    if (overlap)
    {
	overlapdata = gmalloc (number_samples * sizeof(float), 
			   NOTHING_SPECIAL);
    }
    if (Interleave > 1)
    {
	number_interleave_samples = number_samples / Interleave;
	if (!number_interleave_samples)
	{
	    error_message (INSUFFICIENT_DATA);
	    RAISE_ERROR (NOTHING_SPECIAL);  /* longjmp outa here! */
	}
	interleavedata = gmalloc (number_interleave_samples * sizeof(float),
				 NOTHING_SPECIAL);
    }

/*
 * Now, figure the number of FFT Inner Loops (FILs) required
 *   This is not going to deal with all possible complexities...
 *   since there is no great harm if we are wrong--it's only for the
 *   progress indicator.
 *     In particular, it's not going to deal with Interleave and Pad,
 *     which are fairly useless options not directly supported by the
 *     GUI interface anyway.
 */
    {
	extern long Actual_Time_Segments;
	extern long Inner_Loop_Count;
	long fils_per_segment;
	long est_fils;
	int segments_per_time_segment;
	double computational_percentage;
	int time_segments;

	fils_per_segment = fft_inner_loops (number_bins);

	if (!overlap)
	{
	    segments_per_time_segment = total_actually_read /
	                                 number_samples;
	}
	else if (total_actually_read % number_samples == 0)
	{
	    segments_per_time_segment = 2 * (total_actually_read / 
					 number_samples) - 1;
	}
	else
	{
	    segments_per_time_segment = 2 * (total_actually_read /
					 number_samples);
	}

	if (Time3D)
	{
	    time_segments = Actual_Time_Segments;
	    Ending_Progress = 1;
	    Initial_Progress = 1;
	}
	else
	{
	    Inner_Loop_Count = 0;
	    time_segments = 1;
	    if (segments_per_time_segment * time_segments > 2)
	    {
		Ending_Progress = ENDING_WRITE_PROGRESS * 2 /
		  (segments_per_time_segment * time_segments);
		Initial_Progress = INITIAL_READ_PROGRESS * 2 /
		  (segments_per_time_segment * time_segments);
		if (Ending_Progress < 1) Ending_Progress = 1;
	    }
	    else
	    {
		Ending_Progress = ENDING_WRITE_PROGRESS;
		Initial_Progress = INITIAL_READ_PROGRESS;
	    }
	}
	
	est_fils = fils_per_segment * segments_per_time_segment *
	              time_segments;

	computational_percentage = ((100 - Initial_Progress) -
	                            Ending_Progress);

	if (est_fils)
	{
	    Percent_Per_Loop = computational_percentage / est_fils;
	}
	else
	{
	    Percent_Per_Loop = computational_percentage;
	}
	if (number_bins > 1)
	{
	    progress_requester__update (1);  /* Warm and fuzzy */
	}
	else
	{
	    progress_requester__update (-1);  /* Can't really tell */
	}

    }
/*
 * Now that these preliminaries are out of the way, we can
 * begin looping over input data
 */
    time_beginning = time (NULL);

    while (more_data)
    {
	actually_read = ok_read (indata, number_samples);

	  /* ignored if already > this value */
	progress_requester__update ((int) Initial_Progress);

	if (!actually_read)
	{
	    more_data = FALSE;
	    if (!loop_count)
	    {
		error_message (NO_DATA_PRESENT);
		error_in_loop = TRUE;
	    }
	    break;
	}
	if (actually_read < number_samples)  /* Deal with partial segment */
	{
	    more_data = FALSE;
	    if (overlap && loop_count)
	    {
		for (i = 0, j = actually_read; j < number_samples; 
		     i++, j++)
		{
		    overlapdata[i] = overlapdata[j];
		}
		for (j = 0; j < actually_read; i++, j++)
		{
		    overlapdata[i] = indata[j];
		}
		if (Interleave > 1)    /* Overlap and Interleave */
		{
		    for (ai = 0; ai < Interleave; ai++)
		    {
			float *a_data = interleavedata;
			for (aj = ai; aj < number_samples; aj += Interleave)
			{
			    *a_data++ = overlapdata[aj];
			}
			ok_window (interleavedata, number_interleave_samples);
			ok_rfft (interleavedata, number_interleave_samples);
			ok_sigma (interleavedata, bins, number_bins);
			segment_count++;
		    }
		}
		else  /* Overlap only...no Interleave */
		{
		    ok_window (overlapdata, number_samples);
		    ok_rfft (overlapdata, number_samples);
		    ok_sigma (overlapdata, bins, number_bins);
		    segment_count++;
		}
		overlap = FALSE;  /* overlap buffer now used up */
		last_partial_segment_used = TRUE;
	    }
	    if (Pad)
	    {
		error_message (PADDING_TAILEND);
		for (i = actually_read; i < number_samples; i++)
		{
		    indata[i] = 0.0;
		}
	    }
	    else
	    {
		if (!segment_count)
		{
		    error_message (INSUFFICIENT_DATA);
		    error_in_loop = TRUE;
		}
/*		else if (!last_partial_segment_used)
		{
		    error_message (IGNORING_TAILEND);
		} */
		break;
	    }
	}
/*
 * Here, we've got a full segment to work with (padded or not)
 */
	if (overlap)
	{
	    if (loop_count)  /* Can't process overlap first time around */
	    {
#ifdef USE_MEMMOVE
		memmove (overlapdata, &overlapdata[half_samples], 
			 half_samples * sizeof (float));
		memcpy (&overlapdata[half_samples], indata,
			half_samples * sizeof (float));
#else
		for (i = 0, j = half_samples; i < half_samples; i++, j++)
		{
		    overlapdata[i] = overlapdata[j];
		}
		for (j = 0; j < half_samples; i++, j++)
		{
		    overlapdata[i] = indata[j];
		}
#endif
		if (Interleave > 1)   /* Overlap and Interleave */
		{
		    for (ai = 0; ai < Interleave; ai++)
		    {
			float *a_data = interleavedata;
			for (aj = ai; aj < number_samples; aj += Interleave)
			{
			    *a_data++ = overlapdata[aj];
			}
			ok_window (interleavedata, number_interleave_samples);
			ok_rfft (interleavedata, number_interleave_samples);
			ok_sigma (interleavedata, bins, number_bins);
			segment_count++;
		    }
		}
		else  /* Overlap only...no Interleave */
		{

		    ok_window (overlapdata, number_samples);
		    ok_rfft (overlapdata, number_samples);
		    ok_sigma (overlapdata, bins, number_bins);
		    segment_count++;
		}
	    }
#ifdef USE_MEMMOVE
	    memcpy (overlapdata, indata, number_samples * sizeof (float));
#else
	    for (i = 0; i < number_samples; i++)
	    {
		overlapdata[i] = indata[i];
	    }
#endif
	}
	if (Interleave > 1)  /* Regular (unoverlapped) alternation */
	{
	    for (ai = 0; ai < Interleave; ai++)
	    {
		float *a_data = interleavedata;
		for (aj = ai; aj < number_samples; aj += Interleave)
		{
		    *a_data++ = indata[aj];
		}
		ok_window (interleavedata, number_interleave_samples);
		ok_rfft (interleavedata, number_interleave_samples);
		ok_sigma (interleavedata, bins, number_bins);
		segment_count++;
	    }
	}
	else
	{
	    ok_window (indata, number_samples);
	    ok_rfft (indata, number_samples);
	    ok_sigma (indata, bins, number_bins);
	    segment_count++;
	}
	loop_count++;
    }
    time_ending = time (NULL);
    LoopTimeElapsed = difftime (time_ending, time_beginning);

    if (!error_in_loop)
    {
	loop_time_message (LoopTimeElapsed);
	ok_write (bins, number_bins, segment_count);
    }
    gfree (indata);
    indata = NULL;
/*    gfree (bins); save for ReOut */
/*    bins = NULL; */
    if (overlapdata)
    {
	gfree (overlapdata);
	overlapdata = NULL;
    }
    if (interleavedata)
    {
	gfree (interleavedata);
	interleavedata = NULL;
    }
    if (error_in_loop)
    {
	RAISE_ERROR (NOTHING_SPECIAL);  /* longjmp outa here! */
    }

    return number_bins;
}

	
[ RETURN TO DIRECTORY ]