#!/usr/bin/python3
# ===================================================================
# Fast Fourier Transform
#
# URL: dsp.stackexchange.com/questions/74748/
# fft-of-square-wave-what-does-output-represent
# URL: byjus.com/maths/fourier-series/
# URL: lpsa.swarthmore.edu/Fourier/Series/ExFS.html
#
# As you add sine waves of increasingly
# higher frequency, the approximation improves.
# ===================================================================
import math
import draw_xy_axes as ax
import coordinate_conversion as cc
import user_interface as ui
from graphics import *
# -------------------------------------------------------------------
# ---- draw a point
# -------------------------------------------------------------------
def draw_point(win,cx,cy):
wx,wy = cc.center_to_win_coords(cx,cy,win.width,win.height)
p = Point(wx,wy)
p.draw(win)
return p
# -------------------------------------------------------------------
# ---- draw a circle
# -------------------------------------------------------------------
def draw_circle(win,cx,cy,raidus=2):
wx,wy = cc.center_to_win_coords(cx,cy,win.width,win.height)
c = Circle(Point(wx,wy),raidus)
c.setFill('red')
c.setOutline('red')
c.draw(win)
return c
# -------------------------------------------------------------------
# ----- draw lines
# -------------------------------------------------------------------
def draw_line(win,line):
p0 = line[0]
p1 = line[1]
l = Line(Point(p0[0],p0[1]),Point(p1[0],p1[1]))
l.setFill('black')
l.draw(win)
return l
# -------------------------------------------------------------------
# ---- display square wave
# ----
# ---- sub - number of sub-harmonic to add
# -------------------------------------------------------------------
def display_square_wave(win,sub):
scale = 100 # y scale factor
gobj = [] # list of plotted points
for deg in range(-180,180,2):
y = 0 # accmulated y values
for i in range(1,2*(sub+1),2): # odd numbers only
rad = math.radians(i*deg)
y += (4/math.pi)*((1/i)*math.sin(rad))
y = scale * y
gobj.append(draw_point(win,deg,y))
return gobj
# -------------------------------------------------------------------
# ---- display sawtooth wave
# ----
# ---- sub - number of sub-harmonic to add
# -------------------------------------------------------------------
def display_sawtooth_wave(win,sub):
scale = 100 # y scale factor
gobj = [] # list of plotted points
for deg in range(-180,180,2):
y = 0 # accmulated y values
for i in range(1,sub+1):
rad = math.radians(i*deg)
y += - (1/math.pi)*(1/i)*math.sin(rad)
y = scale * y
gobj.append(draw_point(win,deg,y))
return gobj
# -------------------------------------------------------------------
# ---- display triangle wave
# ----
# ---- sub - number of sub-harmonic to add
# -------------------------------------------------------------------
def display_triangle_wave(win,sub):
scale = 100 # y scale factor
gobj = [] # list of plotted points
for deg in range(-180,180,2):
y = 0 # accmulated y values
for i in range(1,2*(sub+1),2): # odd values
rad = math.radians(i*deg)
y += (8/(math.pi**2))*(pow(-1,(i-1)/2)/i**2)*math.sin(rad)
y = scale * y
gobj.append(draw_point(win,deg,y))
return gobj
# -------------------------------------------------------------------
# ---- get the number of sub-harmonics to use
# -------------------------------------------------------------------
def sub_harmonics():
while(True):
i = 0
print()
s = ui.get_user_input(f'Enter sub-harmonics count: ')
if not s: break
tf,i = ui.is_integer(s)
if not tf:
print()
print(f'Non-integer entered - try again')
continue
if i < 1:
print()
print(f'Bad sub-harmonics count ({i}) - try again')
continue
break
return i
# -------------------------------------------------------------------
# ---- square wave option
# -------------------------------------------------------------------
def square_wave_option(win):
pobj = []
sub = sub_harmonics()
if sub < 1:
return pobj
print()
print(f'sub-harmonics count is {sub}')
gobj = display_square_wave(win,sub)
return gobj
# -------------------------------------------------------------------
# ---- saw tooth wave option
# -------------------------------------------------------------------
def sawtooth_option(win):
pobj = []
sub = sub_harmonics()
if sub < 1:
return pobj
print()
print(f'sub-harmonics count is {sub}')
gobj = display_sawtooth_wave(win,sub)
return gobj
# -------------------------------------------------------------------
# ---- trangle wave option
# -------------------------------------------------------------------
def triangle_option(win):
pobj = []
sub = sub_harmonics()
if sub < 1:
return pobj
print()
print(f'sub-harmonics count is {sub}')
gobj = display_triangle_wave(win,sub)
return gobj
# -------------------------------------------------------------------
# ---- main
# -------------------------------------------------------------------
# ---- create graphics window
win = GraphWin("Fourier Transform", 801, 801)
win.setBackground("white")
ax.draw_xy_axes(win)
menu = '''\
+----------------------------------------------------------------+
| Demonstrate How A Fourier Series Work |
| |
| As you add sine/cosine waves of increasingly higher frequency, |
| the approximation improves. |
| |
| Program Environment |
| a. Range used -180 degrees to +180 degrees |
| b. Vertical scale factor is 100 |
+----------------------------------------------------------------+
Option Description
------ ---------------------------
1 square wave
2 sawtooth wave
3 triangle wave
'''
gobj = []
while True:
print()
print(menu)
s = ui.get_user_input('Enter selection: ')
if not s: break
tf,i = ui.is_integer(s)
if not tf:
print()
print(f' Illegal option entered ({s}) - Try again')
continue
if i == 1:
if len(gobj) > 0:
for obj in gobj:
obj.undraw()
gobj = []
gobj = square_wave_option(win)
continue
if i == 2:
if len(gobj) > 0:
for obj in gobj:
obj.undraw()
gobj = []
gobj = sawtooth_option(win)
continue
if i == 3:
if len(gobj) > 0:
for obj in gobj:
obj.undraw()
gobj = []
gobj = triangle_option(win)
continue
print()
print(f'Unknown Option ({i}) - Try again')
# ---- end program
win.close()