Skip to content

Commit

Permalink
Energy envelope-over-time (#183)
Browse files Browse the repository at this point in the history
* Energyenvelope-over-time

* Added new visualizer for 'Energy envelope-over-time' and added this file to the main file.
  • Loading branch information
PDBharadwaj authored Aug 4, 2024
1 parent 6628562 commit 513f34f
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
196 changes: 196 additions & 0 deletions Energyenvelope-over-time.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 2 additions & 0 deletions Home.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions Index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 513f34f

Please sign in to comment.