'===========================================================================
' Subject: CALCULATE FOURIER COEFFICIENTS     Date: 10-04-99 (02:50)       
'  Author: David Karlov                       Code: QB, QBasic, PDS        
'  Origin: dkarlov@acs.itd.uts.edu.au       Packet: ALGOR.ABC
'===========================================================================
' This program can calculate the Fourier coefficients of a periodic function.
' If you don't know what that means, a maths textbook can help..
' But it isn't necessary to understand it to use this program.

' Here is a very brief description of what all this Fourier stuff is:
' Every function can be thought of as a sum of an infinite number of
' sinusoidal functions.  Each sinusoid has a frequency which is an integer
' multiple of the "fundamental frequency", or the frequency of the original
' function (1 over the period).  These sinusoids are added together, and by
' changing the amplitude (strength) of each sinusoid, any periodic function
' can be made.
' This program finds what amplitudes are necessary to make a few interesting
' functions.  You can look at a table or graph of those values, or watch the
' function being built step-by-step out of sinusoids, or view an animated
' screen where each sinusoid is a rotating line attached on to the end of
' the previous rotating line, each rotating at different speeds (the speed
' represents the frequency of the sinusoid).  All of these line segments
' rotating together make up the original function ..approximately ;)
'
' NOTE -- if you want to add your own functions to do stuff with, look in
' the QB function F1(), add the code in there, and alter the constant
' NumFuncs.  You have to specify the function mathematically so QB can
' understand it, and also state the function's period.
'
' ..Written by David Karlov
' Feel free to contact me:  dkarlov@rocketmail.com


TYPE GraphData
   MinX AS DOUBLE
   MaxX AS DOUBLE
   MinY AS DOUBLE
   MaxY AS DOUBLE
   XRange AS DOUBLE
   YRange AS DOUBLE
END TYPE

DECLARE FUNCTION Mod2# (x AS DOUBLE, t AS DOUBLE)
DECLARE FUNCTION ArcTan# (Numer AS DOUBLE, Denom AS DOUBLE)
DECLARE FUNCTION Rect# (x AS DOUBLE)
DECLARE FUNCTION Sinc# (x AS DOUBLE)
DECLARE FUNCTION SquareWave# (x AS DOUBLE, DC AS DOUBLE)

DECLARE FUNCTION IntegrateF1# (x0 AS DOUBLE, x1 AS DOUBLE, Condition AS INTEGER, n AS INTEGER)

DECLARE SUB ShowPage (PageNum AS INTEGER)
DECLARE SUB GraphF1 (Params AS GraphData)
DECLARE SUB GraphAxes (Params AS GraphData)
DECLARE SUB GraphFourier (Params AS GraphData, Num AS INTEGER)
DECLARE SUB ShowFourierCoeffs ()
DECLARE SUB GraphFourierCoeffs ()
DECLARE SUB SubGraph (d() AS DOUBLE, x0 AS INTEGER, y0 AS INTEGER, Label AS STRING)
DECLARE SUB DrawWhirly ()
DECLARE SUB CalculateCoeffs ()
DECLARE SUB Options ()
DECLARE FUNCTION Trim$ (a AS DOUBLE)

DECLARE FUNCTION F1# (x AS DOUBLE, Condition AS INTEGER, n AS INTEGER)

CONST IntegrateResolution = 500  ' The higher this is, the more accurate
                                 ' (but slower also).
CONST ScreenMode = 9
CONST XSize = 640
CONST YSize = 350
CONST ExtraYSpace = .2
CONST Pi = 3.141592653589793#
CONST Tiny = .00000000000001#
CONST NUMCOEFFS = 70          ' Number of Fourier coefficients to calculate.

CONST NumFuncs = 12
DIM SHARED FF AS INTEGER ' the current function number. (1..NumFuncts)

DIM SHARED T0 AS DOUBLE  ' period of function


DIM SHARED FourierCoeffs(1 TO 2, 0 TO NUMCOEFFS) AS DOUBLE
DIM SHARED Func$
DIM SHARED Params AS GraphData

DIM SHARED Page AS INTEGER
Page = 0: ShowPage Page

FF = 1   ' Start on function 1

CalculateCoeffs

Options

