


MODULE FFTLIB;
{Library of routines used in the computation of discrete}
{Fourier transforms                                     }

{$M DEBUG        }
{$M LOG2         }
{$M POST_PROCESS }
{$M SHUFFLE      }
{$M FFT          }

{$M *}

CONST  ARRAY_SIZE = 2048;
               pi = 3.141592654;

TYPE     TRANSFORM_TYPE = (FORWRD,INVERSE);
                 XARRAY = ARRAY[1..ARRAY_SIZE] OF REAL;

VAR       N : EXTERNAL INTEGER;
         NU : EXTERNAL INTEGER;
        N_2 : EXTERNAL INTEGER;
        N_4 : EXTERNAL INTEGER;
          X : EXTERNAL XARRAY;
       MODE : EXTERNAL TRANSFORM_TYPE;


PROCEDURE DEBUG;
   VAR  I : INTEGER;
   BEGIN
   WRITELN;
   FOR I:=1 TO  N DO WRITELN('X[',I:2,']=',X[I]:8:2);
   WRITELN; WRITELN
   END;



FUNCTION LOG2( FACTOR : INTEGER) : INTEGER;
   VAR LOG : INTEGER;
   BEGIN
   LOG:=0;
   WHILE FACTOR>=2 DO
      BEGIN
      LOG:=LOG+1;
      FACTOR:=FACTOR DIV 2
      END;
   LOG2:=LOG
   END;




PROCEDURE POST_PROCESS(MODE : TRANSFORM_TYPE);
   {      Post-processing for forward real transforms     }
   {      Pre-processing  for inverse real transform      }
   {                                                      }
   { When using POST, the method of storage follows the   }
   { following pattern:                                   }
   {                                                      }
   {                                                      }
   {                                                      }
   {        Action            Locations of array X[i]     }
   {    --------------       --------------------------   }
   {                                                      }
   {    Post-Processing      first (N/2+1) are real       }
   {   (Forward Real FT)     next  (N/2-1) are imaginary  }
   {                                                      }
   {                                                      }
   {    Pre-Processing       first (N/2)   are real       }
   {   (Inverse Real FT)     next  (N/2)   are imaginary  }
   {                                                      }
   {                                                      }
   { This storage scheme is necessary to insure that the  }
   { composition of the inverse FT with a forward FT re-  }
   { turns the original input array.  If one desires the  }
   { results of the forward FT, then output a zero instead}
   { of X[N/2+1]; this is because the imaginary component }
   { of the first Fourier coefficient is always zero for  }
   { real data.  With the zero in place, however, the     }
   { array is not invertible (i.e., the N/2+1 real compo- }
   { nent is necessary for the inverse computation).      }

