commit 47734d34a7235f814e68f9286fc0d5e0bdcd6d80
parent fc29ea9ad10919f7438381f035cc27ad91f2b779
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date: Wed, 26 Nov 2025 17:43:56 -0300
updated `tp/*`
Diffstat:
9 files changed, 222 insertions(+), 22 deletions(-)
diff --git a/tp/img/diagrama_de_bloques-decimador.png b/tp/img/diagrama_de_bloques-decimador.png
Binary files differ.
diff --git a/tp/logofiuba.png b/tp/img/logofiuba.png
Binary files differ.
diff --git a/tp/main.py b/tp/main.py
@@ -1,5 +1,6 @@
import numpy as np
from scipy.io import wavfile
+from scipy.signal import firwin, freqz, tf2zpk
from utils import *
## Datos
@@ -64,7 +65,6 @@ def time_domain_cancion1():
"cancion1_filter2_output_compare_0_248_a_0_256",
t=0.248, dt=0.008)
-## 'cancion2'
def time_domain_cancion2():
### grafico completo
time_plot(file2_fs, file2_data, "cancion2", t=6)
@@ -281,27 +281,112 @@ def a4_violin_fseries():
save_plot(fig, "a4_violin_fseries_comparison")
+############################# Tercera parte ###################################
+
+cutoff = 2650 # frecuencia de corte
+M = 700 # orden FIR (número de coeficientes)
+fs = 44100
+
+def filtro_fir():
+ # Diseño FIR pasabajos con ventana
+ b = firwin(M, cutoff, fs=fs, window='hamming')
+
+ w, H = freqz(b, worN=2048, fs=fs)
+ fase = np.unwrap(np.angle(H))*180/np.pi
+
+ fig, ax1, ax2 = freq_response_plot(w, H, fase, show=False)
+ save_plot(fig, "respuesta_en_frecuencia_pasa-bajos_fir")
+
+# `a` son los coeficientes de la respuesta al impulso (coinciden con los
+# coeficientes de respuesta en frecuencia)
+def filtro_fir_polos_y_ceros(a):
+ zeros, poles, gain = tf2zpk(a, [1])
+
+ # Crear figura
+ fig, axis = plt.subplots(figsize=(8, 4))
+
+ axis.scatter(np.real(zeros), np.imag(zeros),
+ s=25, facecolors='none', edgecolors='tab:blue', zorder=10,
+ label='Ceros', linewidth=1.25)
+
+ axis.scatter(np.real(poles), np.imag(poles),
+ s=25, marker='x', color='tab:red',
+ label='Polos')
+
+ axis.set_xlabel("Real", color="black")
+ axis.set_ylabel("Imaginario", color="black")
+
+ # Unidad círculo para referencia
+ # theta = np.linspace(0, 2*np.pi, 100)
+ # plt.plot(np.cos(theta), np.sin(theta)) # círculo unitario
+
+ # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ axis.xaxis.set_minor_locator(AutoMinorLocator(2))
+
+ plt.grid(True)
+ plt.axis('equal')
+ axis.legend()
+ save_plot(fig, "polos_y_ceros_pasa-bajos_fir")
+
+
+def filtro_fir_deducido():
+ # respuesta ideal pasabajos: sinc centrada en M/2
+ n = np.arange(M + 1)
+ wc = 2*np.pi*cutoff / fs
+
+ # h_ideal = sinc(wc*n)/(pi n); wc = 2pi*fc/fs
+ # se normaliza la ganancia a 1 multiplicando por 2.0*(fc/fs)
+ h_ideal = np.sinc(2.0 * (cutoff/fs) * (n - M/2))
+
+ # ventana de Hamming
+ v = 0.54 - 0.46 * np.cos(2*np.pi*n/M)
+
+ # respuesta del filtro FIR (version acotada de la sinc)
+ h = h_ideal * v
+
+ # se normaliza para tener ganancia unitaria para frecuancias <= fc
+ h = h / np.sum(h)
+
+ fig, ax = dtime_plot(M, h, "respuesta_al_impulso_filtro_fir",
+ f'Respuesta al impulso filtro FIR grado {M}')
+
+ # respuesta en frecuencia del filtro
+ w, H = freqz(h, worN=2048, fs=fs)
+ fase = np.unwrap(np.angle(H)) * 180 / np.pi
+
+ fig, ax1, ax2 = freq_response_plot(w, H, fase, show=False, fc=5e3)
+ save_plot(fig, "respuesta_en_frecuencia_pasa-bajos_fir")
+
+ # polos y ceros
+ filtro_fir_polos_y_ceros(h)
############################# Llamados a funciones ############################
-# time_domain
-time_domain_cancion1()
-time_domain_cancion2()
-time_domain_music_instruments()
+def primer_y_segunda_parte():
+ # time_domain
+ time_domain_cancion1()
+ time_domain_cancion2()
+ time_domain_music_instruments()
+
+ # freq_domain
+ freq_domain_cancion1()
+ freq_domain_cancion2()
+ freq_domain_spectograms()
-# freq_domain
-freq_domain_cancion1()
-freq_domain_cancion2()
-freq_domain_spectograms()
+ freq_plot(48000, filter1_h, "filter1_h_fft", f_max=2000)
+ freq_plot(48000, filter2_h, "filter2_h_fft", f_max=8000)
-freq_plot(48000, filter1_h, "filter1_h_fft", f_max=2000)
-freq_plot(48000, filter2_h, "filter2_h_fft", f_max=8000)
+ a4_flauta_fseries()
+ a4_clarinete_fseries()
+ a4_violin_fseries()
-a4_flauta_fseries()
-a4_clarinete_fseries()
-a4_violin_fseries()
+ # obs: para realizar el filtrado se toma toda la señal no solo un periodo
+ a4_flauta_cutoff()
+ a4_clarinete_cutoff()
+ a4_violin_cutoff()
-# obs: para realizar el filtrado se toma toda la señal no solo un periodo
-a4_flauta_cutoff()
-a4_clarinete_cutoff()
-a4_violin_cutoff()
+# filtro_fir()
+filtro_fir_deducido()
diff --git a/tp/main.tex b/tp/main.tex
@@ -81,7 +81,7 @@ fontupper={\ttfamily\mystrut}}
\begin{titlepage}
\vspace*{-2.5cm}
{\centering
- \includegraphics[width=1.00\textwidth]{logofiuba.png}\\[2.25 cm]}
+ \includegraphics[width=1.00\textwidth]{img/logofiuba.png}\\[2.25 cm]}
\centering
\textsc{\Large TB065}\\[0.2 cm]
\textsc{\large Señales y Sistemas}\\[4 cm]
@@ -926,4 +926,4 @@ Cada efecto puede interpretarse como un sistema que transforma una señal de ent
\fi
-\end{document}
-\ No newline at end of file
+\end{document}
diff --git a/tp/plot/polos_y_ceros_pasa-bajos_fir.png b/tp/plot/polos_y_ceros_pasa-bajos_fir.png
Binary files differ.
diff --git a/tp/plot/polos_y_ceros_pasa-bajos_fir_cero_marcado.png b/tp/plot/polos_y_ceros_pasa-bajos_fir_cero_marcado.png
Binary files differ.
diff --git a/tp/plot/respuesta_al_impulso_filtro_fir.png b/tp/plot/respuesta_al_impulso_filtro_fir.png
Binary files differ.
diff --git a/tp/plot/respuesta_en_frecuencia_pasa-bajos_fir.png b/tp/plot/respuesta_en_frecuencia_pasa-bajos_fir.png
Binary files differ.
diff --git a/tp/utils.py b/tp/utils.py
@@ -139,7 +139,7 @@ def save_plot(fig, name):
# crea carpeta para plots
os.makedirs(plot_dir_name, exist_ok=True)
- fig.savefig(save_name, dpi=150, bbox_inches="tight")
+ fig.savefig(save_name, dpi=250, bbox_inches="tight")
plt.close(fig) # liberar memoria
def save_to_wav(fs, data, save_name):
@@ -311,3 +311,119 @@ def spectogram_plot(fs, data, save_name="", t=0, dt=0, N=1024, overlp=16, win='h
save_plot(fig, save_name)
return fig, axis
+
+def bode_plot(w, H, show=True):
+ figure, axis = plt.subplots(figsize=(8, 4))
+
+ axis.plot(w, 20*np.log10(np.abs(H)))
+
+ axis.set(xlabel='Frecuencia [Hz]', ylabel='Magnitud [dB]')
+ axis.minorticks_on()
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ plt.tight_layout()
+
+ axis.set_xlim(0.0, 20e3)
+
+ axis.legend(loc='upper left')
+
+ if show:
+ plt.show()
+
+ return figure, axis
+
+def freq_response_plot(w, H, phase, show=True, fc=20e3):
+ fig, ax1 = plt.subplots(figsize=(8, 4))
+
+ H_db = 20*np.log10(np.abs(H))
+
+ line1, = ax1.plot(w, H_db)
+ ax1.set(xlabel='Frecuencia [Hz]', ylabel='Magnitud [dB]')
+ ax1.minorticks_on()
+ ax1.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ ax1.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ ax1.set_xlim(0.0, fc)
+
+ ax2 = ax1.twinx()
+ line2, = ax2.plot(w, phase, color="tab:red")
+ ax2.set_ylabel("Fase [grados]", color="black")
+ ax2.tick_params(axis='y', labelcolor="black")
+
+ # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ ax1.yaxis.set_minor_locator(AutoMinorLocator(2))
+
+ # Add ONE point
+
+ # Find index closest to -3 dB
+ idx = np.argmin(np.abs(H_db + 3)) # H_db = -3 => H_db +3 = 0
+ w_3db = w[idx]
+ H_3db = H_db[idx]
+ line3 = ax1.scatter(w_3db, H_3db, color='tab:green', s=50, zorder=10)
+
+ # nyquist
+ w_nyquist = 2756.25
+ idx = np.argmin(np.abs(w - w_nyquist)) # H_db = -3 => H_db +3 = 0
+ H_nyquist = H_db[idx]
+ line4 = ax1.scatter(w_nyquist, H_nyquist, color='tab:orange', s=50, zorder=10)
+
+ ax1.legend([line1, line2, line3, line4],
+ ["Magnitud [dB]",
+ "Fase [grados]",
+ r'-3dB $\approx$ %0.0f Hz'%w_3db,
+ r'Nyquist $\approx$ %0.0f Hz'%w_nyquist],
+ loc='upper right')
+
+ plt.tight_layout()
+
+ if show:
+ plt.show()
+
+ return fig, ax1, ax2
+
+def dtime_plot(N, f, save_name="", legend="", n=0, dn=0, a=0, da=0):
+ show = True if save_name == "" else False
+
+ n = np.arange(N + 1)
+
+ fig, axis = plt.subplots(figsize=(8,4))
+ axis.set(xlabel='Tiempo discreto', ylabel='Amplitud')
+
+ markerline, stemlines, baseline = axis.stem(
+ n, f,
+ markerfmt='o', # tipo de marcador en la cabeza
+ basefmt="k-",
+ )
+
+ markerline.set_markersize(2.0)
+ stemlines.set_linewidth(0.35)
+ baseline.set_linewidth(0.5)
+
+ stemlines.set_zorder(2)
+ markerline.set_zorder(3)
+ baseline.set_zorder(1)
+
+ axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
+ axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
+ axis.set_xlim(0, N+1)
+ axis.set_ylim(-0.03, 0.15)
+
+ # configuracion de ticks del eje x
+ axis.xaxis.set_major_locator(MaxNLocator(nbins=15))
+ axis.xaxis.set_minor_locator(AutoMinorLocator(2))
+
+ axis.yaxis.set_minor_locator(AutoMinorLocator(2))
+
+ # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+ # axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+ if legend != "":
+ axis.legend([markerline], [legend], loc='upper right')
+
+ if show == False:
+ save_plot(fig, save_name)
+ else:
+ plt.show()
+
+ return fig, axis
+
+# np.linspace(start, stop, num).astype(int)