' Inverse TAN function. (returns result in radians)
FUNCTION ArcTan# (Numer AS DOUBLE, Denom AS DOUBLE)

   DIM i AS DOUBLE

   IF Numer = 0 THEN
      IF Denom >= 0 THEN ArcTan = 0 ELSE ArcTan = Pi
   ELSEIF Denom = 0 THEN
      IF Numer > 0 THEN ArcTan = Pi / 2 ELSE ArcTan = -Pi / 2
   ELSE
      i = ATN(Numer / Denom)
      IF SGN(Numer) * SGN(Denom) = -1 THEN i = i + Pi
      ArcTan = i
   END IF

END FUNCTION

' Calculates the Fourier coefficients for the current function.
SUB CalculateCoeffs

   DIM n AS INTEGER
   n = F1(0, 0, 0) ' dummy call to initialize T0.

   FOR n = 0 TO NUMCOEFFS
      IF n = 0 THEN
         FourierCoeffs(1, n) = IntegrateF1(-T0 / 2, T0 / 2, 0, 0) / T0
      ELSE
         FourierCoeffs(1, n) = IntegrateF1(-T0 / 2, T0 / 2, 1, n) * 2 / T0
      END IF
      FourierCoeffs(2, n) = IntegrateF1(-T0 / 2, T0 / 2, 2, n) * 2 / T0
   NEXT n

END SUB

' Draws the rotating phasors on the screen.
' The image is drawn as fast as your computer can do it, with no delay.
SUB DrawWhirly
   DIM Im1(30000) AS INTEGER
 
   SCREEN 9, 0, 0, 0: CLS
   SCREEN 9, 0, 1, 1: CLS

   AngularRes = 100
   DetailRes = 70
   Scale1 = 175

   EX1 = 0: Beg = 0: Oldy = -1
   GET (1, 0)-(290, 349), Im1(0)

   DO
      FOR Ang = 1 TO AngularRes
         Page = 1 - Page: 'ShowPage Page
         SCREEN ScreenMode, , Page, Page
         PCOPY Page, 1 - Page

         IF Beg = 1 THEN DRAW "C1A0L640"
        
         FOR y = 0 TO 349
            IF POINT(0, y) = 1 THEN EXIT FOR
         NEXT y

         Page = 1 - Page: ShowPage Page
         LINE (290, 0)-(290, 349), 0
         PUT (0, 0), Im1(0), PSET
         IF y < 350 THEN
            IF Oldy > -1 THEN
               LINE (289, Oldy)-(290, y), 15
            ELSE
               PSET (290, y), 15
            END IF
            Oldy = y
         END IF
         Beg = 1

         GET (1, 0)-(290, 349), Im1(0)

         PRESET (320, 175)
         DRAW "C14"
         FOR t = 1 TO DetailRes
            a = FourierCoeffs(1, t)
            b = FourierCoeffs(2, t)
            c = (-ArcTan((b), (a)) * 180 / Pi + t * Ang / AngularRes * 360) MOD 360
            DRAW "TA=" + VARPTR$(c)
            c = INT(Scale1 * SQR(a * a + b * b))
            DRAW "U=" + VARPTR$(c)
         NEXT t

         DRAW "C15U1L1D2R2U2L1"
         IF INKEY$ <> "" THEN EX1 = 1: EXIT FOR
      NEXT Ang
   LOOP UNTIL EX1 = 1

END SUB

