diff --git a/Energyenvelope-over-time.py b/Energyenvelope-over-time.py new file mode 100644 index 0000000..7c84c85 --- /dev/null +++ b/Energyenvelope-over-time.py @@ -0,0 +1,196 @@ +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 hilbert, decimate + +# 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', 'Intensity-vs-Frequency-and-time']], +] + +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="Energy envelope over time" + ) + ], + [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 energy envelope 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}/energy_envelope_output.png') + sg.popup('Success', f'Image saved as {folder}/energy_envelope_output.png') + # Save the recorded audio data to a file + sf.write(f'{folder}/energy_envelope_output.wav', _VARS["audioBuffer"], RATE) + sg.popup('Success', f'Audio saved as {folder}/energy_envelope_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() + +def calculate_energy_envelope(signal, chunk_size=1024, decimation_factor=20): + analytic_signal = hilbert(signal) + amplitude_envelope = np.abs(analytic_signal) + energy_envelope = decimate(amplitude_envelope, decimation_factor) + time_per_sample = decimation_factor / RATE + time_axis = np.arange(0, len(energy_envelope)) * time_per_sample + return time_axis, energy_envelope + +# 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 == 'Intensity-vs-Frequency-and-time': + close_current_visualizer() + _VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Intensity-vs-Frequency-and-time.py']) + _VARS["window"].close() + break + + elif _VARS["audioBuffer"].size != 0: + try: + _VARS["window"]["-PROG-"].update(np.amax(_VARS["audioData"])) + ax.clear() + time_axis, energy_envelope = calculate_energy_envelope(_VARS["audioBuffer"]) + ax.plot(time_axis, energy_envelope, label='Energy Envelope') + ax.set_title("Energy Envelope over Time") + ax.set_ylabel("Energy") + ax.set_xlabel("Time [sec]") + ax.grid(True) + ax.legend() + fig_agg.draw() + except Exception as e: + print("Error during plotting:", e) + traceback.print_exc() diff --git a/Home.py b/Home.py index e624600..bbd92e4 100644 --- a/Home.py +++ b/Home.py @@ -18,6 +18,7 @@ [sg.Button("Intensity vs Frequency and Time", **button_style, pad=(10, 10))], [sg.Button("Real-Time VU Meter", **button_style, pad=(10, 10))], # New button for Triangle Wave [sg.Button("Power spectral density curve", **button_style, pad=(10, 10))], + [sg.Button("Energy envelope-over-time", **button_style, pad=(10, 10))], ] # Layout for the main landing page @@ -51,6 +52,7 @@ def handle_event(event, process): "Intensity vs Frequency and Time": "Intensity-vs-Frequency-and-time.py", "Real-Time VU Meter": "Real-Time VU Meter.py", # Added button for "Triangle Wave" "Power spectral density curve": "Power-spectral-density.py", + "Energy envelope-over-time": "Energyenvelope-over-time.py", } if event in script_mapping: diff --git a/Index.py b/Index.py index 4894123..ddc5e6b 100644 --- a/Index.py +++ b/Index.py @@ -43,6 +43,9 @@ def run_visualizer(script_name): ("Waveform", "Waveform.py"), ("Amplitude vs Frequency", "Amplitude-Frequency-Visualizer.py"), ("Intensity vs Frequency", "Intensity-vs-Frequency-and-time.py") + ("Power spectral density curve", "Power-spectral-density.py") + ("Energy envelope-over-time", "Energyenvelope-over-time.py") + ] # Create buttons in a matrix layout