diff --git a/.github/workflows/autocomment_on_PR_merge.yml b/.github/workflows/autocomment_on_PR_merge.yml new file mode 100644 index 0000000..ea6b261 --- /dev/null +++ b/.github/workflows/autocomment_on_PR_merge.yml @@ -0,0 +1,37 @@ +name: Auto Comment on PR Merge + +on: + pull_request: + types: [closed] + +permissions: + issues: write + pull-requests: write + +jobs: + comment: + runs-on: ubuntu-latest + permissions: + pull-requests: write + if: github.event.pull_request.merged == true + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Add Comment to Issue + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMENT=$(cat < Interval to listen -TIMEOUT = 10 # In ms for the event loop +CHUNK = 1024 +RATE = 44100 +INTERVAL = 1 +TIMEOUT = 0.1 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(): +def stop(instance=None): 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(): + progress_bar.value = 0 + btn_stop.disabled = True + btn_listen.disabled = False + btn_pause.disabled = True + btn_resume.disabled = True + btn_save.disabled = True + +def pause(instance=None): if _VARS["stream"] and _VARS["stream"].is_active(): _VARS["stream"].stop_stream() - _VARS["window"]["Pause"].Update(disabled=True) - _VARS["window"]["Resume"].Update(disabled=False) + btn_pause.disabled = True + btn_resume.disabled = False -def resume(): +def resume(instance=None): 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) + btn_pause.disabled = False + btn_resume.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: +def save(instance=None): + folder = 'saved_files' + if not os.path.exists(folder): + os.makedirs(folder) + try: # Save the figure as an image file fig.savefig(f'{folder}/output.png') - sg.popup('Success', f'Image saved as {folder}/output.png') + popup_message('Success', f'Image saved as {folder}/output.png') + # Save the recorded audio data to a file sf.write(f'{folder}/output.wav', _VARS["audioBuffer"], RATE) - sg.popup('Success', f'Audio saved as {folder}/output.wav') + popup_message('Success', f'Audio saved as {folder}/output.wav') + except Exception as e: + popup_message('Error saving files', str(e)) def callback(in_data, frame_count, time_info, status): try: @@ -108,10 +81,11 @@ def callback(in_data, frame_count, time_info, status): traceback.print_exc() return (in_data, pyaudio.paContinue) -def listen(): +def listen(instance=None): try: - _VARS["window"]["Stop"].Update(disabled=False) - _VARS["window"]["Listen"].Update(disabled=True) + btn_stop.disabled = False + btn_listen.disabled = True + btn_pause.disabled = False _VARS["stream"] = pAud.open( format=pyaudio.paInt16, channels=1, @@ -122,70 +96,124 @@ def listen(): ) _VARS["stream"].start_stream() except Exception as e: - sg.popup_error(f"Error: {e}") + popup_message('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 popup_message(title, message): + popup = Popup(title=title, content=Label(text=message), size_hint=(0.8, 0.8)) + popup.open() -# 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 +class MicVisualizerApp(App): + def build(self): + global progress_bar, btn_listen, btn_pause, btn_resume, btn_stop, btn_save, fig, ax, canvas_img -# 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 == 'Spectogram': - close_current_visualizer() - _VARS["current_visualizer_process"] = subprocess.Popen(['python', 'Spectogram.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["audioData"].size != 0: + tab_panel = TabbedPanel(do_default_tab=False) + + # Listening tab + listening_tab = TabbedPanelItem(text='Listening') + listening_layout = BoxLayout(orientation='vertical') + + btn_listen = Button(text='Listen', on_press=listen) + btn_pause = Button(text='Pause', on_press=pause, disabled=True) + btn_resume = Button(text='Resume', on_press=resume, disabled=True) + btn_stop = Button(text='Stop', on_press=stop, disabled=True) + btn_save = Button(text='Save', on_press=save, disabled=True) + btn_exit = Button(text='Exit', on_press=self.stop) + + progress_bar = ProgressBar(max=4000) + + button_layout = BoxLayout(size_hint_y=None, height=50) + button_layout.add_widget(btn_listen) + button_layout.add_widget(btn_pause) + button_layout.add_widget(btn_resume) + button_layout.add_widget(btn_stop) + button_layout.add_widget(btn_save) + button_layout.add_widget(btn_exit) + + listening_layout.add_widget(Label(text='Progress:', size_hint_y=None, height=50)) + listening_layout.add_widget(progress_bar) + listening_layout.add_widget(button_layout) + + listening_tab.add_widget(listening_layout) + tab_panel.add_widget(listening_tab) + + # Visualization tab + visualization_tab = TabbedPanelItem(text='Visualization') + visualization_layout = BoxLayout(orientation='vertical') + + fig, ax = plt.subplots() + canvas_img = Image() + visualization_layout.add_widget(canvas_img) + + visualization_tab.add_widget(visualization_layout) + tab_panel.add_widget(visualization_tab) + + # Settings tab + settings_tab = TabbedPanelItem(text='Settings') + settings_layout = BoxLayout(orientation='vertical') + + rate_label = Label(text='Sample Rate:') + self.rate_input = TextInput(text=str(RATE), multiline=False) + chunk_label = Label(text='Chunk Size:') + self.chunk_input = TextInput(text=str(CHUNK), multiline=False) + + apply_button = Button(text='Apply', on_press=self.apply_settings) + settings_layout.add_widget(rate_label) + settings_layout.add_widget(self.rate_input) + settings_layout.add_widget(chunk_label) + settings_layout.add_widget(self.chunk_input) + settings_layout.add_widget(apply_button) + + settings_tab.add_widget(settings_layout) + tab_panel.add_widget(settings_tab) + + Clock.schedule_interval(self.update_plot, TIMEOUT) + + return tab_panel + + def apply_settings(self, instance): + global RATE, CHUNK try: - _VARS["window"]["-PROG-"].update(np.amax(_VARS["audioData"])) - yf = scipy.fft.fft(_VARS["audioData"]) - xf = np.linspace(0.0, RATE / 2, CHUNK // 2) - ax.clear() + RATE = int(self.rate_input.text) + CHUNK = int(self.chunk_input.text) + popup_message('Settings Applied', 'Sample Rate and Chunk Size updated.') + except ValueError: + popup_message('Invalid Input', 'Please enter valid integer values.') + + def update_plot(self, dt): + if _VARS["audioData"].size != 0: + progress_bar.value = np.amax(_VARS["audioData"]) + yf = scipy.fft.fft(_VARS["audioData"]) + xf = np.linspace(0.0, RATE / 2, CHUNK // 2) + ax.clear() ax.plot( xf, 2.0 / CHUNK * np.abs(yf[: CHUNK // 2]), label='Frequency Spectrum' - ) - ax.set_title("Frequency Spectrum") # Add this line to set the title - ax.set_ylabel("Amplitude") - ax.set_xlabel("Frequency [Hz]") - ax.grid(True) # Enable gridlines - ax.legend() # Add a legend - fig_agg.draw() # redraw the figure - except Exception as e: - print("Error during plotting:", e) - traceback.print_exc() + ) + ax.set_title("Frequency Spectrum") + ax.set_ylabel("Amplitude") + ax.set_xlabel("Frequency [Hz]") + ax.grid(True) + ax.legend() + + self.update_canvas() + + def update_canvas(self): + fig.canvas.draw() + width, height = fig.canvas.get_width_height() + buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) + buf = buf.reshape(height, width, 3) + + texture = Texture.create(size=(width, height)) + texture.blit_buffer(buf.tostring(), colorfmt='rgb', bufferfmt='ubyte') + texture.flip_vertical() + + canvas_img.texture = texture + + def on_stop(self): + stop(None) + if _VARS["current_visualizer_process"]: + close_current_visualizer() + pAud.terminate() + +if __name__ == '__main__': + app = MicVisualizerApp() + app.run() diff --git a/Energy-Frequency-Visualizer.py b/Energy-Frequency-Visualizer.py index 76d65cd..022e524 100644 --- a/Energy-Frequency-Visualizer.py +++ b/Energy-Frequency-Visualizer.py @@ -1,121 +1,85 @@ -import PySimpleGUI as sg +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.button import Button +from kivy.uix.label import Label +from kivy.uix.progressbar import ProgressBar +from kivy.uix.popup import Popup +from kivy.clock import Clock +from kivy.graphics.texture import Texture +from kivy.uix.image import Image +from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem +from kivy.uix.textinput import TextInput + import pyaudio import numpy as np -from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import soundfile as sf import scipy.fft import matplotlib.pyplot as plt -import subprocess +import os # vars: _VARS = { - "window": None, "stream": None, "audioData": np.array([]), "audioBuffer": np.array([]), "current_visualizer_process": None, } -# pysimpleGUI INIT: -AppFont = "Helvetica" -sg.theme("DarkBlue3") - -menu_layout = [ - ['Run Visualizers', ['Energy-Frequency-Visualizer', 'Waveform', 'Spectogram', '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="Frequency 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 energy-frequency plot", layout, finalize=True) -graph = _VARS["window"]["graph"] - # INIT vars: CHUNK = 1024 RATE = 44100 -TIMEOUT = 10 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(): +def stop(instance): 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) - _VARS["window"]["Pause"].Update(disabled=True) - _VARS["window"]["Resume"].Update(disabled=True) - _VARS["window"]["Save"].Update(disabled=True) - -def pause(): + progress_bar.value = 0 + btn_listen.disabled = False + btn_pause.disabled = True + btn_resume.disabled = True + btn_stop.disabled = True + btn_save.disabled = True + +def pause(instance): if _VARS["stream"] and _VARS["stream"].is_active(): _VARS["stream"].stop_stream() - _VARS["window"]["Pause"].Update(disabled=True) - _VARS["window"]["Resume"].Update(disabled=False) + btn_pause.disabled = True + btn_resume.disabled = False -def resume(): +def resume(instance): 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) + btn_pause.disabled = False + btn_resume.disabled = True -def save(): - folder = sg.popup_get_folder('Please select a directory to save the files') - if folder: - try: - # Save the plot as an image file - fig.savefig(f'{folder}/output.png') - sg.popup('Success', f'Image saved as {folder}/output.png') - - # Save the audio data as a WAV file - sf.write(f'{folder}/output.wav', _VARS["audioBuffer"], RATE) - sg.popup('Success', f'Audio saved as {folder}/output.wav') - except FileNotFoundError as fnf_error: - sg.popup_error('File Not Found Error', f'The specified path was not found: {fnf_error}') - except PermissionError as perm_error: - sg.popup_error('Permission Error', f'Permission denied: {perm_error}') - except Exception as e: - sg.popup_error('Error saving files', str(e)) +def save(instance): + folder = 'saved_files' + if not os.path.exists(folder): + os.makedirs(folder) + try: + # Save the plot as an image file + fig.savefig(f'{folder}/output.png') + popup_message('Success', f'Image saved as {folder}/output.png') + + # Save the audio data as a WAV file + sf.write(f'{folder}/output.wav', _VARS["audioBuffer"], RATE) + popup_message('Success', f'Audio saved as {folder}/output.wav') + except Exception as e: + popup_message('Error saving files', str(e)) def callback(in_data, frame_count, time_info, status): _VARS["audioData"] = np.frombuffer(in_data, dtype=np.int16) _VARS["audioBuffer"] = np.append(_VARS["audioBuffer"], _VARS["audioData"]) return (in_data, pyaudio.paContinue) -def listen(): +def listen(instance): try: - _VARS["window"]["Stop"].Update(disabled=False) - _VARS["window"]["Listen"].Update(disabled=True) - _VARS["window"]["Pause"].Update(disabled=False) + btn_listen.disabled = True + btn_pause.disabled = False + btn_stop.disabled = False _VARS["stream"] = pAud.open( format=pyaudio.paInt16, channels=1, @@ -126,52 +90,126 @@ def listen(): ) _VARS["stream"].start_stream() except Exception as e: - sg.popup_error('Error starting the stream', str(e)) + popup_message('Error starting the stream', str(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() -fig_agg = draw_figure(graph.TKCanvas, fig) +def popup_message(title, message): + popup = Popup(title=title, content=Label(text=message), size_hint=(0.8, 0.8)) + popup.open() + +class MicVisualizerApp(App): + def build(self): + global progress_bar, btn_listen, btn_pause, btn_resume, btn_stop, btn_save, fig, ax, canvas_img + + tab_panel = TabbedPanel(do_default_tab=False) + + # Listening tab + listening_tab = TabbedPanelItem(text='Listening') + listening_layout = BoxLayout(orientation='vertical') + + btn_listen = Button(text='Listen', on_press=listen) + btn_pause = Button(text='Pause', on_press=pause, disabled=True) + btn_resume = Button(text='Resume', on_press=resume, disabled=True) + btn_stop = Button(text='Stop', on_press=stop, disabled=True) + btn_save = Button(text='Save', on_press=save, disabled=True) + btn_exit = Button(text='Exit', on_press=self.stop) + + progress_bar = ProgressBar(max=4000) + + button_layout = BoxLayout(size_hint_y=None, height=50) + button_layout.add_widget(btn_listen) + button_layout.add_widget(btn_pause) + button_layout.add_widget(btn_resume) + button_layout.add_widget(btn_stop) + button_layout.add_widget(btn_save) + button_layout.add_widget(btn_exit) -# MAIN LOOP -while True: - event, values = _VARS["window"].read(timeout=TIMEOUT) - if event in (sg.WIN_CLOSED, "Exit"): - stop() + listening_layout.add_widget(Label(text='Progress:', size_hint_y=None, height=50)) + listening_layout.add_widget(progress_bar) + listening_layout.add_widget(button_layout) + + listening_tab.add_widget(listening_layout) + tab_panel.add_widget(listening_tab) + + # Visualization tab + visualization_tab = TabbedPanelItem(text='Visualization') + visualization_layout = BoxLayout(orientation='vertical') + + fig, ax = plt.subplots() + canvas_img = Image() + visualization_layout.add_widget(canvas_img) + + visualization_tab.add_widget(visualization_layout) + tab_panel.add_widget(visualization_tab) + + # Settings tab + settings_tab = TabbedPanelItem(text='Settings') + settings_layout = BoxLayout(orientation='vertical') + + rate_label = Label(text='Sample Rate:') + self.rate_input = TextInput(text=str(RATE), multiline=False) + chunk_label = Label(text='Chunk Size:') + self.chunk_input = TextInput(text=str(CHUNK), multiline=False) + + apply_button = Button(text='Apply', on_press=self.apply_settings) + settings_layout.add_widget(rate_label) + settings_layout.add_widget(self.rate_input) + settings_layout.add_widget(chunk_label) + settings_layout.add_widget(self.chunk_input) + settings_layout.add_widget(apply_button) + + settings_tab.add_widget(settings_layout) + tab_panel.add_widget(settings_tab) + + Clock.schedule_interval(self.update_plot, 0.1) + + return tab_panel + + def apply_settings(self, instance): + global RATE, CHUNK + try: + RATE = int(self.rate_input.text) + CHUNK = int(self.chunk_input.text) + popup_message('Settings Applied', 'Sample Rate and Chunk Size updated.') + except ValueError: + popup_message('Invalid Input', 'Please enter valid integer values.') + + def update_plot(self, dt): + if _VARS["audioData"].size != 0: + progress_bar.value = np.amax(_VARS["audioData"]) + yf = scipy.fft.fft(_VARS["audioData"]) + xf = np.linspace(0.0, RATE / 2, CHUNK // 2) + ax.clear() + energy = (2.0 / CHUNK * np.abs(yf[: CHUNK // 2]))**2 # Compute energy + ax.plot(xf, energy, label='Energy Spectrum') + ax.set_ylabel("Energy") + ax.set_xlabel("Frequency [Hz]") + ax.grid(True) + ax.legend() + + self.update_canvas() + + def update_canvas(self): + fig.canvas.draw() + width, height = fig.canvas.get_width_height() + buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) + buf = buf.reshape(height, width, 3) + + texture = Texture.create(size=(width, height)) + texture.blit_buffer(buf.tostring(), colorfmt='rgb', bufferfmt='ubyte') + texture.flip_vertical() + + canvas_img.texture = texture + + def on_stop(self): + stop(None) if _VARS["current_visualizer_process"]: close_current_visualizer() pAud.terminate() - break - if event == "Listen": - listen() - if event == "Pause": - pause() - if event == "Resume": - resume() - if event == "Stop": - stop() - if event == "Save": - save() - if event in ['Energy-Frequency-Visualizer', 'Waveform', 'Spectogram', 'Intensity-vs-Frequency-and-time']: - close_current_visualizer() - try: - _VARS["current_visualizer_process"] = subprocess.Popen(['python', f'{event}.py']) - except Exception as e: - sg.popup_error('Error running visualizer', str(e)) - _VARS["window"].close() - break - elif _VARS["audioData"].size != 0: - _VARS["window"]["-PROG-"].update(np.amax(_VARS["audioData"])) - yf = scipy.fft.fft(_VARS["audioData"]) - xf = np.linspace(0.0, RATE / 2, CHUNK // 2) - ax.clear() - energy = (2.0 / CHUNK * np.abs(yf[: CHUNK // 2]))**2 # Compute energy - ax.plot(xf, energy, label='Energy Spectrum') - ax.set_ylabel("Energy") - ax.set_xlabel("Frequency [Hz]") - ax.grid(True) - ax.legend() - fig_agg.draw() + +if __name__ == '__main__': + app = MicVisualizerApp() + app.run() diff --git a/Frequency-Energy-plot.py b/Frequency-Energy-plot.py new file mode 100644 index 0000000..74232e9 --- /dev/null +++ b/Frequency-Energy-plot.py @@ -0,0 +1,152 @@ +import PySimpleGUI as sg +import pyaudio +import numpy as np +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import soundfile as sf +import scipy.fft +import matplotlib.pyplot as plt + +# VARS CONSTS: +_VARS = {"window": False, "stream": False, "audioData": np.array([]), "audioBuffer": np.array([])} + +# pysimpleGUI INIT: +AppFont = "Helvetica" +sg.theme("DarkBlue3") + +layout = [ + [ + sg.Graph( + canvas_size=(600, 600), + graph_bottom_left=(-2, -2), + graph_top_right=(102, 102), + background_color="#809AB6", + key="graph", + tooltip="Frequency vs Energy graph" # Tooltip added + ) + ], + [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 Frequency vs Energy 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["window"]["-PROG-"].update(0) + _VARS["window"]["Stop"].Update(disabled=True) + _VARS["window"]["Listen"].Update(disabled=False) + +def pause(): + if _VARS["stream"].is_active(): + _VARS["stream"].stop_stream() + _VARS["window"]["Pause"].Update(disabled=True) + _VARS["window"]["Resume"].Update(disabled=False) + +def resume(): + if 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}/output.png') + sg.popup('Success', f'Image saved as {folder}/output.png') + # Save the recorded audio data to a file + sf.write(f'{folder}/output.wav', _VARS["audioBuffer"], RATE) + sg.popup('Success', f'Audio saved as {folder}/output.wav') + +def callback(in_data, frame_count, time_info, status): + _VARS["audioData"] = np.frombuffer(in_data, dtype=np.int16) + _VARS["audioBuffer"] = np.append(_VARS["audioBuffer"], _VARS["audioData"]) + return (in_data, pyaudio.paContinue) + +def listen(): + _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() + +# INIT: +fig, ax = plt.subplots() +fig_agg = draw_figure(graph.TKCanvas, fig) + +# MAIN LOOP +while True: + event, values = _VARS["window"].read(timeout=TIMEOUT) + if event == "Exit": + stop() + pAud.terminate() + break + # for handling the closing of application + if event == sg.WIN_CLOSED : + _VARS["stream"].stop_stream() + _VARS["stream"].close() + 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() + + elif _VARS["audioData"].size != 0: + _VARS["window"]["-PROG-"].update(np.amax(_VARS["audioData"])) + yy = scipy.fft.fft(_VARS["audioData"]) + xx = np.linspace(0.0, RATE / 2, CHUNK // 2) + ax.clear() + + # Calculating the energy + energy = np.abs(yy[:CHUNK // 2]) ** 2 + + # Plot frequency vs energy + ax.plot(xx, energy, label='Frequency vs Energy') + + # Update axis labels + ax.set_ylabel("Energy") + ax.set_xlabel("Frequency [Hz]") + + ax.grid(True) # Enable gridlines + ax.legend() # Add a legend + fig_agg.draw() # redraw the figure diff --git a/Home.py b/Home.py index dae19bd..7566930 100644 --- a/Home.py +++ b/Home.py @@ -17,7 +17,7 @@ [sg.Button("Spectrogram", **button_style, pad=(10, 10))], [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 - ] +] # Layout for the main landing page layout = [ @@ -40,6 +40,23 @@ def close_current_visualizer(process): process.kill() process.wait() +# Consolidated function to handle GUI updates +def handle_event(event, process): + # Mapping event to corresponding script names + script_mapping = { + "Amplitude-Frequency Visualizer": "Amplitude-Frequency-Visualizer.py", + "Waveform": "Waveform.py", + "Spectrogram": "Spectrogram.py", + "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" + } + + if event in script_mapping: + close_current_visualizer(process) + script_name = script_mapping[event] + return subprocess.Popen([sys.executable, script_name]) + return process + # Main loop current_visualizer_process = None @@ -50,19 +67,6 @@ def close_current_visualizer(process): close_current_visualizer(current_visualizer_process) break - if event in ["Amplitude-Frequency Visualizer", "Waveform", "Spectrogram", "Intensity vs Frequency and Time","Real-Time VU Meter", "Sine Wave", "Square Wave", "Triangle Wave", "Sawtooth Wave"]: - close_current_visualizer(current_visualizer_process) - - # Mapping event to corresponding script names - script_mapping = { - "Amplitude-Frequency Visualizer": "Amplitude-Frequency-Visualizer.py", - "Waveform": "Waveform.py", - "Spectrogram": "Spectrogram.py", - "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" - } - - script_name = script_mapping[event] - current_visualizer_process = subprocess.Popen([sys.executable, script_name]) + current_visualizer_process = handle_event(event, current_visualizer_process) window.close() diff --git a/Index.py b/Index.py index 342c679..4894123 100644 --- a/Index.py +++ b/Index.py @@ -1,18 +1,6 @@ import tkinter as tk import subprocess -def run_spec(): - subprocess.Popen(["python", "Spectogram.py"]) - -def run_wave(): - subprocess.Popen(["python", "Waveform.py"]) - -def run_AvsF(): - subprocess.Popen(["python", "Amplitude-Frequency-Visualizer.py"]) - -def run_IvsF(): - subprocess.Popen(["python", "Intensity-vs-Frequency-and-time.py"]) - def center_window(window, width, height): screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() @@ -22,6 +10,16 @@ def center_window(window, width, height): window.geometry(f"{width}x{height}+{x_coordinate}+{y_coordinate}") +def create_button(frame, text, command, bg, fg, font, row, column): + button = tk.Button(frame, text=text, command=command, bg=bg, fg=fg, relief=tk.FLAT, font=font, bd=0) + button.config(highlightbackground=bg, highlightcolor=bg, highlightthickness=2, borderwidth=0, padx=20, pady=10) + button.grid(row=row, column=column, padx=10, pady=10, sticky="nsew") + button.bind("", lambda e, b=button: b.config(bg="#444444")) # Change button color on hover + button.bind("", lambda e, b=button: b.config(bg=bg)) # Restore button color on leave + +def run_visualizer(script_name): + subprocess.Popen(["python", script_name]) + root = tk.Tk() root.title("Soundscape") root.configure(bg="#1e1e1e") # Dark background color @@ -29,7 +27,6 @@ def center_window(window, width, height): button_bg = "#292929" # Dark gray button_fg = "#FFFFFF" # White button_font = ("Helvetica", 12) # Button font -button_radius = 10 # Button corner radius window_width = 600 window_height = 400 @@ -40,21 +37,17 @@ def center_window(window, width, height): button_frame = tk.Frame(root, bg="#1e1e1e") # Dark background color button_frame.place(relx=0.5, rely=0.5, anchor="center") -# Define button texts and corresponding commands +# Define button texts and corresponding script names buttons = [ - ("Spectogram", run_spec), - ("Waveform", run_wave), - ("Amplitude vs Frequency", run_AvsF), - ("Intensity vs Frequency", run_IvsF) + ("Spectogram", "Spectogram.py"), + ("Waveform", "Waveform.py"), + ("Amplitude vs Frequency", "Amplitude-Frequency-Visualizer.py"), + ("Intensity vs Frequency", "Intensity-vs-Frequency-and-time.py") ] # Create buttons in a matrix layout -for i, (text, command) in enumerate(buttons): +for i, (text, script) in enumerate(buttons): row, column = divmod(i, 2) # 2 buttons per row - button = tk.Button(button_frame, text=text, command=command, bg=button_bg, fg=button_fg, relief=tk.FLAT, font=button_font, bd=0) - button.config(highlightbackground=button_bg, highlightcolor=button_bg, highlightthickness=2, borderwidth=0, padx=20, pady=10) - button.grid(row=row, column=column, padx=10, pady=10, sticky="nsew") - button.bind("", lambda e, b=button: b.config(bg="#444444")) # Change button color on hover - button.bind("", lambda e, b=button: b.config(bg=button_bg)) # Restore button color on leave + create_button(button_frame, text, lambda s=script: run_visualizer(s), button_bg, button_fg, button_font, row, column) -root.mainloop() \ No newline at end of file +root.mainloop() diff --git a/Intensity-vs-Frequency-and-time.py b/Intensity-vs-Frequency-and-time.py index b85bd53..8a1b150 100644 --- a/Intensity-vs-Frequency-and-time.py +++ b/Intensity-vs-Frequency-and-time.py @@ -18,23 +18,22 @@ def build(self): self.current_visualizer_process = None layout = BoxLayout(orientation='vertical', padding=20, spacing=20) - - self.graph = BoxLayout() - self.graph.canvas.add(Color(0.5, 0.5, 0.5, 1)) - self.graph.canvas.add(Rectangle(size=(500, 500))) + + self.graph = BoxLayout(size_hint=(1, 0.6)) + self.graph.bind(size=self.update_heatmap_size) self.progress_bar = ProgressBar(max=4000, size_hint=(1, None), height=20) - + btn_layout = BoxLayout(size_hint=(1, None), height=50) - self.listen_btn = Button(text="Listen", on_press=self.listen) - self.stop_btn = Button(text="Stop", on_press=self.stop, disabled=True) - self.exit_btn = Button(text="Exit", on_press=self.stop) + self.listen_btn = Button(text="Listen", on_press=self.listen, font_size=20) + self.stop_btn = Button(text="Stop", on_press=self.stop, disabled=True, font_size=20) + self.exit_btn = Button(text="Exit", on_press=self.stop, font_size=20) btn_layout.add_widget(self.listen_btn) btn_layout.add_widget(self.stop_btn) btn_layout.add_widget(self.exit_btn) - layout.add_widget(Label(text="Mic to Sound Intensity vs Frequency heatmap", font_size=24, size_hint=(1, 0.1))) + layout.add_widget(Label(text="Mic to Sound Intensity vs Frequency Heatmap", font_size=30, size_hint=(1, 0.1))) layout.add_widget(self.graph) layout.add_widget(self.progress_bar) layout.add_widget(btn_layout) @@ -91,14 +90,16 @@ def compute_intensity_data(self, audio_data, window_size=1024, hop_size=512): def draw_heatmap(self, intensity_data): self.graph.canvas.clear() rows, cols = intensity_data.shape + graph_width, graph_height = self.graph.size + for row in range(rows): for col in range(cols): intensity = intensity_data[row, col] color = self.get_heatmap_color(intensity) - x1 = col * 500 / cols - y1 = 500 - (row + 1) * 500 / rows - x2 = x1 + 500 / cols - y2 = y1 + 500 / rows + x1 = col * graph_width / cols + y1 = graph_height - (row + 1) * graph_height / rows + x2 = x1 + graph_width / cols + y2 = y1 + graph_height / rows with self.graph.canvas: Color(*color) Rectangle(pos=(x1, y1), size=(x2 - x1, y2 - y1)) @@ -110,10 +111,14 @@ def get_heatmap_color(self, intensity, threshold=0.0): intensity_norm = np.log1p(intensity) / 20 color_index = min(int(intensity_norm * len(cmap)), len(cmap) - 1) return cmap[color_index] -if __name__=='__main__': - CHUNK=1024 - RATE=44100 - INTERVAL=1 - TIMEOUT=10 + + def update_heatmap_size(self, instance, value): + self.draw_heatmap(np.zeros((10, 10))) + +if __name__ == '__main__': + CHUNK = 1024 + RATE = 44100 + INTERVAL = 1 + TIMEOUT = 10 pAud = pyaudio.PyAudio() SoundScapeApp().run() diff --git a/Spectrogram.py b/Spectrogram.py index 39e8f57..028f4af 100644 --- a/Spectrogram.py +++ b/Spectrogram.py @@ -18,10 +18,10 @@ _VARS = {"stream": None, "audioData": np.array([])} CHUNK = 1024 RATE = 44100 -pAud = pyaudio.PyAudio() -# Check if audio input is available +# Initialize PyAudio try: + pAud = pyaudio.PyAudio() pAud.get_device_info_by_index(0) except pyaudio.PyAudioError as e: print(f"Error initializing PyAudio: {e}") @@ -32,21 +32,32 @@ def callback(in_data, frame_count, time_info, status): return (in_data, pyaudio.paContinue) def listen(): - _VARS["stream"] = pAud.open( - format=pyaudio.paInt16, - channels=1, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - stream_callback=callback, - ) - _VARS["stream"].start_stream() + if pAud: + try: + _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: + print(f"Error starting audio stream: {e}") + _VARS["stream"] = None + else: + print("PyAudio is not initialized.") def stop(): if _VARS["stream"]: - _VARS["stream"].stop_stream() - _VARS["stream"].close() - _VARS["stream"] = None + try: + _VARS["stream"].stop_stream() + _VARS["stream"].close() + except Exception as e: + print(f"Error stopping audio stream: {e}") + finally: + _VARS["stream"] = None class SpectrogramApp(App): def build(self): @@ -84,10 +95,13 @@ def build(self): def on_listen(self, instance): listen() - self.listen_button.disabled = True - self.stop_button.disabled = False - self.save_button.disabled = False - self.event = Clock.schedule_interval(self.update, 0.1) + if _VARS["stream"]: + self.listen_button.disabled = True + self.stop_button.disabled = False + self.save_button.disabled = False + self.event = Clock.schedule_interval(self.update, 0.1) + else: + print("Failed to start listening.") def on_stop(self, instance): stop() @@ -117,7 +131,8 @@ def save(instance): def on_exit(self, instance): stop() - pAud.terminate() + if pAud: + pAud.terminate() App.get_running_app().stop() def save_figure(self, file_path): diff --git a/mainLanding.py b/mainLanding.py index e9792d8..a2e974a 100644 --- a/mainLanding.py +++ b/mainLanding.py @@ -6,6 +6,7 @@ from kivy.uix.popup import Popup from kivy.clock import Clock from kivy.utils import get_color_from_hex +from kivy.graphics import Color, Rectangle, RoundedRectangle import subprocess import os @@ -16,29 +17,74 @@ def build(self): # Define the initial layout layout = BoxLayout(orientation='vertical', padding=20, spacing=20) - title = Label(text="Welcome to SoundScape", font_size=24, size_hint=(1, 0.2)) - subtitle = Label(text="Explore various audio visualizers", font_size=18, size_hint=(1, 0.1)) + title = Label( + text="Welcome to SoundScape", + font_size=32, + size_hint=(1, 0.15), + color=get_color_from_hex("#006064"), + bold=True, + ) + subtitle = Label( + text="Explore various audio visualizers", + font_size=24, + size_hint=(1, 0.1), + color=get_color_from_hex("#00838f"), + ) description = TextInput( - text=("SoundScape is an innovative application designed to transform audio data into stunning visualizations. " - "Choose from a variety of visualizers to unleash the power of audio visualizations!"), + text=("SoundScape is an innovative application designed to transform audio data into stunning visualizations. "), readonly=True, + multiline=True, background_color=get_color_from_hex("#e1f5fe"), foreground_color=get_color_from_hex("#006064"), - font_size=16, + font_size=18, size_hint=(1, 0.3), ) - button_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.2), spacing=10) - button_layout.add_widget(Button(text="Amplitude-Frequency Visualizer", on_press=self.launch_visualizer)) - button_layout.add_widget(Button(text="Waveform", on_press=self.launch_visualizer)) - button_layout.add_widget(Button(text="Spectrogram", on_press=self.launch_visualizer)) - button_layout.add_widget(Button(text="Intensity vs Frequency and Time", on_press=self.launch_visualizer)) - - theme_button = Button(text="Change Theme", size_hint=(1, 0.1), background_color=(0, 1, 0, 1)) + button_layout = BoxLayout(orientation='vertical', size_hint=(1, 0.3), spacing=10) + visualizers = [ + "Amplitude-Frequency Visualizer", + "Waveform", + "Spectrogram", + "Intensity vs Frequency and Time" + ] + for visualizer in visualizers: + button = Button( + text=visualizer, + text_size=(None, None), + halign='center', + valign='middle', + size_hint=(1, None), + height=50, + background_color=get_color_from_hex("#0288d1"), + color=get_color_from_hex("#ffffff"), + font_size=18, + ) + button.bind(on_press=self.launch_visualizer) + button_layout.add_widget(button) + + theme_button = Button( + text="Change Theme", + size_hint=(1, 0.1), + background_color=get_color_from_hex("#00c853"), + color=get_color_from_hex("#ffffff"), + text_size=(None, None), + halign='center', + valign='middle', + font_size=18, + ) theme_button.bind(on_press=self.change_theme) - exit_button = Button(text="Exit", size_hint=(1, 0.1), background_color=(1, 0, 0, 1)) + exit_button = Button( + text="Exit", + size_hint=(1, 0.1), + background_color=get_color_from_hex("#d32f2f"), + color=get_color_from_hex("#ffffff"), + text_size=(None, None), + halign='center', + valign='middle', + font_size=18, + ) exit_button.bind(on_press=self.stop) layout.add_widget(title) @@ -50,6 +96,12 @@ def build(self): self.layout = layout self.theme_index = 0 + + with layout.canvas.before: + Color(1, 1, 1, 1) # default background color + self.bg_rect = RoundedRectangle(size=layout.size, pos=layout.pos, radius=[20]) + layout.bind(size=self._update_rect, pos=self._update_rect) + return layout def close_current_visualizer(self): @@ -67,17 +119,19 @@ def change_theme(self, instance): self.layout.canvas.before.clear() with self.layout.canvas.before: - from kivy.graphics import Color, Rectangle Color(*theme["bg_color"]) - self.bg_rect = Rectangle(size=self.layout.size, pos=self.layout.pos) - self.layout.bind(size=self._update_rect, pos=self._update_rect) - + self.bg_rect = RoundedRectangle(size=self.layout.size, pos=self.layout.pos, radius=[20]) + self._update_rect(self.layout, None) + for child in self.layout.children: if isinstance(child, Label): child.color = theme["fg_color"] if isinstance(child, Button): child.background_color = theme["bg_color"] child.color = theme["fg_color"] + if isinstance(child, TextInput): + child.background_color = theme["bg_color"] + child.foreground_color = theme["fg_color"] def _update_rect(self, instance, value): self.bg_rect.pos = instance.pos @@ -87,7 +141,7 @@ def launch_visualizer(self, instance): visualizer_scripts = { "Amplitude-Frequency Visualizer": "Amplitude-Frequency-Visualizer.py", "Waveform": "Waveform.py", - "Spectrogram": "Spectogram.py", + "Spectrogram": "Spectrogram.py", "Intensity vs Frequency and Time": "Intensity-vs-Frequency-and-Time.py" } @@ -96,11 +150,14 @@ def launch_visualizer(self, instance): if script_path and os.path.exists(script_path): self.close_current_visualizer() - self.current_visualizer_process = subprocess.Popen(['python', script_path]) + try: + self.current_visualizer_process = subprocess.Popen(['python', script_path]) + except Exception as e: + popup = Popup(title="Error", content=Label(text=f"Failed to launch '{script_path}': {e}"), size_hint=(None, None), size=(400, 200)) + popup.open() else: popup = Popup(title="File Not Found", content=Label(text=f"Script '{script_path}' not found!"), size_hint=(None, None), size=(400, 200)) popup.open() if __name__ == '__main__': SoundScapeApp().run() -