' This function is the actual function we are finding the Fourier
' coefficients for.
' Depending on the value of FF (a shared variable), one of a number of
' different functions are selected, each of which is a function of x.
'
' The input Condition is used to help find the Fourier coefficients..
' 0 means the actual function value is returned
' 1 means the value is multiplied by COS(2 * Pi * n / T0 * x)
' 2 means the value is multiplied by SIN(2 * Pi * n / T0 * x)
'
' (T0 is the period of the function, it is a shared variable).
'
FUNCTION F1# (x AS DOUBLE, Condition AS INTEGER, n AS INTEGER)

   SELECT CASE FF
   CASE 1
      T0 = 4   ' Period
      i# = SquareWave((x - 1) / 4, 2 / 4) - .5  ' Actual function
      Func$ = "g(t) =  Sigma(n = -oo -> n = oo) Rect((t - 1 - 4n) / 2)- 1/2"
      ' Func$ can be used to hold a description of the function.
   CASE 2
      T0 = 4
      IF x = 0 THEN
         i# = .5
      ELSE
         i# = SquareWave((x - 3) / 4, .5) - .5
      i# = i# + .5 * SquareWave((x - 2.5) / 4, .25) * (Mod2(x, T0) - 3)
         i# = i# + .5 * SquareWave((x - .5) / 4, .25) * (1 - Mod2(x, T0))
      END IF
      Func$ = ""
   CASE 3
      T0 = 4
      i# = SquareWave(x / 4, 2 / 4) * COS(4 * Pi * x)
      Func$ = "g(t) = Sigma(n = -oo -> n = oo) Rect(t - 4n) / 2) * Cos(4 Pi t)"
   CASE 4
      T0 = 2 * Pi
      i# = SquareWave((x - Pi / 2) / (2 * Pi), .5) * EXP(-Mod2(x, T0)) / 2
      i# = i# + (SquareWave((x - Pi / 2) / (2 * Pi), .5) - 1) * EXP(-Mod2(x, T0 / 2)) / 2
      Func$ = ""
   CASE 5
      T0 = 2
      IF x = 0 THEN
         i# = 0
      ELSE
         i# = SquareWave((x - T0 / 4) / T0, .5) * Mod2(x, T0)
         i# = i# + SquareWave((x - 3 * T0 / 4) / T0, .5) * (2 - Mod2(x, T0))
      END IF
      Func$ = ""
   CASE 6
      T0 = 2
      IF x = 0 THEN
         i# = 0
      ELSE
         i# = SquareWave((x - T0 / 4) / T0, .5) * Mod2(x, T0)
         i# = i# + SquareWave((x - 3 * T0 / 4) / T0, .5) * (2 - Mod2(x, T0))
      END IF
      i# = i# * (SIN(2 * Pi * x))
      Func$ = ""
   CASE 7
      T0 = 4
      i# = .5 * SIN(EXP(.8 * Mod2(x, T0)))
      Func$ = "w(t) = Sigma (n = -oo -> n = oo) Rect((t - 4n) / 4) * Sin(.8 * e ^ (t - 4n))"
   CASE 8
      T0 = 1 * 20
      i# = COS(2 * Pi * x / 20) * SquareWave(x * 3 * 2 * Pi / 20, .1)
      Func$ = ""
   CASE 9
      T0 = 20
      i# = COS(2 * Pi * x / 20) * (COS(2 * Pi * 33 * x / 20)) ^ 2
      Func$ = ""
   CASE 10
      T0 = 2
      i# = SQR(1 - (Mod2(x, T0) - 1) ^ 2)
      Func$ = ""
   CASE 11
      T0 = 2
      i# = .2 * (SIN(Pi * x) + SIN(3 * Pi * x) + SIN(5 * Pi * x) + SIN(7 * Pi * x))
      Func$ = "w(t) = (Sin(Pi t) + Sin(3 Pi t) + Sin(5 Pi t) + Sin(7 Pi t)) / 5"
   CASE 12
      T0 = 2
      i# = (SquareWave((x - 1) / 2, .5) * COS(Pi * x) + ABS(SquareWave(x / 2, .5) * SIN(2 * Pi * x)) / 2)
      Func$ = ""
   END SELECT

   SELECT CASE Condition
      CASE 0   ' normal
      CASE 1   ' projected onto COS
         i# = i# * COS(2 * Pi * n / T0 * x)
      CASE 2   ' projected onto SIN
         i# = i# * SIN(2 * Pi * n / T0 * x)
   END SELECT
  
   F1 = i#

END FUNCTION

