├── 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 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/dsp%20block%20diagram.PNG)
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 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/controls.PNG)
42 | 43 | 44 | # Demo 45 | Audio Waveform 46 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/audio%20waveform.PNG)
47 | 48 | We have use a low pass filter to filter out high frequency audio. 49 | 50 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/H(e%5Ejw).png)
51 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/freq%20response.png)
52 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/zeros%26poles.png)
53 | 54 | * When low frequency audio was passed through the filter.Audio was allowed to pass. 55 | 56 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/low%20frequency%20for%20low%20pass.png)
57 |
58 | * When High Frequecny audio was passed through the filter. Audio was blocked and was not allowed to pass. 59 | 60 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/low%20pass%20filter.PNG)
61 | 62 | * When Ideal Low Pass filter was applied. On decreasing the low cut off frequency the higher frequency audio was blocked. 63 | 64 | ![](https://github.com/deependra227/Real-Time-Audio-Filtering-using-Python/blob/master/images/demo.gif)
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 | --------------------------------------------------------------------------------