Espectro Paramétrico de Audio - Colab PDF

Document Details

ChivalrousPlutonium8456

Uploaded by ChivalrousPlutonium8456

Universidad Politécnica de Madrid

Tags

audio processing stereo audio signal processing sound engineering

Summary

This document is a Jupyter Notebook, analyzing stereo audio. It calculates parameters like IID (Inter-channel Intensity Difference), IPD (Inter-channel Phase Difference), IC (Inter-channel Coherence), and OPD (Overall Phase Difference) for stereo signals. The code uses libraries like pydub, numpy, scipy, and soundfile for efficient sound processing and visualization.

Full Transcript

16/12/24 12:22 estereo paramétrico - Colab !pip install pydub Collecting pydub Downloading pydub-0.25.1-py2.py3-none-any.whl.metadata (1.4 kB) Downloading pydub-0.25.1-py2.py3-none-any.whl (32 kB)...

16/12/24 12:22 estereo paramétrico - Colab !pip install pydub Collecting pydub Downloading pydub-0.25.1-py2.py3-none-any.whl.metadata (1.4 kB) Downloading pydub-0.25.1-py2.py3-none-any.whl (32 kB) Installing collected packages: pydub Successfully installed pydub-0.25.1 import numpy as np from scipy.signal import stft, istft import matplotlib.pyplot as plt import soundfile as sf from pydub import AudioSegment from IPython.display import Audio from scipy.io.wavfile import write from scipy.signal import lfilter def calculate_stereo_parameters(left_channel, right_channel, fs, n_fft=1024, hop_length=512): # Cálculo de la STFT para cada canal f, t, L = stft(left_channel, fs=fs, nperseg=n_fft, noverlap=hop_length) _, _, R = stft(right_channel, fs=fs, nperseg=n_fft, noverlap=hop_length) # Calcular el canal mono como la media de los canales izquierdo y derecho mono_signal = (left_channel + right_channel) / 2 _, _, M = stft(mono_signal, fs=fs, nperseg=n_fft, noverlap=hop_length) # Magnitud y fase L_mag = np.abs(L) R_mag = np.abs(R) # IID (Intensidad entre canales) IID = 10 * np.log10(np.sum(L_mag**2, axis=0) / np.sum(R_mag**2, axis=0)) # IPD (Fase entre canales) IPD = np.angle(np.sum(L * np.conj(R), axis=0)) # IC (Coherencia entre canales) numerator = np.abs(np.sum(L * np.conj(R), axis=0)) denominator = np.sqrt(np.sum(L_mag**2, axis=0) * np.sum(R_mag**2, axis=0)) IC = numerator / denominator # OPD (Diferencia de fase global) OPD = np.angle(np.sum(L * np.conj(M), axis=0)) return IID, IPD, IC, OPD, t import numpy as np from scipy.signal import stft, istft, lfilter def all_pass_filter(signal, delay_samples=1, coefficient=0.7): """ Aplica un filtro paso-todo a la señal. Parámetros: signal: np.ndarray Señal de entrada. delay_samples: int Número de muestras de retraso. coefficient: float Coeficiente del filtro (entre -1 y 1, excluyendo 0). Retorna: signal_ap: np.ndarray Señal filtrada. """ # Coeficientes del filtro paso-todo a = np.zeros(delay_samples + 1) https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 1/8 16/12/24 12:22 estereo paramétrico - Colab b = np.zeros(delay_samples + 1) a = 1 a[-1] = -coefficient b = coefficient b[-1] = 1 # Aplicar el filtro signal_ap = lfilter(b, a, signal) return signal_ap def reconstruct_stereo(mono_signal, IID, IPD, OPD, ICC, fs, n_fft=1024, hop_length=512): # Generar la señal decorrelacionada decorrelated_signal = all_pass_filter(mono_signal) # Calcular la STFT de m[n] y d[n] f, t, M = stft(mono_signal, fs=fs, nperseg=n_fft, noverlap=n_fft - hop_length) _, _, D = stft(decorrelated_signal, fs=fs, nperseg=n_fft, noverlap=n_fft - hop_length) # Separar magnitud y fase M_mag = np.abs(M) M_phase = np.angle(M) D_mag = np.abs(D) D_phase = np.angle(D) # Asegurar que los parámetros tengan dimensiones compatibles num_bins, num_frames = M_mag.shape # Expandir los parámetros si es necesario if IID.ndim == 1: IID = np.tile(IID, (num_bins, 1)) if IPD.ndim == 1: IPD = np.tile(IPD, (num_bins, 1)) if OPD.ndim == 1: OPD = np.tile(OPD, (num_bins, 1)) if ICC.ndim == 1: ICC = np.tile(ICC, (num_bins, 1)) # Calcular las ganancias de intensidad A_L = np.sqrt(10**(IID / 20)) A_R = np.sqrt(10**(-IID / 20)) # Calcular las ganancias de la señal mono y decorrelacionada G_m = np.sqrt((1 + ICC) / 2) G_s = np.sqrt((1 - ICC) / 2) # Fases phi_L = M_phase + OPD phi_R = M_phase + OPD + IPD # Reconstrucción L_recon = A_L * (G_m * M_mag * np.exp(1j * phi_L) + G_s * D_mag * np.exp(1j * D_phase)) R_recon = A_R * (G_m * M_mag * np.exp(1j * phi_R) - G_s * D_mag * np.exp(1j * D_phase)) # Convertir de nuevo al dominio temporal _, left_recon = istft(L_recon, fs=fs, nperseg=n_fft, noverlap=n_fft - hop_length) _, right_recon = istft(R_recon, fs=fs, nperseg=n_fft, noverlap=n_fft - hop_length) return left_recon, right_recon def draw_params(time_bins, IID, IPD, IC, OPD): plt.figure(figsize=(15, 10)) plt.subplot(4, 1, 1) plt.plot(time_bins, IID) plt.title('IID (Diferencias de Intensidad)') plt.xlabel('Tiempo (s)') plt.ylabel('IID (dB)') https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 2/8 16/12/24 12:22 estereo paramétrico - Colab plt.subplot(4, 1, 2) plt.plot(time_bins, IPD) plt.title('IPD (Diferencias de Fase)') plt.xlabel('Tiempo (s)') plt.ylabel('IPD (radianes)') plt.subplot(4, 1, 3) plt.plot(time_bins, IC) plt.title('IC (Coherencia entre canales)') plt.xlabel('Tiempo (s)') plt.ylabel('IC') plt.subplot(4, 1, 4) plt.plot(time_bins, OPD) plt.title('OPD (Diferencias de Fase Global)') plt.xlabel('Tiempo (s)') plt.ylabel('OPD (radianes)') plt.tight_layout() plt.show() signal, fs = sf.read("sirena.mp3") # Si hay dos canales, separar izquierdo y derecho left_channel = signal[:, 0] # Canal izquierdo right_channel = signal[:, 1] # Canal derecho # Convertir audio estéreo a mono mono_signal = np.mean(signal, axis=1) # Promedia los dos canales left_channel_stereo = np.vstack((left_channel, np.zeros_like(left_channel))).T sf.write('left_channel.wav', left_channel_stereo, fs) # Original - Canal derecho con datos, canal izquierdo en silencio right_channel_stereo = np.vstack((np.zeros_like(right_channel), right_channel)).T sf.write('right_channel.wav', right_channel_stereo, fs) # Señal mono (ambos canales iguales para mantener el audio perceptible) mono_signal_stereo = np.vstack((mono_signal, mono_signal)).T sf.write('mono_signal.wav', mono_signal_stereo, fs) # Calcular parámetros estéreo IID, IPD, IC, OPD, time_bins = calculate_stereo_parameters(left_channel, right_channel, fs) # Reconstrucción estéreo left_recon, right_recon = reconstruct_stereo(mono_signal, IID, IPD, OPD, IC, fs) # Pintar draw_params(time_bins, IID, IPD, IC, OPD) # Reconstrucción - Canal izquierdo con datos, canal derecho en silencio reconstructed_stereo = np.vstack((left_recon, right_recon)).T sf.write('stereo_reconstructed_siren.wav', reconstructed_stereo, fs) https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 3/8 16/12/24 12:22 estereo paramétrico - Colab :15: RuntimeWarning: divide by zero encountered in divide IID = 10 * np.log10(np.sum(L_mag**2, axis=0) / np.sum(R_mag**2, axis=0)) :15: RuntimeWarning: invalid value encountered in divide IID = 10 * np.log10(np.sum(L_mag**2, axis=0) / np.sum(R_mag**2, axis=0)) :23: RuntimeWarning: invalid value encountered in divide IC = numerator / denominator fs = 44100 # Frecuencia de muestreo t = np.linspace(0, 5, fs * 5) # Duración de 5 segundos # Generar señal de sirena (modulación de amplitud) siren_freq = 2 # Frecuencia de modulación (oscilación) carrier_freq = 440 # Frecuencia base de la sirena siren = np.sin(2 * np.pi * siren_freq * t) * np.sin(2 * np.pi * carrier_freq * t) # Simular movimiento estéreo # Canal izquierdo (amplio al principio) left_channel = siren * (1 - np.clip(t / max(t), 0, 1)) # Canal derecho (amplio al final) right_channel = siren * np.clip(t / max(t), 0, 1) # Combinar en un archivo estéreo stereo_siren = np.vstack((left_channel, right_channel)).T write('siren_simulation.wav', fs, (stereo_siren * 32767).astype(np.int16)) # Calcular parámetros estéreo IID, IPD, IC, OPD, time_bins = calculate_stereo_parameters(left_channel, right_channel, fs) # Crear señal mono mono_signal = (left_channel + right_channel) / 2 # Reconstrucción estéreo left_recon, right_recon = reconstruct_stereo(mono_signal, IID, IPD, OPD, IC, fs) # Pintar draw_params(time_bins, IID, IPD, IC, OPD) https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 4/8 16/12/24 12:22 estereo paramétrico - Colab # Reconstrucción - Canal izquierdo con datos, canal derecho en silencio reconstructed_stereo = np.vstack((left_recon, right_recon)).T sf.write('stereo_reconstructed_siren_simulation.wav', reconstructed_stereo, fs) fs = 44100 # Frecuencia de muestreo duration = 5 # Duración en segundos t = np.linspace(0, duration, int(fs * duration), endpoint=False) # Frecuencia de la señal frequency = 440 # Frecuencia del tono puro en Hz # Generar señal base mono_signal = np.sin(2 * np.pi * frequency * t) # Crear diferencia de fase entre canales phase_shift = np.linspace(0, np.pi, len(t)) # Fase que cambia gradualmente left_channel = mono_signal # Canal izquierdo sin cambios right_channel = np.sin(2 * np.pi * frequency * t + phase_shift) # Canal derecho con desfase # Combinar canales en estéreo stereo_signal = np.vstack((left_channel, right_channel)).T # Guardar y reproducir write('phase_difference.wav', fs, (stereo_signal * 32767).astype(np.int16)) # Calcular parámetros estéreo IID, IPD, IC, OPD, time_bins = calculate_stereo_parameters(left_channel, right_channel, fs) # Crear señal mono mono_signal = (left_channel + right_channel) / 2 # Reconstrucción estéreo left_recon, right_recon = reconstruct_stereo(mono_signal, IID, IPD, OPD, IC, fs) # Pintar draw_params(time_bins, IID, IPD, IC, OPD) https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 5/8 16/12/24 12:22 estereo paramétrico - Colab # Reconstrucción - Canal izquierdo con datos, canal derecho en silencio reconstructed_stereo = np.vstack((left_recon, right_recon)).T sf.write('stereo_reconstructed_phase.wav', reconstructed_stereo, fs) def add_noise(signal, snr_db): """ Agrega ruido blanco a una señal dada un SNR específico. Parámetros: signal: np.ndarray Señal original. snr_db: float Relación señal-ruido deseada en decibeles. Retorna: signal_noisy: np.ndarray Señal con ruido añadido. """ # Potencia de la señal signal_power = np.mean(signal**2) # Convertir SNR de dB a una proporción lineal snr_linear = 10**(snr_db / 10) # Potencia del ruido requerida noise_power = signal_power / snr_linear # Generar ruido blanco gaussiano noise = np.sqrt(noise_power) * np.random.randn(len(signal)) # Señal con ruido signal_noisy = signal + noise return signal_noisy # Parámetros https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 6/8 16/12/24 12:22 estereo paramétrico - Colab # Parámetros snr_db = 10 # Relación señal-ruido deseada en decibeles # Agregar ruido a los canales left_noisy = add_noise(left_channel, snr_db) right_noisy = add_noise(right_channel, snr_db) # Visualización plt.figure(figsize=(10, 5)) plt.plot(t[:500], left_channel[:500], label="Original (Izquierda)") plt.plot(t[:500], left_noisy[:500], label="Con ruido (Izquierda)") plt.legend() plt.title("Efecto del ruido en la señal") plt.show() # Calcular parámetros con ruido IID_noisy, IPD_noisy, IC_noisy, OPD_noisy, _ = calculate_stereo_parameters(left_noisy, right_noisy, fs) draw_params(time_bins, IID_noisy, IPD_noisy, IC_noisy, OPD_noisy) https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 7/8 16/12/24 12:22 estereo paramétrico - Colab Tarea! Haz un estéreo con una señal a tu elección. Puede ser estática o dinámica, pero tiene que notarse que el audio no está en el centro. Puedes utilizar la web de freesound para inspirarte. https://colab.research.google.com/drive/1s5Tofu2b8uRDtwNmtFKJCFYtTp1FLxXu#scrollTo=ibvdWtFcRqP4&printMode=true 8/8

Use Quizgecko on...
Browser
Browser