' Draw the axes for the original function (in options menu).
SUB GraphAxes (Params AS GraphData)

   ' uses F1 as a guide for the size of the axes.

   DIM Y1(0 TO XSize - 1) AS DOUBLE

   IF Params.XRange = 0 THEN
      Params.MinX = -10.5: Params.MaxX = 10.5
      Params.XRange = Params.MaxX - Params.MinX
      Params.MinY = 30000: Params.MaxY = -30000

      FOR i = 0 TO XSize - 1
         Y1(i) = F1((i / XSize) * Params.XRange + Params.MinX, 0, 0)
         IF Y1(i) > Params.MaxY THEN Params.MaxY = Y1(i)
         IF Y1(i) < Params.MinY THEN Params.MinY = Y1(i)
      NEXT i
 
      Params.YRange = Params.MaxY - Params.MinY
      Params.MaxY = Params.MaxY + Params.YRange * ExtraYSpace
      Params.MinY = Params.MinY - Params.YRange * ExtraYSpace

      Params.YRange = Params.MaxY - Params.MinY
   END IF

   i = -XSize * Params.MinX / Params.XRange
   LINE (i, 0)-(i, YSize - 1), 1

   i = YSize * (1 + Params.MinY / Params.YRange)
   LINE (0, i)-(XSize - 1, i), 1

   XStep = 10 ^ INT(LOG(Params.XRange) / 3.5)
   FOR i = 0 TO Params.MinX STEP -XStep
      LINE ((i - Params.MinX) / Params.XRange * XSize, YSize * (1 + Params.MinY / Params.YRange) - 5)-((i - Params.MinX) / Params.XRange * XSize, YSize * (1 + Params.MinY / Params.YRange) + 5), 1
   NEXT i
   FOR i = 0 TO Params.MaxX STEP XStep
      LINE ((i - Params.MinX) / Params.XRange * XSize, YSize * (1 + Params.MinY / Params.YRange) - 5)-((i - Params.MinX) / Params.XRange * XSize, YSize * (1 + Params.MinY / Params.YRange) + 5), 1
   NEXT i

   YStep = 10 ^ INT(LOG(Params.YRange) / 3.5)
   FOR i = 0 TO Params.MinY STEP -YStep
      LINE (-XSize * Params.MinX / Params.XRange - 5, YSize - 1 - (i - Params.MinY) / Params.YRange * YSize)-(-XSize * Params.MinX / Params.XRange + 5, YSize - 1 - (i - Params.MinY) / Params.YRange * YSize), 1
   NEXT i
   FOR i = 0 TO Params.MaxY STEP YStep
      LINE (-XSize * Params.MinX / Params.XRange - 5, YSize - 1 - (i - Params.MinY) / Params.YRange * YSize)-(-XSize * Params.MinX / Params.XRange + 5, YSize - 1 - (i - Params.MinY) / Params.YRange * YSize), 1
   NEXT i


END SUB

' Draw the actual function (in options menu).
SUB GraphF1 (Params AS GraphData)

   DIM Y1(0 TO XSize - 1) AS DOUBLE

   FOR i = 0 TO XSize - 1
      Y1(i) = F1((i / XSize) * Params.XRange + Params.MinX, 0, 0)
   NEXT i

   PSET (0, (Params.MaxY - Y1(0)) / Params.YRange * YSize), 12
   FOR i = 1 TO XSize - 1
      LINE -(i, (Params.MaxY - Y1(i)) / Params.YRange * YSize), 12
   NEXT i

END SUB

' Draw the Fourier transform of the function, using Num sinsoids.
SUB GraphFourier (Params AS GraphData, Num AS INTEGER)

   DIM Y1(0 TO XSize - 1) AS DOUBLE

   FOR i = 0 TO XSize - 1
      Y1(i) = 0
      FOR j = 0 TO Num
         Y1(i) = Y1(i) + FourierCoeffs(1, j) * COS(2 * Pi * j / T0 * ((i / XSize) * Params.XRange + Params.MinX))
         Y1(i) = Y1(i) + FourierCoeffs(2, j) * SIN(2 * Pi * j / T0 * ((i / XSize) * Params.XRange + Params.MinX))
      NEXT j
   NEXT i

   PSET (0, (Params.MaxY - Y1(0)) / Params.YRange * YSize), 14
   FOR i = 1 TO XSize - 1
      LINE -(i, (Params.MaxY - Y1(i)) / Params.YRange * YSize), 14
   NEXT i


END SUB

