├── output.wav
├── images
├── ui.PNG
├── demo.gif
├── H(e^jw).png
├── controls.PNG
├── ui - Copy.PNG
├── zeros&poles.png
├── audio waveform.PNG
├── freq response.png
├── dsp block diagram.PNG
├── low pass filter.PNG
└── low frequency for low pass.png
├── main.py
├── filters.py
├── ui.py
├── README.md
└── audio_init.py
/output.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/output.wav
--------------------------------------------------------------------------------
/images/ui.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/ui.PNG
--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/demo.gif
--------------------------------------------------------------------------------
/images/H(e^jw).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/H(e^jw).png
--------------------------------------------------------------------------------
/images/controls.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/controls.PNG
--------------------------------------------------------------------------------
/images/ui - Copy.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/ui - Copy.PNG
--------------------------------------------------------------------------------
/images/zeros&poles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/zeros&poles.png
--------------------------------------------------------------------------------
/images/audio waveform.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/audio waveform.PNG
--------------------------------------------------------------------------------
/images/freq response.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/freq response.png
--------------------------------------------------------------------------------
/images/dsp block diagram.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/dsp block diagram.PNG
--------------------------------------------------------------------------------
/images/low pass filter.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/low pass filter.PNG
--------------------------------------------------------------------------------
/images/low frequency for low pass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deependra227/Real-Time-Audio-Filtering-using-Python/HEAD/images/low frequency for low pass.png
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from audio_init import pyaudio_init
2 | from ui import build_ui
3 |
4 | if __name__=="__main__":
5 | stream = pyaudio_init() # Return the stream of Pyaudio
6 | build_ui(stream) # Build the ui using tkinter
7 |
8 |
9 |
--------------------------------------------------------------------------------
/filters.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import numpy as np
3 | from math import pi
4 | import sympy
5 | from sympy import symbols,sympify
6 | from sympy.functions import arg
7 | from sympy.utilities.lambdify import lambdify
8 | z = symbols('z')
9 |
10 | Hz = (z**7+ z**6 +z**5 + z**4 + z**3 +z**2 + z+1)/(2*(z**7))
11 |
12 | def update_Hz(input):
13 | global Hz
14 | Hz = sympify(input)
15 |
16 |
17 | def evalHz(N):
18 | k = np.linspace(0,N-2,N)
19 | func = lambdify(z,Hz,'numpy')
20 | H = func(np.exp(1j*((2*np.pi*k)/N)))
21 | H = H/np.amax(np.abs(H))
22 | return H
23 |
24 | def plot_Hz(N, funcname ='H', plotMag = True, plotPhase = True):
25 | k = np.linspace(0,N-2,N)
26 | H = evalHz(N)
27 | magH = np.abs(H)
28 | phaseH = np.angle(H)
29 | hn = np.real(np.fft.ifft(H))
30 | if plotMag:
31 | plt.figure()
32 | plt.plot(k,magH)
33 | plt.ylabel('$|%s(e*{j\omega})|$'%funcname)
34 | plt.xlabel('$\omega/\pi$')
35 | plt.grid()
36 | if plotPhase:
37 | plt.figure()
38 | plt.plot(hn)
39 | plt.ylabel('$\measuredangle %s(e*{j\omega})$'%funcname)
40 | plt.xlabel('$\omega/\pi$')
41 | plt.grid()
42 | plt.show()
43 |
44 |
45 | def lowpass(cutoff,CHUNK):
46 | w = np.linspace(0,CHUNK-2,CHUNK)
47 | return 1*(w <= cutoff)
48 |
49 | def highpass(cutoff,CHUNK):
50 | w = np.linspace(0,CHUNK-2,CHUNK)
51 | return 1*(w >= cutoff)
52 |
53 | def bandpass(lowcut,highcut,CHUNK):
54 | w = np.linspace(0,CHUNK-2,CHUNK)
55 | return 1*((w >= lowcut) & (w<=highcut))
56 |
57 | def bandstop(lowcut,highcut,CHUNK):
58 | w = np.linspace(0,CHUNK-2,CHUNK)
59 | return 1*((w <= lowcut) | (w >=highcut))
60 |
61 | # #***********If h[n] is given************
62 | def hn_filter(hn,CHUNK):
63 | '''
64 | xn: (np.array 1D)
65 | hn: (np.array 1D)
66 | yn: (np.array 1D)
67 | '''
68 | H_z = np.fft.fft(hn,CHUNK)
69 | H_z = H_z/np.amax(H_z)
70 | return H_z
71 |
72 | def zeros_filter(zeors,CHUNK):
73 | global Hz
74 | Hz = 1
75 | for zero in zeors:
76 | Hz = Hz*(z - zero)
77 | return evalHz(CHUNK)
78 |
79 | def lccde_filter(b,CHUNK):
80 | num = np.poly1d(b)
81 | global Hz
82 | Hz = 1.0
83 | zeros = num.roots
84 | for zero in zeros:
85 | Hz = Hz*(z - zero)
86 | return evalHz(CHUNK)
87 |
88 |
--------------------------------------------------------------------------------
/ui.py:
--------------------------------------------------------------------------------
1 | from tkinter import *
2 | from audio_init import *
3 | from filters import update_Hz
4 | from math import pi
5 |
6 | def build_ui(stream):
7 | root = Tk()
8 | root.grid_columnconfigure((0,1,2),weight = 1)
9 | root.title('Real Time Audio Processing')
10 |
11 | Button(root, text='Start',command=lambda: start_stream(stream)).grid(row = 0,column = 0)
12 | Button(root, text='Pause',command=lambda:pause_stream(stream)).grid(row = 0,column = 1)
13 | Button(root, text='Stop',command=lambda:stop_stream(stream,root)).grid(row = 0,column = 2)
14 | Button(root, text='Record Start',command=record_start).grid(row = 1,column = 0)
15 | Button(root, text='Record Stop',command=record_stop).grid(row = 1,column = 1)
16 | Button(root, text='Record Save',command=record_save).grid(row = 1,column = 2)
17 | Button(root, text='Show Freq Response of filter',command=plot_filter_response)
18 |
19 | Hz_string = StringVar()
20 | Label(root, text="Enter H(z) Equation").grid(row = 2, column = 0)
21 | Entry(root,textvariable = Hz_string, font=('calibre',10,'normal')).grid(row = 2,column = 1)
22 | Button(root,text = 'Submit Hz', command = lambda: update_Hz(Hz_string.get())).grid(row = 2,column = 2)
23 |
24 | zeros_string = StringVar()
25 | Label(root, text="Enter Zeros of H(z)").grid(row = 3,column = 0)
26 | Entry(root,textvariable = zeros_string, font=('calibre',10,'normal')).grid(row = 3,column = 1)
27 | Button(root,text = 'Submit Zeros of H(z)', command = lambda: update_zeros(zeros_string.get())).grid(row = 3,column = 2)
28 |
29 | hn_string = StringVar()
30 | Label(root, text="Enter Coeffiecient of h[n]").grid(row = 4,column = 0)
31 | Entry(root,textvariable = hn_string, font=('calibre',10,'normal')).grid(row = 4,column = 1)
32 | Button(root,text = 'Submit Coeffiecient of h[n]', command = lambda: update_hn(hn_string.get())).grid(row = 4,column = 2)
33 |
34 | b_string = StringVar()
35 | Label(root, text="Enter Lccde Coeffiecient of numerator").grid(row = 5,column = 0)
36 | Entry(root,textvariable = b_string, font=('calibre',10,'normal')).grid(row = 5,column = 1)
37 | Button(root,text = 'Submit Lccde Coeffiecient', command = lambda: update_lccde(b_string.get())).grid(row = 5,column = 2)
38 |
39 |
40 | filters = StringVar(root)
41 | filters.set(OPTIONS[0]) # default value
42 |
43 | OptionMenu(root, filters, *OPTIONS).grid(row = 7,column = 0)
44 | Button(root, text='Apply Filter',command=lambda:change_filter(filters.get())).grid(row = 7,column = 1)
45 | Button(root, text='Plot ',command=plot_filter_response).grid(row = 7,column = 2)
46 |
47 | Scale(root,orient=HORIZONTAL, from_=1, to=1000,length=1000,resolution=1,command=change_lowcutoff).grid(row = 8,columnspan = 3)
48 | Scale(root,orient=HORIZONTAL, from_=1, to=1000,length=1000,resolution=1,command=change_highcutoff).grid(row = 9,columnspan = 3)
49 |
50 |
51 | root.mainloop()
52 | stop_stream(stream,root)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Real-Time-Audio-Filtering-using-Python
2 |
3 |
4 | Platform for Audio Filtering (Digital Filters) in Real-Time using Convolution Theorem and Fast Fourier Transform.
5 |
6 | # Features
7 | * Users to configure the specification of the filter using impulse response of the system h[n], H(z) Transfer fucntion either by H(z) equation or by giving zeros/poles of H(z), LCCDE coefficients, and cut-off frequency.
8 |
9 | * It also have in-built Ideal filters like Low Pass Filter, High Pass Filter, Band Pass Filter, and Band Stop filter.
10 |
11 | * Users can also Save the Filtered Audio, and Plot the Frequency Response of the ideal as well as custom filters.
12 |
13 | # Implementation
14 | * Use Pyaudio to get audio in real time.
15 | * Matpoltlib for visualization.
16 | * Tkinter For UI.
17 |
18 | 
19 |
20 | In time domain, filtering is convolution of input x[n] and impulse response of h[n].
21 |
22 |
23 | y[n] = Σ x[k]*h[n-k]
24 |
25 |
26 | where is y[n] is filtered audio.
27 |
28 |
29 | Convolution in time domain is same as product in frequency domain. In frequency domain,
30 |
31 | Y(ejw) = X(ejw)H(ejw)
32 |
33 |
34 | To Convert back into time domain, we have to take inverse dft. Fast and efficient way to take dft is ifft.
35 |
36 |
37 | y[n] = IFFT(Y(ejw))
38 |
39 |
40 | # Controls
41 | 
42 |
43 |
44 | # Demo
45 | Audio Waveform
46 | 
47 |
48 | We have use a low pass filter to filter out high frequency audio.
49 |
50 | .png)
51 | 
52 | 
53 |
54 | * When low frequency audio was passed through the filter.Audio was allowed to pass.
55 |
56 | 
57 |
58 | * When High Frequecny audio was passed through the filter. Audio was blocked and was not allowed to pass.
59 |
60 | 
61 |
62 | * When Ideal Low Pass filter was applied. On decreasing the low cut off frequency the higher frequency audio was blocked.
63 |
64 | 
65 |
--------------------------------------------------------------------------------
/audio_init.py:
--------------------------------------------------------------------------------
1 | from filters import *
2 | from tkinter import *
3 | import pyaudio
4 | import wave
5 | import matplotlib.pyplot as plt
6 | from tkinter import messagebox
7 |
8 | ## Configuation ###
9 | Fs = 5000 #Sampling Rate
10 | low_cutoff_freq = 100 #Low Cut off Freq
11 | high_cutoff_freq = 200 #Low Cut off Freq
12 | order = 5
13 | ###################
14 |
15 | ## Change h[n] ######
16 | hn = [0.5,0.5,0.5,0.5,0.5,0.5,0.5]
17 |
18 | def update_hn(input):
19 | global hn
20 | hn = np.fromstring(input,dtype = np.float32,sep=' ')
21 | #################
22 |
23 | ####### Filiter using Poles and Zeros #####
24 | poles = []
25 | zeros = [1]
26 | gain = 0.5
27 |
28 | def update_zeros(input):
29 | global zeros
30 | zeros = np.fromstring(input,dtype = np.float32,sep=' ')
31 | #####################
32 |
33 | ####### Filiter using Coffiecnet of LCCDE Eq #####
34 | num = [1,1] ## Can't be empty
35 | den = [1]
36 | def update_lccde(b):
37 | global num,den
38 | num = np.fromstring(b,dtype = np.float32,sep=' ')
39 | # den = np.fromstring(a,dtype = np.float32,sep=' ')
40 | #####################
41 |
42 | WIDTH = 2
43 | CHANNELS = 1
44 | RATE = 44100
45 | WAVE_OUTPUT_FILENAME = "output.wav"
46 | CHUNK = 2048
47 | FORMAT = pyaudio.paInt16
48 |
49 | isRecording = False
50 | isSteamOpen = False
51 | isSteamPause = True
52 |
53 |
54 | OPTIONS = [
55 | "No Filiter",
56 | "Low Pass Filter",
57 | "High Pass Filter",
58 | "Band Pass Filter",
59 | "Band Stop Filter",
60 | "h[n] Filter",
61 | "Poles and Zeors Filter",
62 | "LCCDE",
63 | "H(z)"
64 | ]
65 |
66 |
67 | p = pyaudio.PyAudio()
68 | frames = []
69 |
70 | filter_type = OPTIONS[0]
71 |
72 |
73 |
74 | def plot_filter_response():
75 | '''
76 | Plot the response of the filter
77 | '''
78 | print(filter_type)
79 | plt.figure()
80 | H = 1
81 | if filter_type == OPTIONS[0]:
82 | messagebox.showinfo("Title", "No Filter Applied")
83 | return
84 | elif filter_type == OPTIONS[1]:
85 | H = np.abs(lowpass(low_cutoff_freq,CHUNK))
86 | elif filter_type == OPTIONS[2]:
87 | H =np.abs(highpass(high_cutoff_freq,CHUNK))
88 | elif filter_type == OPTIONS[3]:
89 | H = np.abs(bandpass(low_cutoff_freq,high_cutoff_freq,CHUNK))
90 | elif filter_type == OPTIONS[4]:
91 | H = np.abs(bandstop(low_cutoff_freq,high_cutoff_freq,CHUNK))
92 | elif filter_type == OPTIONS[5]:
93 | H = np.abs(hn_filter(hn,CHUNK))
94 | elif filter_type == OPTIONS[6]:
95 | H = np.abs(zeros_filter(zeros,CHUNK))
96 | elif filter_type == OPTIONS[7]:
97 | H = np.abs(lccde_filter(num,CHUNK))
98 | elif filter_type == OPTIONS[8]:
99 | H = np.abs(evalHz(CHUNK))
100 | freqs = np.fft.fftfreq(len(H))
101 | plt.plot(freqs,H)
102 | plt.show()
103 |
104 | def change_lowcutoff(value=5):
105 | '''
106 | Change the value of Low Cut off Freq
107 | '''
108 | global low_cutoff_freq
109 | low_cutoff_freq = float(value)
110 | def change_highcutoff(value=5):
111 | '''
112 | Change the value of High Cut off Freq
113 | '''
114 | global high_cutoff_freq
115 | high_cutoff_freq = float(value)
116 |
117 |
118 |
119 | def pyaudio_init():
120 | '''
121 | Return : Stream of pyaudio
122 | '''
123 | stream = p.open(
124 | format=FORMAT,
125 | channels=CHANNELS,
126 | rate=RATE,
127 | input=True,
128 | output=True,
129 | frames_per_buffer=CHUNK
130 | )
131 | global isSteamOpen
132 | isSteamOpen = True
133 | return stream
134 |
135 | def record_start():
136 | '''
137 | Start Recording
138 | '''
139 | global isRecording
140 | isRecording = True
141 | print('recording started')
142 |
143 | def record_stop():
144 | '''
145 | Pause Recording
146 | '''
147 | global isRecording
148 | isRecording = False
149 | print('recording stopped')
150 |
151 |
152 | def record_save():
153 | '''
154 | Save Recording
155 | '''
156 | wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
157 | wf.setnchannels(CHANNELS)
158 | wf.setsampwidth(p.get_sample_size(FORMAT))
159 | wf.setframerate(RATE)
160 | wf.writeframes(b''.join(frames))
161 | wf.close()
162 | print('recording saved')
163 |
164 |
165 | def plot():
166 | '''
167 | Plots the Waveform (input Audio) and Filterd Audio Plot
168 | '''
169 | x = np.arange(0, 2 * CHUNK, 2)
170 | fig, (ax1,ax2) = plt.subplots(2)
171 | # create a line object with random data
172 | ax1.plot(x, np.random.rand(CHUNK), '-', lw=2)
173 | ax2.plot(x, np.random.rand(CHUNK), '-', lw=2)
174 | plt.ion()
175 | # basic formatting for the axes
176 | ax1.set_title('FFT of input')
177 | # ax1.set_xlabel('samples')
178 | # ax1.set_ylabel('volume')
179 | ax1.set_ylim(0,2000)
180 | ax1.set_xlim(0, CHUNK)
181 | plt.setp(ax1, xticks=[0, CHUNK], yticks=[ 0, 2000])
182 |
183 | ax2.set_title(filter_type)
184 | # ax2.set_xlabel('samples')
185 | # ax2.set_ylabel('volume')
186 | ax2.set_ylim(0,2000)
187 | # freqs = np.fft.fftfreq(CHUNK)
188 | ax2.set_xlim(0, CHUNK)
189 | plt.setp(ax2, xticks=[0,CHUNK], yticks=[0,2000])
190 |
191 | # show the plot
192 | fig.tight_layout()
193 |
194 | plt.show(block=False)
195 | return fig,ax1,ax2
196 |
197 | def dft(s):
198 | N = len(s) # N show the length of signal
199 |
200 | # (S show the DFT points)
201 | S = [0 for _ in range(N)] # Initialization the S with 0
202 |
203 | # DFT calculation
204 | for i in range(N):
205 | for j in range(N):
206 | tmp = [((0-1j)*(2*np.pi*i*j)) / N]
207 | S[i] += s[j] * np.exp(tmp)
208 | return s
209 |
210 | def apply_filter(X):
211 | '''
212 | Apply the selected Filiter
213 | '''
214 | if filter_type == OPTIONS[0]:
215 | # messagebox.showinfo("Title", "No Filter Applied")
216 | return X
217 | elif filter_type == OPTIONS[1]:
218 | return X*lowpass(low_cutoff_freq,CHUNK)
219 | elif filter_type == OPTIONS[2]:
220 | return X*highpass(high_cutoff_freq,CHUNK)
221 | elif filter_type == OPTIONS[3]:
222 | return X*bandpass(low_cutoff_freq,high_cutoff_freq,CHUNK)
223 | elif filter_type == OPTIONS[4]:
224 | return X*bandstop(low_cutoff_freq,high_cutoff_freq,CHUNK)
225 | elif filter_type == OPTIONS[5]:
226 | return X*hn_filter(hn,CHUNK)
227 | elif filter_type == OPTIONS[6]:
228 | return X*zeros_filter(zeros,CHUNK)
229 | elif filter_type == OPTIONS[7]:
230 | return X*lccde_filter(num,CHUNK)
231 | elif filter_type == OPTIONS[8]:
232 | return X*evalHz(CHUNK)
233 |
234 |
235 |
236 |
237 | def start_stream(stream):
238 | '''
239 | Start Listerning
240 | '''
241 | if isSteamOpen is True: stream.start_stream()
242 | else:
243 | print("Can't start Session ended")
244 | return
245 | global isSteamPause
246 | isSteamPause = False
247 | print('started',isSteamPause,isSteamOpen)
248 |
249 | if plt.fignum_exists(1):
250 | fig = plt.figure(1)
251 | ax1 = fig.axes[0]
252 | ax2 = fig.axes[1]
253 | else :
254 | fig,ax1,ax2 = plot()
255 |
256 | line1 = ax1.lines[0]
257 | line2 = ax2.lines[0]
258 |
259 |
260 | while isSteamOpen is True and isSteamPause is False :
261 | # Get audio in bytes
262 | data = stream.read(CHUNK)
263 |
264 | # convert byte data to ndarray
265 | data_np = np.frombuffer(data,dtype=np.int16)
266 | input = data_np.astype(np.float32)
267 |
268 | # update Y axis of input plot
269 | X = np.fft.fft(input,CHUNK)
270 | line1.set_ydata(np.abs(X[0:CHUNK])/(CHUNK))
271 |
272 | # apply filter with given input
273 | Y = apply_filter(X)
274 |
275 | y = np.fft.ifft(Y)
276 | y = np.real(y)
277 | # Update Y-axis of filter plot
278 | line2.set_ydata(np.abs(Y[0:CHUNK])/(CHUNK))
279 |
280 |
281 | y = y.astype(np.int16)
282 | # Convert ndarray back to bytes and play back immediately
283 | stream.write(y.tobytes())
284 |
285 | #If recording is on, store the filtered audio.
286 | if isRecording is True:
287 | global frames
288 | frames.append(y.tobytes())
289 | #Update the plot
290 | try:
291 | fig.canvas.draw()
292 | fig.canvas.flush_events()
293 |
294 | except :
295 | print('stream stopped')
296 | break
297 |
298 |
299 | def pause_stream(stream):
300 | '''
301 | Pause Stream
302 | '''
303 | global isSteamPause
304 | isSteamPause = True
305 | stream.stop_stream()
306 | print('paused')
307 |
308 | def stop_stream(stream,root):
309 | '''
310 | Stop Stream
311 | '''
312 | global isSteamOpen
313 | isSteamOpen = False
314 | print('stream stopped')
315 | stream.close()
316 | p.terminate()
317 | try:
318 | root.destroy()
319 | except :
320 | print('Root already destroyed')
321 |
322 |
323 | def change_filter(filter):
324 | '''
325 | Change the Filter
326 | '''
327 | global filter_type
328 | filter_type = filter
329 | fig = plt.gcf()
330 | ax2 = fig.axes[1]
331 | ax2.set_title(filter)
332 |
333 | print(filter_type)
334 | return
335 |
336 |
337 |
338 |
--------------------------------------------------------------------------------