"""Signal-to-Noise Ratio =============================== Imagine developing a song recognition application. The software's goal is to recognize a song even when it's played in a noisy environment, similar to Shazam. To achieve this, you want to enhance the audio quality by reducing the noise and evaluating the improvement using the Signal-to-Noise Ratio (SNR). In this example, we will demonstrate how to generate a clean signal, add varying levels of noise to simulate the noisy recording, use FFT for noise reduction, and then evaluate the quality of the reconstructed audio using SNR. """ # %% # Import necessary libraries import matplotlib.animation as animation import matplotlib.pyplot as plt import numpy as np import torch from torchmetrics.audio import SignalNoiseRatio # %% # Generate a clean signal (simulating a high-quality recording) def generate_clean_signal(length: int = 1000) -> tuple[np.ndarray, np.ndarray]: """Generate a clean signal (sine wave)""" t = np.linspace(0, 1, length) signal = np.sin(2 * np.pi * 10 * t) # 10 Hz sine wave, representing the clean recording return t, signal # %% # Add Gaussian noise to the signal to simulate the noisy environment def add_noise(signal: np.ndarray, noise_level: float = 0.5) -> np.ndarray: """Add Gaussian noise to the signal.""" noise = noise_level * np.random.randn(signal.shape[0]) return signal + noise # %% # Apply FFT to filter out the noise def fft_denoise(noisy_signal: np.ndarray, threshold: float) -> np.ndarray: """Denoise the signal using FFT.""" freq_domain = np.fft.fft(noisy_signal) # Filter frequencies using FFT magnitude = np.abs(freq_domain) filtered_freq_domain = freq_domain * (magnitude > threshold) return np.fft.ifft(filtered_freq_domain).real # Perform inverse FFT to reconstruct the signal # %% # Generate and plot clean, noisy, and denoised signals to visualize the reconstruction length = 1000 t, clean_signal = generate_clean_signal(length) noisy_signal = add_noise(clean_signal, noise_level=0.5) denoised_signal = fft_denoise(noisy_signal, threshold=10) plt.figure(figsize=(12, 4)) plt.plot(t, noisy_signal, label="Noisy environment", color="blue", alpha=0.7) plt.plot(t, denoised_signal, label="Denoised signal", color="green", alpha=0.7) plt.plot(t, clean_signal, label="Clean song", color="red", linewidth=3) plt.xlabel("Time") plt.ylabel("Amplitude") plt.title("Clean Song vs. Noisy Environment vs. Denoised Signal") plt.legend() plt.show() # %% # Convert the signals to PyTorch tensors and calculate the SNR clean_signal_tensor = torch.tensor(clean_signal).float() noisy_signal_tensor = torch.tensor(noisy_signal).float() denoised_signal_tensor = torch.tensor(denoised_signal).float() snr = SignalNoiseRatio() initial_snr = snr(preds=noisy_signal_tensor, target=clean_signal_tensor) reconstructed_snr = snr(preds=denoised_signal_tensor, target=clean_signal_tensor) print(f"Initial SNR: {initial_snr:.2f}") print(f"Reconstructed SNR: {reconstructed_snr:.2f}") # %% # To show the effect of different noise levels on the SNR, we create an animation that iterates over different noise levels and updates the plot accordingly: fig, ax = plt.subplots(figsize=(12, 4)) noise_levels = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] def update(num: int) -> tuple: """Update the plot for each frame.""" t, clean_signal = generate_clean_signal(length) noisy_signal = add_noise(clean_signal, noise_level=noise_levels[num]) denoised_signal = fft_denoise(noisy_signal, threshold=10) clean_signal_tensor = torch.tensor(clean_signal).float() noisy_signal_tensor = torch.tensor(noisy_signal).float() denoised_signal_tensor = torch.tensor(denoised_signal).float() initial_snr = snr(preds=noisy_signal_tensor, target=clean_signal_tensor) reconstructed_snr = snr(preds=denoised_signal_tensor, target=clean_signal_tensor) ax.clear() (noisy,) = plt.plot(t, noisy_signal, label="Noisy Environment", color="blue", alpha=0.7) (denoised,) = plt.plot(t, denoised_signal, label="Denoised Signal", color="green", alpha=0.7) (clean,) = plt.plot(t, clean_signal, label="Clean Song", color="red", linewidth=3) ax.set_xlabel("Time") ax.set_ylabel("Amplitude") ax.set_title( f"Initial SNR: {initial_snr:.2f} - Reconstructed SNR: {reconstructed_snr:.2f} - Noise level: {noise_levels[num]}" ) ax.legend(loc="upper right") ax.set_ylim(-3, 3) return noisy, denoised, clean ani = animation.FuncAnimation(fig, update, frames=len(noise_levels), interval=1000)