' Draw 4 graphs, showing the spectra of the function.
' Two of them are a(n) and b(n), these are the coefficients of the COS and
' SIN terms of the Fourier series.  A(n) and Phi(n) form the compact Fourier
' series, A(n) being the magnitude and Phi(n) the phase for each n.
'
' These graphs are another way to show the tables in ShowFourierCoeffs().
SUB GraphFourierCoeffs

   SCREEN 12: CLS

   DIM d(0 TO 50) AS DOUBLE   ' data array

   ' do 4 graphs:    a(n) vs n
   '                 b(n) vs n
   '                 A(n) vs n
   '                 Phi(n) vs n

   ' GRAPH: a(n) vs n in top left corner
   FOR i = 0 TO 50: d(i) = FourierCoeffs(1, i): NEXT i
   SubGraph d(), 60, 20, "a(n)"

   ' GRAPH: b(n) vs n in bottom left corner
   FOR i = 0 TO 50: d(i) = FourierCoeffs(2, i): NEXT i
   SubGraph d(), 60, 260, "b(n)"

   ' GRAPH: A(n) vs n in top right corner
   FOR i = 0 TO 50
      d(i) = SQR(FourierCoeffs(1, i) ^ 2 + FourierCoeffs(2, i) ^ 2)
   NEXT i
   SubGraph d(), 380, 20, "A(n)"

   ' GRAPH: Phi(n) vs n in bottom right corner
   FOR i = 0 TO 50
      j1 = FourierCoeffs(2, i): IF ABS(j1) < .000001 THEN j1 = 0
      j2 = FourierCoeffs(1, i): IF ABS(j2) < .000001 THEN j2 = 0
      d(i) = -ArcTan((j1), (j2))
      IF d(i) < -Pi THEN d(i) = d(i) + 2 * Pi
      IF d(i) > Pi THEN d(i) = d(i) - 2 * Pi
   NEXT i
   SubGraph d(), 380, 260, "Phi(n)"

   LINE (319, 0)-(319, 463), 8
   LINE (0, 239)-(639, 239), 8
   LINE (0, 0)-(639, 463), 8, B
   LOCATE 30, 2: COLOR 7: PRINT Func$;
  
   WHILE INKEY$ = "": WEND
  
END SUB

' Performs a crude numerical integration of the function specified by FF,
' between x0 and x1.
' The values Condition and n are used by F1() to simplify finding Fourier
' coefficients.
FUNCTION IntegrateF1# (x0 AS DOUBLE, x1 AS DOUBLE, Condition AS INTEGER, n AS INTEGER)

   DIM XRange AS DOUBLE, Total AS DOUBLE
   DIM i AS INTEGER

   XRange = x1 - x0
   FOR i = 0 TO IntegrateResolution - 1
      Total = Total + F1((i / IntegrateResolution) * XRange + x0, Condition, n)
   NEXT i

   IntegrateF1 = Total * XRange / IntegrateResolution

END FUNCTION

' Mod2 is a lot like the built-in MOD function, except that with Mod2,
' the values do not have to be integers.
' e.g.  Mod2(4.6, 1.1) will return 0.2
FUNCTION Mod2# (x AS DOUBLE, t AS DOUBLE)
  
   DIM NewX AS DOUBLE

   NewX = x - t * INT(x / t)
   DO UNTIL NewX < 0: NewX = NewX - t: LOOP
   DO UNTIL NewX >= 0: NewX = NewX + t: LOOP
   Mod2 = NewX

END FUNCTION

