Skip to content

Commit

Permalink
Added power spectral density curve
Browse files Browse the repository at this point in the history
  • Loading branch information
PDBharadwaj committed Jul 16, 2024
1 parent 6e7f87c commit 1a7db17
Showing 1 changed file with 186 additions and 0 deletions.
186 changes: 186 additions & 0 deletions Power-spectral-density.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import PySimpleGUI as sg
import pyaudio
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import soundfile as sf
import matplotlib.pyplot as plt
import subprocess
import traceback
from scipy.signal import welch

# VARS CONSTS:

_VARS = {
"window": False,
"stream": False,
"audioData": np.array([]),
"audioBuffer": np.array([]),
"current_visualizer_process": None,
}

# PySimpleGUI INIT:
AppFont = "Helvetica"
sg.theme("DarkBlue3")

menu_layout = [
['Run Visualizers', ['Amplitude-Frequency-Visualizer', 'Waveform', 'Spectrogram', 'Power-Spectral-Density']],
]

layout = [
[sg.Menu(menu_layout)],
[
sg.Graph(
canvas_size=(600, 600),
graph_bottom_left=(-2, -2),
graph_top_right=(102, 102),
background_color="#809AB6",
key="graph",
tooltip="Power Spectral Density graph"
)
],
[sg.Text("Progress:", text_color='white', font=('Helvetica', 15, 'bold')), sg.ProgressBar(4000, orientation="h", size=(20, 20), key="-PROG-")],
[
sg.Button("Listen", font=AppFont, tooltip="Start listening"),
sg.Button("Pause", font=AppFont, disabled=True, tooltip="Pause listening"),
sg.Button("Resume", font=AppFont, disabled=True, tooltip="Resume listening"),
sg.Button("Stop", font=AppFont, disabled=True, tooltip="Stop listening"),
sg.Button("Save", font=AppFont, disabled=True, tooltip="Save the plot"),
sg.Button("Exit", font=AppFont, tooltip="Exit the application"),
],
]

_VARS["window"] = sg.Window("Mic to power spectral density plot", layout, finalize=True)
graph = _VARS["window"]["graph"]

# INIT vars:
CHUNK = 1024 # Samples: 1024, 512, 256, 128
RATE = 44100 # Equivalent to Human Hearing at 40 kHz
INTERVAL = 1 # Sampling Interval in Seconds -> Interval to listen
TIMEOUT = 10 # In ms for the event loop
pAud = pyaudio.PyAudio()

# FUNCTIONS:

def draw_figure(canvas, figure):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side="top", fill="both", expand=1)
return figure_canvas_agg

def stop():
if _VARS["stream"]:
_VARS["stream"].stop_stream()
_VARS["stream"].close()
_VARS["stream"] = None
_VARS["window"]["-PROG-"].update(0)
_VARS["window"]["Stop"].Update(disabled=True)
_VARS["window"]["Listen"].Update(disabled=False)

def pause():
if _VARS["stream"] and _VARS["stream"].is_active():
_VARS["stream"].stop_stream()
_VARS["window"]["Pause"].Update(disabled=True)
_VARS["window"]["Resume"].Update(disabled=False)

def resume():
if _VARS["stream"] and not _VARS["stream"].is_active():
_VARS["stream"].start_stream()
_VARS["window"]["Pause"].Update(disabled=False)
_VARS["window"]["Resume"].Update(disabled=True)

def save():
# Ask the user for a directory to save the image file
folder = sg.popup_get_folder('Please select a directory to save the files')
if folder:
# Save the figure as an image file
fig.savefig(f'{folder}/psd_output.png')
sg.popup('Success', f'Image saved as {folder}/psd_output.png')
# Save the recorded audio data to a file
sf.write(f'{folder}/psd_output.wav', _VARS["audioBuffer"], RATE)
sg.popup('Success', f'Audio saved as {folder}/psd_output.wav')

def callback(in_data, frame_count, time_info, status):
try:
_VARS["audioData"] = np.frombuffer(in_data, dtype=np.int16)
_VARS["audioBuffer"] = np.append(_VARS["audioBuffer"], _VARS["audioData"])
except Exception as e:
print("Error in callback:", e)
traceback.print_exc()
return (in_data, pyaudio.paContinue)

def listen():
try:
_VARS["window"]["Stop"].Update(disabled=False)
_VARS["window"]["Listen"].Update(disabled=True)
_VARS["stream"] = pAud.open(
format=pyaudio.paInt16,
channels=1,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=callback,
)
_VARS["stream"].start_stream()
except Exception as e:
sg.popup_error(f"Error: {e}")

def close_current_visualizer():
if _VARS["current_visualizer_process"] and _VARS["current_visualizer_process"].poll() is None:
_VARS["current_visualizer_process"].kill()

# INIT:
fig, ax = plt.subplots() # create a figure and an axis object
fig_agg = draw_figure(graph.TKCanvas, fig) # draw the figure on the graph

# MAIN LOOP
while True:
event, values = _VARS["window"].read(timeout=TIMEOUT)
if event == "Exit" or event == sg.WIN_CLOSED:
stop()
pAud.terminate()
break
if event == "Listen":
listen()
_VARS["window"]["Save"].Update(disabled=False)
if event == "Pause":
pause()
if event == "Resume":
resume()
if event == "Stop":
stop()
if event == "Save":
save()
if event == 'Amplitude-Frequency-Visualizer':
close_current_visualizer()
_VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Amplitude-Frequency-Visualizer.py'])
_VARS["window"].close()
break
if event == 'Waveform':
close_current_visualizer()
_VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Waveform.py'])
_VARS["window"].close()
break
if event == 'Spectrogram':
close_current_visualizer()
_VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Spectrogram.py'])
_VARS["window"].close()
break
if event == 'Power-Spectral-Density':
close_current_visualizer()
_VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Power-Spectral-Density.py'])
_VARS["window"].close()
break

elif _VARS["audioData"].size != 0:
try:
_VARS["window"]["-PROG-"].update(np.amax(_VARS["audioData"]))
f, Pxx = welch(_VARS["audioData"], RATE, nperseg=CHUNK, scaling='density')
ax.clear()
ax.semilogy(f, Pxx)
ax.set_title("Power Spectral Density")
ax.set_ylabel("Power/Frequency [dB/Hz]")
ax.set_xlabel("Frequency [Hz]")
fig_agg.draw()
except Exception as e:
print("Error during plotting:", e)
traceback.print_exc()

0 comments on commit 1a7db17

Please sign in to comment.