diff --git a/Chroma-Feature-Visualizer.py b/Chroma-Feature-Visualizer.py new file mode 100644 index 0000000..a952808 --- /dev/null +++ b/Chroma-Feature-Visualizer.py @@ -0,0 +1,71 @@ +import numpy as np +import librosa +import librosa.display +import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation +import pyaudio +import threading + +FRAME_SIZE = 2048 +HOP_SIZE = 512 +SR = 22050 +BUFFER_SIZE = 10 + +p = pyaudio.PyAudio() +stream = p.open(format=pyaudio.paFloat32, channels=1, rate=SR, input=True, frames_per_buffer=HOP_SIZE) + +# Initialize the plot +fig, ax = plt.subplots() +chroma_data = np.zeros((12, BUFFER_SIZE)) +img = ax.imshow(chroma_data, aspect='auto', cmap='inferno', origin='lower') +fig.colorbar(img, ax=ax) +ax.set_title('Chroma Feature Visualizer') +ax.set_xlabel('Time') +ax.set_ylabel('Pitch Class') + +is_paused = False + +# Function to update the plot +def update(frame): + global chroma_data, is_paused + if not is_paused: + data = np.frombuffer(stream.read(HOP_SIZE), dtype=np.float32) + chroma = librosa.feature.chroma_stft(y=data, sr=SR, n_fft=FRAME_SIZE, hop_length=HOP_SIZE) + chroma_db = librosa.amplitude_to_db(chroma, ref=np.max) + chroma_data = np.roll(chroma_data, -1, axis=1) + chroma_data[:, -1] = np.mean(chroma_db, axis=1) + img.set_data(chroma_data) + return [img] + +# Function to toggle pause/resume +def toggle_pause(event): + global is_paused + is_paused = not is_paused + +# Adding a button to pause/resume +ax_pause = plt.axes([0.81, 0.01, 0.1, 0.075]) +btn_pause = plt.Button(ax_pause, 'Pause/Resume') +btn_pause.on_clicked(toggle_pause) + +# Function to adjust color map +def change_colormap(event): + current_cmap = img.get_cmap().name + new_cmap = 'inferno' if current_cmap == 'viridis' else 'viridis' + img.set_cmap(new_cmap) + fig.canvas.draw() + +# Adding a button to change color map +ax_colormap = plt.axes([0.61, 0.01, 0.15, 0.075]) +btn_colormap = plt.Button(ax_colormap, 'Change Color Map') +btn_colormap.on_clicked(change_colormap) + +# Create an animation +ani = FuncAnimation(fig, update, frames=BUFFER_SIZE, blit=True, interval=50) + +# Start the plot +plt.show() + +# Close the stream when done +stream.stop_stream() +stream.close() +p.terminate() diff --git a/Lissajous-Curves-Visualizer.py b/Lissajous-Curves-Visualizer.py new file mode 100644 index 0000000..dc76971 --- /dev/null +++ b/Lissajous-Curves-Visualizer.py @@ -0,0 +1,124 @@ +import numpy as np +import pyaudio +from kivy.app import App +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.button import Button +from kivy.uix.widget import Widget +from kivy.clock import Clock +from kivy.graphics import Line, Color +import random +import subprocess + +# Constants +CHUNK = 1024 +RATE = 44100 + +class LissajousWidget(Widget): + def __init__(self, **kwargs): + super(LissajousWidget, self).__init__(**kwargs) + self.points = [] + self.offset_x = self.width / 2 + self.offset_y = self.height / 2 + + def update(self, data, a=5, b=4, delta=np.pi / 2): + self.canvas.clear() + with self.canvas: + Color(1, 0, 0) + t = np.linspace(0, 2 * np.pi, 1000) + amplitude = np.max(data) / 32768.0 * 2 # Increase size by scaling amplitude + x = np.sin(a * t + delta) * amplitude * self.width / 2 + self.width / 2 + self.offset_x + y = np.sin(b * t) * amplitude * self.height / 2 + self.height / 2 + self.offset_y + self.points = list(zip(x, y)) + flattened_points = [coord for point in self.points for coord in point] + Line(points=flattened_points, width=1.5) + + # Randomly move the curves over the screen + self.offset_x = random.uniform(-self.width / 2, self.width / 2) + self.offset_y = random.uniform(-self.height / 2, self.height / 2) + +class AudioVisualizerApp(App): + def build(self): + # Initialize PyAudio + self.pAud = pyaudio.PyAudio() + try: + self.pAud.get_device_info_by_index(0) + except pyaudio.PyAudioError as e: + print(f"Error initializing PyAudio: {e}") + self.pAud = None + + # Main layout + self.layout = BoxLayout(orientation='vertical', padding=10, spacing=10) + + # Lissajous widget + self.lissajous_widget = LissajousWidget() + + # Buttons + self.listen_button = Button(text='Listen', size_hint=(None, None), size=(100, 50)) + self.listen_button.bind(on_press=self.listen) + + self.stop_button = Button(text='Stop', size_hint=(None, None), size=(100, 50), disabled=True) + self.stop_button.bind(on_press=self.stop) + + self.exit_button = Button(text='Exit', size_hint=(None, None), size=(100, 50)) + self.exit_button.bind(on_press=self.close_app) + + self.button_layout = BoxLayout(size_hint=(1, None), height=50, spacing=10) + self.button_layout.add_widget(self.listen_button) + self.button_layout.add_widget(self.stop_button) + self.button_layout.add_widget(self.exit_button) + + self.layout.add_widget(self.lissajous_widget) + self.layout.add_widget(self.button_layout) + + Clock.schedule_interval(self.update_plot, 1.0 / 30.0) # Update plot every 1/30th of a second + + return self.layout + + def update_plot(self, dt): + if hasattr(self, 'audioData') and self.audioData.size != 0: + self.lissajous_widget.update(self.audioData) + + def listen(self, instance): + self.stop_button.disabled = False + self.listen_button.disabled = True + if self.pAud: + try: + self.stream = self.pAud.open( + format=pyaudio.paInt16, + channels=1, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=self.callback, + ) + self.stream.start_stream() + except pyaudio.PyAudioError as e: + print(f"Error opening stream: {e}") + self.listen_button.disabled = False + self.stop_button.disabled = True + + def stop(self, instance=None): + if hasattr(self, 'stream'): + self.stream.stop_stream() + self.stream.close() + del self.stream + self.stop_button.disabled = True + self.listen_button.disabled = False + + def callback(self, in_data, frame_count, time_info, status): + self.audioData = np.frombuffer(in_data, dtype=np.int16) + return (in_data, pyaudio.paContinue) + + def close_app(self, instance): + self.stop() + if self.pAud: + self.pAud.terminate() + App.get_running_app().stop() + + def run_visualizer(self, visualizer): + self.stop() + subprocess.Popen(['python', visualizer]) + self.close_app(None) + +if __name__ == '__main__': + AudioVisualizerApp().run() diff --git a/mainLanding.py b/mainLanding.py index f1a8e54..056ffd2 100644 --- a/mainLanding.py +++ b/mainLanding.py @@ -47,7 +47,9 @@ def build(self): "Waveform", "Spectrogram", "Intensity vs Frequency and Time", - "Depth-Perspective Visualizer" + "Depth-Perspective Visualizer", + "Chroma-Feature Visualizer" + "Lissajous-Curves Visualizer" ] for visualizer in visualizers: button = Button( @@ -144,7 +146,9 @@ def launch_visualizer(self, instance): "Waveform": "Waveform.py", "Spectrogram": "Spectrogram.py", "Intensity vs Frequency and Time": "Intensity-vs-Frequency-and-Time.py", - "Depth-Perspective Visualizer": "Depth-Perspective-Visualizer.py" + "Depth-Perspective Visualizer": "Depth-Perspective-Visualizer.py", + "Chroma-Feature Visualizer": "Chroma-Feature-Visualizer.py" + "Lissajous-Curves Visualizer": "Lissajous-Curves-Visualizer.py" } script_name = instance.text diff --git a/requirements.txt b/requirements.txt index effaaa2..79399ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ PyAudio PySimpleGUI sounddevice soundfile +librosa