' Draw a graph of the function and ask the user what to do next.
SUB Options

   DIM n AS INTEGER

   DO
      GraphAxes Params
      GraphF1 Params
      COLOR 15
      LOCATE 1, 40 - LEN(Func$) / 2: PRINT Func$;
      COLOR 7
      LOCATE 20, 1: PRINT " 1. Select a different function";
      LOCATE 21, 1: PRINT " 2. Show table of Fourier coefficients";
      LOCATE 22, 1: PRINT " 3. Show spectra of Fourier coefficients";
      LOCATE 23, 1: PRINT " 4. Build function from sinusoids";
      LOCATE 24, 1: PRINT " 5. Watch rotating phasors";
      LOCATE 25, 1: PRINT " 6. Quit";
      Page = 1 - Page: ShowPage Page
      DO: i$ = INKEY$: LOOP WHILE i$ = ""
      SELECT CASE i$
         CASE "1"
            SCREEN 9, , Page, Page
            DO
               FOR y = 20 TO 25
                  LOCATE y, 1: PRINT SPACE$(80);
               NEXT y
               LOCATE 22, 1: PRINT " There are"; NumFuncs; "defined functions."
               PRINT " Enter a number from 1 to"; NumFuncs; "-> ";
               INPUT F$
               v = VAL(F$)
            LOOP WHILE v < 1 OR v > NumFuncs OR v <> INT(v)
            FF = v
            Params.XRange = 0    ' Force recalculation of graph parameters
            PRINT : PRINT " Please wait, calculating Fourier coefficients....";
            CalculateCoeffs
            Page = 1 - Page: ShowPage Page
         CASE "2"
            ShowFourierCoeffs
            Page = 1 - Page: ShowPage Page
         CASE "3"
            GraphFourierCoeffs
            Page = 1 - Page: ShowPage Page
         CASE "4"
            CLS
            Page = 1 - Page: ShowPage Page
            CLS
            FOR n = 0 TO NUMCOEFFS
               GraphAxes Params
               GraphF1 Params
               GraphFourier Params, n
               LOCATE 1, 1: PRINT "n ="; n
               IF n > 0 THEN
                  DO: i$ = INKEY$: LOOP UNTIL i$ <> ""
                  IF i$ = CHR$(27) THEN Page = 1 - Page: ShowPage Page: EXIT FOR
               END IF
               Page = 1 - Page: ShowPage Page
            NEXT n
         CASE "5"
            DrawWhirly
            Page = 1 - Page: ShowPage Page
         CASE "6"
            SCREEN 0, 0, 0, 0: CLS : END
      END SELECT
   LOOP


END SUB

' A basic building block to make more complicated functions out of.
' Rect(x) = 1 when x is between -0.5 and 0.5, and 0 elsewhere.
'
' Using the form Rect((x - x0) / A), this returns 1 when the difference
' between x and x0 is less than A/2, and 0 otherwise.
'
FUNCTION Rect# (x AS DOUBLE)

   IF ABS(x) > .5 THEN
      Rect = 0
   ELSE
      Rect = 1
   END IF

END FUNCTION

' Draw 4 tables, showing the spectra of the function.
' Two of them are a(n) and b(n), these are the coefficients of the COS and
' SIN terms of the Fourier series.  A(n) and Phi(n) form the compact Fourier
' series, A(n) being the magnitude and Phi(n) the phase for each n.
'
' These tables show the values used for the graphs in GraphFourierCoeffs().
SUB ShowFourierCoeffs

   SCREEN 0, 0, 0, 0: WIDTH 80, 50: CLS

   DIM a AS SINGLE, b AS SINGLE
  
   LOCATE 1, 1
   FOR n = 0 TO 49
      LOCATE n + 1, 1
      a = FourierCoeffs(1, n)
      IF ABS(a) < Tiny THEN a = 0
      b = FourierCoeffs(2, n)
      IF ABS(b) < Tiny THEN b = 0
      Num1$ = Trim$((n))
      COLOR 15: LOCATE , 1: PRINT "a";
      COLOR 7: PRINT Num1$; " = ";
      COLOR 1: PRINT USING "\    \"; Trim$((a));
      COLOR 15: LOCATE , 18: PRINT "b";
      COLOR 7: PRINT Num1$; " = ";
      COLOR 2: PRINT USING "\    \"; Trim$((b));

      COLOR 15: LOCATE , 41: PRINT "A";
      COLOR 7: PRINT Num1$; " = ";
      COLOR 3: PRINT USING "\    \"; Trim$(SQR(a * a + b * b));
      COLOR 15: LOCATE , 58: PRINT "Phi";
      COLOR 7: PRINT Num1$; " = ";
      COLOR 4: PRINT USING "\    \"; Trim$(-ArcTan((b), (a)));
   NEXT n

   WHILE INKEY$ = "": WEND

END SUB

SUB ShowPage (PageNum AS INTEGER)

   ' Switches the visual page to PageNum, puts the active page in the
   ' background and clears it.

   SCREEN ScreenMode, 0, 1 - PageNum, PageNum
   LINE (0, 0)-(XSize - 1, YSize - 1), 0, BF

END SUB