VAR       L,M,I : INTEGER;
           IPN2 : INTEGER;
           MPN2 : INTEGER;
            ARG : REAL;
      DELTA_ARG : REAL;
    IPCOS_RMSIN : REAL;
    IPSIN_RMCOS : REAL;
        IC0,IC1 : REAL;
        IS0,IS1 : REAL;
          RP,RM : REAL;
          IP,IM : REAL;

   BEGIN
   ARG:=pi/N_2;
   IC0:=COS(ARG);
   IS0:=SIN(ARG);
   IC1:=1.0;
   IS1:=0.0;

   CASE MODE OF

       FORWRD : BEGIN
                RP      :=  X[1] + X[N_2+1];
                X[N_2+1]:=  X[1] - X[N_2+1]
                END;

      INVERSE : BEGIN
                IS0:=-IS0;
                IC1:=-1.0;
                RP      :=( X[1] + X[N_2+1] )/2;
                X[N_2+1]:=( X[1] - X[N_2+1] )/2
                END
      END;

   X[1]:=RP;
   X[N_2+N_4+1]:=-X[N_2+N_4+1];

   FOR I:=2 TO N_4 DO
      BEGIN
      M:=N_2 - I + 2;
      IPN2:=I + N_2;
      MPN2:=M + N_2;

      RP :=IC1*IC0 - IS1*IS0;   {compute sine and cosine for next}
      IS1:=IC1*IS0 + IS1*IC0;   {angles using de Moivre's Theorem}
      IC1:=RP;

      RP:=( X[  I ] + X[  M ] )/2;
      RM:=( X[  I ] - X[  M ] )/2;

      IP:=( X[IPN2] + X[MPN2] )/2;
      IM:=( X[IPN2] - X[MPN2] )/2;

      IPCOS_RMSIN:=IP*IC1 - RM*IS1;     { IP*COS(Y) - RM*SIN(Y) }
      IPSIN_RMCOS:=IP*IS1 + RM*IC1;     { IP*SIN(Y) + RM*COS(Y) }

      X[  I ]:= RP + IPCOS_RMSIN;
      X[  M ]:= RP - IPCOS_RMSIN;

      X[IPN2]:= IM - IPSIN_RMCOS;
      X[MPN2]:=-IM - IPSIN_RMCOS;

      END
   END;



PROCEDURE SHUFFLE(MODE : TRANSFORM_TYPE);
   {Shuffle points from alternate real-imaginary to 1st-half real,}
   {2nd-half imaginary if MODE=FORWRD.  The procedure is reversed }
   {if MODE=INVERSE.                                              }

   VAR          I,J,K,L : INTEGER;
                  IPCM1 : INTEGER;
          CELL_DISTANCE : INTEGER;
             CELL_COUNT : INTEGER;
            POINT_COUNT : INTEGER;
                  XTEMP : REAL;

   BEGIN
   {choose whether to start with large cells and go down or start}
   {with small cells and increase.                               }

   CASE MODE OF

       FORWRD : BEGIN
                CELL_DISTANCE:=N DIV 2;
                CELL_COUNT:=1;
                POINT_COUNT:=N DIV 4
                END;

      INVERSE : BEGIN
                CELL_DISTANCE:=2;
                CELL_COUNT:=N DIV 4;
                POINT_COUNT:=1
                END
      END;

   FOR L:=1 TO NU DO
      BEGIN 
      I:=2;
      FOR J:=1 TO CELL_COUNT DO
         BEGIN
         FOR K:=1 TO POINT_COUNT DO
            BEGIN
            IPCM1:=I + CELL_DISTANCE - 1;
            XTEMP:=X[I];
            X[I]:=X[IPCM1];
            X[IPCM1]:=XTEMP;
            I:=I+2
            END;
         I:=I + CELL_DISTANCE
         END;

      CASE MODE OF

          FORWRD : BEGIN
                   CELL_DISTANCE:=CELL_DISTANCE DIV 2;
                   CELL_COUNT:=CELL_COUNT*2;
                   POINT_COUNT:=POINT_COUNT DIV 2
                   END;

         INVERSE : BEGIN
                   CELL_DISTANCE:=CELL_DISTANCE*2;
                   CELL_COUNT:=CELL_COUNT DIV 2;
                   POINT_COUNT:=POINT_COUNT*2
                   END
         END
      END
   END;



PROCEDURE FFT(MODE : TRANSFORM_TYPE);
   {FFT algorithm--operates on n/2 complex data points.}
   {The i-th complex data value is assumed to be stored}
   {in the array X[1..n] such that:                    }
   {                                                   }
   {          X[ i       ] = real component            }
   {          X[ i + n/2 ] = imaginary component       }
   {                                                   }
   {FFT returns the complex result in the same fashion.}

VAR         CELL_COUNT : INTEGER;
         CELL_DISTANCE : INTEGER;
           POINT_COUNT : INTEGER;
        IPN2,JPN2,KPN2 : INTEGER;
               I,J,K,L : INTEGER;
                    I2 : INTEGER;
                  IMAX : INTEGER;
                 INDEX : INTEGER;
             COS0,SIN0 : REAL;
             COSY,SINY : REAL;
           R2COS_I2SIN : REAL;
           I2COS_R2SIN : REAL;
                TEMP_R : REAL;
                TEMP_I : REAL;
                 XTEMP : REAL;
                    K1 : REAL;

   FUNCTION BIT_REVERSAL( J,NU : INTEGER) : INTEGER;
      {bit invert the number J by NU bits}
      VAR     I,IB : INTEGER;
      BEGIN
      IB:=J MOD 2;
      FOR I:=2 TO NU DO
         BEGIN
         J:=J DIV 2;
         IB:=IB*2 + J MOD 2
         END;
      BIT_REVERSAL:=IB
      END;

   BEGIN
   {shuffle the complex data (n/2 entries) into bit-inverted order}
   FOR I:=2 TO N_2-1 DO
      BEGIN
      K:=BIT_REVERSAL(I-1,NU) + 1;
      IF I>K THEN  {swap the real and imaginary components}
         BEGIN
         IPN2:=I + N_2;      {imaginary}
         KPN2:=K + N_2;      { indices }

         TEMP_R:=X[  K ];
         TEMP_I:=X[KPN2];

         X[  K ]:=X[  I ];
         X[KPN2]:=X[IPN2];

         X[  I ]:=TEMP_R;
         X[IPN2]:=TEMP_I
         END
      END;


   {do first pass specially, since it has no multiplication}
   I:=1;
   FOR J:=1 TO N_4 DO
      BEGIN
      K:=I+1;
      IPN2:=I+N_2;
      KPN2:=IPN2+1;
      K1:=     X[  I ] + X[  K ];
      X[  K ]:=X[  I ] - X[  K ];
      X[  I ]:=K1;
      K1:=     X[IPN2] + X[KPN2];
      X[KPN2]:=X[IPN2] - X[KPN2];
      X[IPN2]:=K1;
      I:=I+2
      END;

   {set up for 2nd pass ARG = pi/2}
   COS0:=0.0;
   IF MODE=FORWRD THEN SIN0:= 1.0
                  ELSE SIN0:=-1.0; 
   CELL_COUNT:=N_2 DIV 4;
   POINT_COUNT:=2;
   CELL_DISTANCE:=4;

   {each pass after the 1st starts here}
   FOR I2:=2 TO NU DO
      BEGIN 
      COSY:=1.0;
      SINY:=0.0;
      FOR INDEX:=1 TO POINT_COUNT DO
         BEGIN
         I:=INDEX;

         FOR L:=1 TO CELL_COUNT DO
            BEGIN
            J:=I + POINT_COUNT;
            IPN2:=I + N_2;
            JPN2:=J + N_2;

            IF INDEX=1 THEN  {no sine or cosine terms}
               BEGIN
               R2COS_I2SIN:=X[  J ];
               I2COS_R2SIN:=X[JPN2] 
               END
            ELSE
               BEGIN
               R2COS_I2SIN:=X[  J ]*COSY + X[JPN2]*SINY;
               I2COS_R2SIN:=X[JPN2]*COSY - X[  J ]*SINY
               END;
 
            X[  J ]:=X[  I ] - R2COS_I2SIN;  {Note:  these assignments}
            X[  I ]:=X[  I ] + R2COS_I2SIN;  {must be in this order if}
            X[JPN2]:=X[IPN2] - I2COS_R2SIN;  {temporary variables are } 
            X[IPN2]:=X[IPN2] + I2COS_R2SIN;  {to be avoided.          }
            I:=I + CELL_DISTANCE
            END;

         K1  :=COSY*COS0 - SINY*SIN0;  {using de Moivre's Theorem}
         SINY:=SINY*COS0 + COSY*SIN0;
         COSY:=K1;

         END;

      {pass done--change cell distance and number of cells}
      CELL_COUNT:=CELL_COUNT DIV 2;
      POINT_COUNT:=POINT_COUNT*2;
      CELL_DISTANCE:=CELL_DISTANCE*2;

      SIN0:=SQRT((1.0 - COS0)/2.0);  {use half-angle formulas to compute}
      COS0:=SQRT((1.0 + COS0)/2.0);  {sin & cos of ARG:=ARG/2           }

      IF MODE=INVERSE THEN SIN0:= -SIN0
      END;              
 
   IF MODE=INVERSE THEN
      FOR I:=1 TO N DO X[I]:=X[I]/N_2
   END;


MODEND.
