Ethereal Spectrum Analyzer Python GUI

👤 Sharing: AI
import tkinter as tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import sounddevice as sd
import threading
import time

class SpectrumAnalyzerApp:
    def __init__(self, master):
        self.master = master
        master.title("Ethereal Spectrum Analyzer")

        self.sample_rate = 44100  # Sample rate
        self.fft_size = 2048      # FFT window size
        self.running = False

        # --- GUI Elements ---
        self.start_button = ttk.Button(master, text="Start", command=self.start_analysis)
        self.start_button.pack(pady=5)

        self.stop_button = ttk.Button(master, text="Stop", command=self.stop_analysis, state=tk.DISABLED)
        self.stop_button.pack(pady=5)

        # Frame for the plot and info button
        self.plot_frame = tk.Frame(master)
        self.plot_frame.pack(fill=tk.BOTH, expand=True)

        # Matplotlib plot setup
        self.fig, self.ax = plt.subplots()
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)

        # Information Button setup
        self.info_button = ttk.Button(self.plot_frame, text="Info", command=self.show_info)
        self.info_button.pack(side=tk.RIGHT, padx=5)

        # Status Label
        self.status_label = tk.Label(master, text="Idle", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status_label.pack(side=tk.BOTTOM, fill=tk.X)

        self.stream = None  # Audio stream object
        self.audio_thread = None  # Thread for audio analysis

    def show_info(self):
        info_window = tk.Toplevel(self.master)
        info_window.title("About Ethereal Spectrum Analyzer")
        info_text = tk.Text(info_window, wrap=tk.WORD)
        info_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
        info_text.insert(tk.END, """
Ethereal Spectrum Analyzer is a real-time audio analysis tool that visualizes the frequency spectrum of sound.

Key Features:
- Real-time Spectrum Display: Shows the frequency components of the incoming audio.
- Adjustable Parameters: Sample rate and FFT size can be configured (currently fixed in code).
- User-Friendly Interface: Simple start and stop controls.
- Informational Details: Provides insights into audio characteristics.

How to Use:
1. Click 'Start' to begin audio analysis. Ensure your microphone is active.
2. Observe the spectrum display for real-time frequency information.
3. Click 'Stop' to halt the analysis.

Technical Details:
The program uses the sounddevice library for audio input, numpy for FFT calculations, and matplotlib for visualization.

Potential Applications:
- Audio engineering and music production.
- Real-time audio monitoring and analysis.
- Educational purposes for understanding frequency spectra.

Note: This is a basic version and can be extended with more features like frequency range selection, peak hold, etc.
""")
        info_text.config(state=tk.DISABLED)  # Make it read-only

    def start_analysis(self):
        self.running = True
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        self.status_label.config(text="Analyzing...")
        self.audio_thread = threading.Thread(target=self.audio_callback)
        self.audio_thread.daemon = True  # Daemonize the thread
        self.audio_thread.start()

    def stop_analysis(self):
        self.running = False
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)
        self.status_label.config(text="Stopping...")
        time.sleep(0.1)  # Give the thread some time to finish
        self.status_label.config(text="Idle")


    def audio_callback(self):
        try:
            with sd.InputStream(samplerate=self.sample_rate, channels=1, callback=self.process_audio):
                while self.running:
                    time.sleep(0.1) # Check status periodically instead of busy-waiting
        except Exception as e:
            print(f"Error during audio capture: {e}")
            self.status_label.config(text=f"Error: {e}")
            self.stop_analysis()

    def process_audio(self, indata, frames, time, status):
        if status:
            print(status)
        if self.running:
            # Apply a window function to reduce spectral leakage
            window = np.hamming(self.fft_size)
            data = indata[:, 0] * window

            # Perform FFT
            fft = np.fft.fft(data, n=self.fft_size)
            fft = np.abs(fft[:self.fft_size // 2]) / self.fft_size #Normalize Amplitude
            # Calculate frequency axis
            freq = np.fft.fftfreq(self.fft_size, d=1/self.sample_rate)[:self.fft_size // 2]
            self.update_plot(freq, fft)


    def update_plot(self, freq, magnitude):
        self.ax.clear()
        self.ax.plot(freq, magnitude)
        self.ax.set_xlabel("Frequency (Hz)")
        self.ax.set_ylabel("Magnitude")
        self.ax.set_title("Real-time Spectrum")
        self.ax.set_xlim(0, self.sample_rate / 2)  # Display up to Nyquist frequency
        self.ax.grid(True)

        self.canvas.draw()


root = tk.Tk()
app = SpectrumAnalyzerApp(root)
root.mainloop()
👁️ Viewed: 7

Comments