repos/TB065

Commits Files Refs
commit 47734d34a7235f814e68f9286fc0d5e0bdcd6d80
parent fc29ea9ad10919f7438381f035cc27ad91f2b779
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Wed, 26 Nov 2025 17:43:56 -0300

updated `tp/*`

Diffstat:
Atp/img/diagrama_de_bloques-decimador.png | 0
Rtp/logofiuba.png -> tp/img/logofiuba.png | 0
Mtp/main.py | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtp/main.tex | 5++---
Atp/plot/polos_y_ceros_pasa-bajos_fir.png | 0
Atp/plot/polos_y_ceros_pasa-bajos_fir_cero_marcado.png | 0
Atp/plot/respuesta_al_impulso_filtro_fir.png | 0
Atp/plot/respuesta_en_frecuencia_pasa-bajos_fir.png | 0
Mtp/utils.py | 118++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
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)