repos/TB065

Commits Files Refs
commit a9da0c956520e4ea19ed0948bbf3129e98ae0c4b
parent 6d356459171a6e195d97d1018dfdd899739e9813
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Mon, 15 Dec 2025 14:06:32 -0300

add `guias/*`

Diffstat:
Aguias/2_19.py | 45+++++++++++++++++++++++++++++++++++++++++++++
Aguias/7_12.py | 27+++++++++++++++++++++++++++
Aguias/7_17.py | 38++++++++++++++++++++++++++++++++++++++
Aguias/7_20.py | 34++++++++++++++++++++++++++++++++++
Aguias/7_22.py | 34++++++++++++++++++++++++++++++++++
Aguias/8_07.py | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aguias/8_10.py | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aguias/utils.py | 541+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 851 insertions(+), 0 deletions(-)
diff --git a/guias/2_19.py b/guias/2_19.py
@@ -0,0 +1,45 @@
+# 2_19.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+# `a` coeficientes de la entrada
+# `b` coeficientes de las salida
+def respuesta_impulso(a, b, N):
+    a = np.array(a)
+    b = np.array(b)
+
+    # delta para la entrada
+    x = np.zeros(N)
+    x[0] = 1
+
+    y = np.zeros(N)
+
+    for n in range(N):
+        # entrada
+        parte_x = 0
+        for i in range(len(a)):
+            # condicion de reposo inicial
+            if n - i >= 0:
+                parte_x += a[i] * x[n - i]
+
+        # salida
+        parte_y = 0
+        for j in range(1, len(b)):
+            # condicion de reposo inicial
+            if n - j >= 0:
+                parte_y += b[j] * y[n - j]
+
+        # normalizado por a0 (por si es distinto de 1)
+        y[n] = (parte_x - parte_y) / a[0]
+
+    return y
+
+# `y(n) - 0.25*y(n - 1) = x(n)` coeficientes: a = [1, -0.5] y b = [1]
+a = [1]
+b = [1, -0.25]
+N = 5
+
+h = respuesta_impulso(a, b, N)
+print(h) # [1.         0.25       0.0625     0.015625   0.00390625]
diff --git a/guias/7_12.py b/guias/7_12.py
@@ -0,0 +1,27 @@
+# 7_12.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+w = np.linspace(-np.pi, np.pi, 1000)
+h = 1 - np.exp(-1j * 5 * w)
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
+
+# magnitud
+ax1.plot(w, np.abs(h))
+ax1.set_title(r'Magnitud $|1 - e^{-j5\omega}|$')
+ax1.set_ylabel('Amplitud')
+ax1.set_xticks([-np.pi, -2*np.pi/5, 0, 2*np.pi/5, np.pi])
+ax1.set_xticklabels([r'$-\pi$', r'$-2\pi/5$', '0', r'$2\pi/5$', r'$\pi$'])
+ax1.grid(True)
+
+# fase
+ax2.plot(w, np.angle(h))
+ax2.set_title(r'Fase $\angle H(e^{j\omega})$')
+ax2.set_ylabel('Fase [rad]')
+ax2.set_xlabel('Frecuencia [rad/muestra]')
+ax2.grid(True)
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/7_17.py b/guias/7_17.py
@@ -0,0 +1,38 @@
+# 7_17.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import fft, signal
+
+T = 1/100 # seg
+sample_rate = 1/T
+sample_time = 1
+N = int(sample_rate*sample_time)
+
+for f1 in [30, 30.5]:
+    for Nf in [N, 10*N]:
+        for alpha in [0.5, 1, 5]:
+            f2 = f1 + alpha/(N*T)
+
+            # la frecuencia de muestreo de la señal es fija
+            t = np.linspace(0, sample_time, int(N))
+            y = np.cos(2*np.pi*f1*t) + np.cos(2*np.pi*f2*t)
+
+            print(f1, f2)
+
+            # calculo de DFT de Nf puntos
+            Xf = np.fft.fft(t, n=Nf)
+            freqs = np.fft.fftfreq(Nf, d=T)
+
+            # centrar la frecuencia 0
+            Xf = np.fft.fftshift(Xf)
+            freqs = np.fft.fftshift(freqs)
+
+            # grafico
+            plt.figure(figsize=(8, 4))
+            markerline, stemlines, baseline = plt.stem(freqs, np.abs(Xf))
+            plt.setp(baseline, color='black', linewidth=0)
+            plt.title(f"DFT con Nf={Nf}, f1={f1}, f2={f2:.2f}")
+            plt.grid(True, linestyle=':', alpha=0.7)
+            plt.grid(True, which='minor')
+            plt.show()
diff --git a/guias/7_20.py b/guias/7_20.py
@@ -0,0 +1,34 @@
+# 7_20.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+fs = 400
+T = 1/fs
+t = np.arange(0, 1, T)
+
+x_a = np.cos(2*np.pi*100*t)
+x_b = (1 + np.cos(2*np.pi*10*t)) * np.cos(2*np.pi*100*t)
+x_c = np.cos(2 * np.pi * 100 * t**2)
+
+sig = [x_a, x_b, x_c]
+
+plt.figure(figsize=(15, 5))
+
+for i, x in enumerate(sig):
+    plt.subplot(1, 3, i+1)
+
+    # `nperseg` tamaño de la ventana
+    # `noverlap` solapamiento entre ventanas
+    f, times, Sxx = signal.spectrogram(x, fs, window='hann',
+                                       nperseg=128, noverlap=32)
+
+    plt.pcolormesh(times, f, 10*np.log10(Sxx), cmap='inferno', shading='gouraud')
+
+    if i == 0:
+        plt.ylabel('Frecuencia [Hz]')
+    plt.xlabel('Tiempo [s]')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/7_22.py b/guias/7_22.py
@@ -0,0 +1,34 @@
+# 7_22.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+fs = 400
+T = 1/fs
+t = np.arange(0, 1, T)
+
+x_a = np.cos(2*np.pi*100*t)
+x_b = (1 + np.cos(2*np.pi*10*t)) * np.cos(2*np.pi*100*t)
+x_c = np.cos(2 * np.pi * 100 * t**2)
+
+sig = [x_a, x_b, x_c]
+
+plt.figure(figsize=(15, 5))
+
+for i, x in enumerate(sig):
+    plt.subplot(1, 3, i+1)
+
+    # nperseg: tamaño de la ventana
+    # noverlap: solapamiento entre ventanas
+    f, times, Sxx = signal.spectrogram(x, fs, window='hann',
+                                       nperseg=128, noverlap=32)
+
+    plt.pcolormesh(times, f, 10*np.log10(Sxx), cmap='inferno', shading='gouraud')
+
+    if i == 0:
+        plt.ylabel('Frecuencia [Hz]')
+    plt.xlabel('Tiempo [s]')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/8_07.py b/guias/8_07.py
@@ -0,0 +1,66 @@
+# 8_07.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from numpy import pi, sin
+
+tau = 0.1
+
+T = 2 * pi  # Periodo
+w0 = 2 * pi / T
+D = 0.5 # duty cycle
+
+k_max = 100
+ks = np.arange(k_max, dtype=int)
+a_k = np.arange(k_max, dtype=float)
+b_k = np.arange(k_max, dtype=float)
+
+for k in range(k_max):
+    if k == 0:
+        a_k[k] = (w0*D*T) / (2*pi)
+        b_k[k] = (1/((tau*2*pi/T)+1)) * (w0*D*T)/(2*pi)
+    else:
+        a_k[k] = sin(k*D*w0*T/2) / (k*pi)
+        b_k[k] = (1/((tau*2*pi/T)+1)) * (sin(k*D*w0*T/2)/(k*pi))
+
+# magnitud a_k
+plt.subplot(1, 2, 1)
+markerline, stemlines, _ = plt.stem(ks, np.abs(a_k), basefmt=" ")
+plt.setp(markerline, 'markerfacecolor', 'blue')
+plt.title('Espectro de Magnitud $|a_k|$')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Amplitud')
+plt.grid(True, linestyle='--')
+
+# fase a_k
+plt.subplot(1, 2, 2)
+plt.stem(ks, np.angle(a_k), basefmt=" ")
+plt.title('Espectro de Fase $\\angle a_k$ (rad)')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Fase')
+plt.grid(True, linestyle='--')
+
+plt.tight_layout()
+plt.show()
+
+plt.figure(figsize=(10, 10))
+
+# magnitud b_k
+plt.subplot(1, 2, 1)
+markerline, stemlines, _ = plt.stem(ks, np.abs(b_k), basefmt=" ")
+plt.setp(markerline, 'markerfacecolor', 'blue')
+plt.title('Espectro de Magnitud $|b_k|$')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Amplitud')
+plt.grid(True, linestyle='--')
+
+# fase b_k
+plt.subplot(1, 2, 2)
+plt.stem(ks, np.angle(b_k), basefmt=" ")
+plt.title('Espectro de Fase $\\angle b_k$ (rad)')
+plt.xlabel('Armónico (k)')
+plt.ylabel('Fase')
+plt.grid(True, linestyle='--')
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/8_10.py b/guias/8_10.py
@@ -0,0 +1,66 @@
+# 8_10.py
+
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import signal
+
+# H(z) = (z) / (z - 0.5)  => H(z) = 1 / (1 - 0.5z^-1)
+zeros = [0]
+poles = [0.5]
+k = 1
+
+# zeros = [0, 1]
+# poles = [0.5, 1.5]
+# k = 1
+
+system_zpk = signal.dlti(zeros, poles, k, dt=1.0)
+
+n_points = 100
+n_1, y_1 = signal.dimpulse(system_zpk, n=n_points)
+n_2, y_2 = signal.dstep(system_zpk, n=n_points)
+
+# la salida de dimpulse es una lista, tomamos el primer elemento
+h_n = y_1[0].flatten()
+y_n = y_2[0].flatten()
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))
+
+# respuesta al impulso
+markerline, stemlines, baseline = ax1.stem(n_1, h_n, basefmt=" ")
+plt.setp(baseline, color='black', linewidth=0)
+ax1.set_title('Respuesta al Impulso $h[n]$')
+ax1.set_ylabel('Amplitud')
+ax1.grid(True, linestyle='--', alpha=0.7)
+
+# respuesta al escalon
+markerline, stemlines, baseline = ax2.stem(n_2, y_n, basefmt=" ")
+plt.setp(baseline, color='black', linewidth=0)
+ax2.set_title('Respuesta al Escalón $y[n]$')
+ax2.set_xlabel('Muestra ($n$)')
+ax2.set_ylabel('Amplitud')
+ax2.grid(True, linestyle='--', alpha=0.7)
+
+plt.setp(baseline, color='black', linewidth=1)
+plt.tight_layout()
+plt.show()
+
+# transferencia como diagrama de bode (modulo/fase)
+w, h = signal.dfreqresp(system_zpk)
+
+fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10))
+
+# magnitud
+ax1.plot(w, 20 * np.log10(abs(h)))
+ax1.set_title('Respuesta en Frecuencia (Magnitud)')
+ax1.set_ylabel('Amplitud [dB]')
+ax1.grid(True)
+
+# fase
+ax2.plot(w, np.angle(h))
+ax2.set_title('Respuesta en Frecuencia (Fase)')
+ax2.set_ylabel('Fase [rad]')
+ax2.set_xlabel('Frecuencia [rad/muestra]')
+ax2.grid(True)
+
+plt.tight_layout()
+plt.show()
diff --git a/guias/utils.py b/guias/utils.py
@@ -0,0 +1,541 @@
+import matplotlib.pyplot as plt
+from matplotlib.ticker import AutoMinorLocator
+from matplotlib.ticker import MultipleLocator
+from matplotlib.ticker import MaxNLocator
+from matplotlib.ticker import FuncFormatter
+from cycler import cycler
+
+import matplotlib
+import numpy as np
+import os
+
+from scipy.io import wavfile
+from scipy.fft import fft, ifft, fftfreq
+from scipy.signal import spectrogram
+
+plot_dir = "."
+
+matplotlib.rcParams['font.family'] = 'Inter'
+matplotlib.rcParams['font.size'] = 12
+matplotlib.rcParams['axes.prop_cycle'] = cycler(
+        color=['#1f77b4', '#ff0000', '#ff5f1f', 'green'])
+matplotlib.use("TkAgg")
+
+def ticks_label_format(x, pos):
+    # 3 decimales, se eliminan los ceros y puntos
+    return f"{x:.3f}".rstrip("0").rstrip(".")
+
+def time_graph_multiple_data(x, y_arr, y_lab, t=0, dt=0, a=0, da=0, show=True):
+    figure, axis = plt.subplots(figsize=(8, 4))
+
+    for i, y in enumerate(y_arr):
+        axis.plot(x, y, label=y_lab[i], alpha=0.75)
+
+    axis.set(xlabel='Tiempo [s]', ylabel='Amplitud normalizada')
+
+    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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+
+    # max 3 decimales
+    axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+
+    axis.set_xlim([t, t+dt if dt > 0 else x[-1]])
+    axis.set_ylim(-1.1, 1.1)
+
+    # resaltado de parte de la señal (solo si a != 0)
+    axis.axvspan(a, a+da, color='skyblue',
+                 alpha=0 if a == 0 else 0.50,
+                 label=f"Un periodo T={da}s" if da != 0 else "")
+    axis.legend(loc='upper left')
+
+    if show:
+        plt.show()
+
+    return figure, axis
+
+# todos deben la misma cantidad de elementos que el primero
+def time_plot_multiple(fs, data_arr, leg_arr, save_name="", t=0, dt=0, a=0, da=0, show=False):
+    x = np.arange(len(data_arr[0])) / fs
+    fig, ax = time_graph_multiple_data(x, data_arr, leg_arr, t, dt, a=a, da=da, show=show)
+
+    if show == False:
+        save_plot(fig, save_name)
+
+    return fig, ax
+
+def time_graph_data(x, y, t=0, dt=0, a=0, da=0, ylim=[], show=True):
+    figure, axis = plt.subplots(figsize=(8, 4))
+
+    axis.plot(x, y, label='Señal de audio')
+    axis.set(xlabel='Tiempo [s]', ylabel='Amplitud normalizada')
+
+    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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+
+    # max 3 decimales
+    axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+
+    axis.set_xlim(t, t+dt if dt > 0 else x[-1])
+
+    if len(ylim) != 0:
+        axis.set_ylim(ylim[0], ylim[1])
+    else:
+        axis.set_ylim(-1.1, 1.1)
+
+    # resaltado de parte de la señal (solo si a != 0)
+    axis.axvspan(a, a+da, color='skyblue',
+                 alpha=0 if a == 0 else 0.50,
+                 label=f"Un periodo T={da}s" if da != 0 else "")
+    axis.legend(loc='upper left')
+
+    if show:
+        plt.show()
+
+    return figure, axis
+
+def normalize(data):
+    data = data.astype(np.float32)
+    data /= np.max(np.abs(data))
+    return data
+
+def time_plot(fs, data, save_name="", t=0, dt=0, a=0, da=0):
+    show = True if save_name == "" else False
+
+    # normaliza la amplitud dividiendo por el valor maximo del tipo de dato
+    data = normalize(data)
+
+    x = np.arange(len(data)) / fs
+    fig, ax = time_graph_data(x, data, t, dt, a, da, show)
+
+    if show == False:
+        save_plot(fig, save_name)
+
+    return fig, ax
+
+def save_plot(fig, name, save_dir="", overwrite=True):
+    base_name = os.path.basename(name)
+    file_name, ext = os.path.splitext(base_name.replace('.', '_'))
+
+    if save_dir == "":
+        save_dir = plot_dir
+
+    if save_dir != "" and not save_dir.endswith('/'):
+        save_dir += "/"
+
+    file_path_no_ext = f'{save_dir}{file_name}'
+    save_name = f'{file_path_no_ext}.png'
+
+    if overwrite == False:
+        if os.path.exists(save_name):
+            i = 1
+            while True:
+                new_save_name = f'{file_path_no_ext}_{i:02d}'
+                if not os.path.exists(f'{new_save_name}.png'):
+                    save_name = f'{new_save_name}.png'
+                    break
+                i += 1
+
+    print(f'[LOG] Guardando figura `{save_name}`')
+
+     # crea carpeta para plots
+    os.makedirs(plot_dir, exist_ok=True)
+    fig.savefig(save_name, dpi=100, bbox_inches="tight")
+    plt.close(fig) # liberar memoria
+
+def save_to_wav(fs, data, save_name):
+    # normalizar para prevenir clipping
+    data = data / np.max(np.abs(data))
+
+    # convertir a 16-bit PCM para WAV
+    data_as_int16 = np.int16(data * 32767)
+
+    # crea carpeta para wavs
+    os.makedirs(out_dir, exist_ok=True)
+
+    file_path = f'{out_dir}{save_name}'
+    print(f'[LOG] Guardando audio: `{file_path}')
+    wavfile.write(file_path, fs, data_as_int16)
+
+# frecuencia
+
+# data = [[fft], [freqs], [legends]]
+def freq_graph_multiple_data(data, f_min=0, f_max=0, y_min=0, y_max=0, show=True):
+    fig, axis = plt.subplots(figsize=(8, 4))
+
+    for i, (fft, freqs, label) in enumerate(data):
+        # print(label)
+        N = len(freqs)
+        x = freqs[:N // 2]
+        y = np.abs(fft[:N // 2])
+        axis.plot(x, y, label=label, alpha=0.90, linewidth=((len(data)-i-1)*0.5 + 1.5))
+
+    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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+
+    # max 3 decimales
+    axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+    plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
+
+    axis.set_xlim([f_min, f_max if f_max != 0 else 20000])
+    axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+    axis.legend(loc='upper right')
+
+    if show:
+        plt.show()
+
+    return fig, axis
+
+def freq_graph_data_norm(x, y, x_min=0, x_max=0, y_min=0, y_max=0, show=True,
+                    xlabel="", ylabel=""):
+
+    fig, axis = plt.subplots(figsize=(8, 4))
+
+    axis.plot(x, y)
+
+    if xlabel == "":
+        axis.set(xlabel='Frecuencia normalizada')
+
+    if ylabel == "":
+        axis.set(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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+
+    axis.set_xlim([x_min, x_max if x_max != 0 else 1])
+    axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+    if show:
+        plt.show()
+
+    return fig, axis
+
+def freq_graph_multiple_data_norm(x, y_arr, labels,
+                                  x_min=0, x_max=0, y_min=0, y_max=0,
+                                  show=True, xlabel="", ylabel=""):
+
+    fig, axis = plt.subplots(figsize=(8, 4))
+
+    for i, y in enumerate(y_arr):
+        axis.plot(x, y, label=labels[i], alpha=0.75)
+
+    if xlabel == "":
+        axis.set(xlabel='Frecuencia normalizada')
+
+    if ylabel == "":
+        axis.set(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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+    axis.legend(loc='upper right')
+
+    axis.set_xlim([x_min, x_max if x_max != 0 else 1])
+    axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+    if show:
+        plt.show()
+
+    return fig, axis
+
+
+def freq_graph_data(x, y, f_min=0, f_max=0, y_min=0, y_max=0, show=True,
+                    xlabel="", ylabel=""):
+    fig, axis = plt.subplots(figsize=(8, 4))
+
+    axis.plot(x, y)
+
+    if xlabel == "":
+        axis.set(xlabel='Frecuencia [Hz]')
+
+    if ylabel == "":
+        axis.set(ylabel='Magnitud')
+
+    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)
+
+    # configuracion de ticks del eje x
+    axis.xaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.xaxis.set_minor_locator(AutoMinorLocator(5))
+
+    axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
+    axis.yaxis.set_minor_locator(AutoMinorLocator(4))
+
+    plt.tight_layout()
+
+    # max 3 decimales
+    axis.xaxis.set_major_formatter(FuncFormatter(ticks_label_format))
+    plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
+
+    axis.set_xlim([f_min, f_max if f_max != 0 else 20000])
+    axis.set_ylim([y_min, y_max if y_max != 0 else 1.05*max(y)])
+
+    if show:
+        plt.show()
+
+    return fig, axis
+
+def freq_compute_fft(fs, data, t=0, dt=0, N=0):
+    i = 0
+    di = fs*len(data)
+    if t != 0 or dt != 0:
+        i = int(t*fs)
+        di = int((t+dt)*fs)
+
+    interval_data = data[i:di]
+
+    # puntos de la fft
+    if N == 0:
+        N = len(interval_data)*4
+
+    interval_fft = fft(interval_data, N)
+    interval_freqs = fftfreq(N, d=1/fs)
+
+    return interval_fft, interval_freqs
+
+# hace la transformacion a frecuencias y pasa lo transformado a `freq_graph_data`
+def freq_plot(fs, data, f_min=0, f_max=0, y_min=0, y_max=0,
+              t=0, dt=0, a=0, da=0, show=False, N=0, save_name="", save_dir=""):
+
+    interval_fft, interval_freqs = freq_compute_fft(fs, data, t, dt, N=N)
+    N = len(interval_fft)
+
+    # se toma la parte positiva en ambos casos (primer parte del arreglo)
+    x = interval_freqs[:N // 2]
+    y = np.abs(interval_fft[:N // 2])
+
+    fig, ax = freq_graph_data(x, y, f_min, f_max, y_min, y_max, show=show)
+
+    if save_name != "":
+        save_plot(fig, name=save_name, save_dir=save_dir)
+
+    return fig, ax
+
+# frecuencia de muestreo comun
+# computa y grafica en una figura la fft the los datos en `data_arr`
+def freq_plot_multiple(fs, data_arr, leg_arr, save_name="",
+                       f_min=0, f_max=0, y_min=0, y_max=0, t=0, dt=0, show=True):
+
+    fft_freqs_arr = []
+    for i, data in enumerate(data_arr):
+        fft, freqs = freq_compute_fft(fs, data, t, dt)
+        fft_freqs_arr.append([fft, freqs, leg_arr[i]])
+
+    fig, axis = freq_graph_multiple_data(fft_freqs_arr, f_min, f_max, y_min, y_max, show)
+
+    if save_name != "":
+        save_plot(fig, save_name)
+
+def generate_spectrogram(fs, data, t=0, dt=0, N=1024, overlp=16, win='hamm'):
+    if dt == 0:
+        dt = (len(data)/fs)-t
+
+    i = int(t*fs)
+    di = int((t+dt)*fs)
+    interval_data = data[i:di]
+
+    # `nperseg`  tamaño de ventana (número de muestras por segmento)
+    # `noverlap` cantidad de solapamiento entre ventanas
+    f, time, Sxx = spectrogram(interval_data, fs=fs, nperseg=N, noverlap=overlp,
+                               window=win)
+
+    return f, time, Sxx
+
+def spectrogram_plot(fs, data, save_name="", save_dir="", t=0, dt=0, N=1024,
+                    overlp=16, win='hamm', xlim=[], ylim=[],
+                    shading='gouraud', cmap='viridis', show=False):
+
+    # f, time, Sxx = generate_spectrogram(fs, data, t, dt, N, overlp, win)
+    f, time, Sxx = spectrogram(data, fs=fs, nperseg=N, noverlap=overlp,
+                               window=win)
+    fig, axis = plt.subplots(figsize=(8, 4))
+
+    # plt.pcolormesh(time, f, Sxx**0.5)
+    plt.pcolormesh(time, f, 10*np.log10(Sxx), shading=shading, cmap=cmap)
+
+    plt.ylabel('Frecuencia [Hz]')
+    plt.xlabel('Tiempo [s]')
+
+    if len(xlim) != 0:
+        plt.xlim(xlim)
+
+    if len(ylim) != 0:
+        plt.ylim(ylim)
+    else:
+        plt.ylim(1, 20000)
+
+    if show == True:
+        plt.show()
+    else:
+        if save_name != "":
+            save_plot(fig, name=save_name, save_dir=save_dir, overwrite=True)
+
+    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)