#!/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()