' The Sinc function.
' Sinc(x) is equal to SIN(Pi * x) / (Pi * x).
FUNCTION Sinc# (x AS DOUBLE)

   IF x = 0 THEN
      Sinc = 1
   ELSE
      Sinc = SIN(Pi * x) / (Pi * x)
   END IF

END FUNCTION

' A building block function to make more complicated functions out of.
' The wave has period x, and duty cycle of DC.
' (Duty cycle = percentage of time the function is 1.)
FUNCTION SquareWave# (x AS DOUBLE, DC AS DOUBLE)

   NewX = Mod2(x, 1)

   IF NewX > 1 / 2 THEN NewX = NewX - 1

   IF (DC * 1 / 2) - ABS(NewX) >= 0 THEN
      SquareWave = 1
   ELSE
      SquareWave = 0
   END IF

END FUNCTION

' This function is used 4 times by GraphFourierCoeffs() to draw the 4 little
' graphs.
SUB SubGraph (d() AS DOUBLE, x0 AS INTEGER, y0 AS INTEGER, Label AS STRING)

' We have a screen space of 250x200 pixels, with top left corner (x0, y0).

   COLOR 9

   YMin = 30000: YMax = -30000
   FOR i = 0 TO 50
      IF d(i) < YMin THEN YMin = d(i)
      IF d(i) > YMax THEN YMax = d(i)
   NEXT i
   IF YMin > 0 THEN YMin = 0 ELSE IF YMax < 0 THEN YMax = 0
   YRange = YMax - YMin
   RYMin = YMin: RYMax = YMax: RYRange = YRange
   YMax = YMax + YRange * ExtraYSpace
   YMin = YMin - YRange * ExtraYSpace
   YRange = YMax - YMin
   IF YRange < .001 THEN YRange = 0

   ' Locate where in screen space we have y = 0, draw x axis.
   IF YRange = 0 THEN
      BaseY = 100
   ELSE
      BaseY = 200 + YMin / YRange * 200
   END IF
   IF BaseY >= 0 AND BaseY <= 200 THEN
      LINE (x0, y0 + BaseY)-(x0 + 252, y0 + BaseY), 1
   ELSE
      STOP
   END IF

   ' draw y axis and label
   LINE (x0, y0)-(x0, y0 + 200), 1
   LOCATE (y0 - 8) / 16, (x0 - 8) / 8: PRINT Label$;
   ' draw yMax, yMin and show on axis
   IF YRange = 0 THEN
      LINE (x0 - 2, y0 + BaseY)-(x0 + 2, y0 + BaseY), 1
      LOCATE (8 + y0 + BaseY) / 16, (x0 - 45) / 8
      PRINT USING "\   \"; Trim$(0);
   ELSE
      LINE (x0 - 2, y0 + BaseY - RYMin / YRange * 200)-(x0 + 2, y0 + BaseY - RYMin / YRange * 200), 1
      LOCATE (8 + y0 + BaseY - RYMin / YRange * 200) / 16, (x0 - 45) / 8
      PRINT USING "\   \"; Trim$((RYMin));
      LINE (x0 - 2, y0 + BaseY - RYMax / YRange * 200)-(x0 + 2, y0 + BaseY - RYMax / YRange * 200), 1
      LOCATE (8 + y0 + BaseY - RYMax / YRange * 200) / 16, (x0 - 45) / 8
      PRINT USING "\   \"; Trim$((RYMax));
   END IF

   ' graph data
   FOR n = 0 TO 50
      IF YRange = 0 THEN
         CIRCLE (x0 + n * 5, y0 + BaseY), 1, 14
      ELSE
         LINE (x0 + n * 5, y0 + BaseY)-(x0 + n * 5, y0 + BaseY - d(n) / YRange * 200), 14
         CIRCLE (x0 + n * 5, y0 + BaseY - d(n) / YRange * 200), 1, 14
      END IF
   NEXT n

END SUB

' Rounds of a number so it can fit nicely in a table of coefficients,
' and converts it into a string at the same time.
FUNCTION Trim$ (a AS DOUBLE)

   DIM x AS SINGLE
  
   x = INT(a * 10000) / 10000
   IF ABS(x) < .0001 THEN
      x = 0
   END IF

   Trim = LTRIM$(RTRIM$(STR$(x)))
  
END FUNCTION
