TB065

Index Commits Files Refs
tp/scripts/tercera_parte.py (22970B)
   1 from utils import *
   2 from scipy.signal import firwin, freqz, tf2zpk, get_window
   3 from scipy.fft import fftshift
   4 from scipy.io.wavfile import write as write_wav
   5 import random
   6 
   7 import pickle
   8 database_name = 'database.pkl'
   9 
  10 ############################# Tercera parte ###################################
  11 
  12 cutoff = 2650    # frecuencia de corte
  13 M = 700          # orden FIR (número de coeficientes)
  14 
  15 def filtro_fir():
  16     fs = 44100
  17 
  18     # Diseño FIR pasabajos con ventana
  19     b = firwin(M, cutoff, fs=fs, window='hamming')
  20 
  21     w, H = freqz(b, worN=2048, fs=fs)
  22     fase = np.unwrap(np.angle(H))*180/np.pi
  23 
  24     fig, ax1, ax2 = freq_response_plot(w, H, fase, show=False)
  25     save_plot(fig, "respuesta_en_frecuencia_pasa-bajos_fir")
  26 
  27 # `a` son los coeficientes de la respuesta al impulso (coinciden con los
  28 # coeficientes de respuesta en frecuencia)
  29 def filtro_fir_polos_y_ceros(a):
  30     zeros, poles, gain = tf2zpk(a, [1])
  31 
  32     # Crear figura
  33     fig, axis = plt.subplots(figsize=(8, 4))
  34 
  35     axis.scatter(np.real(zeros), np.imag(zeros),
  36                  s=25, facecolors='none', edgecolors='tab:blue', zorder=10,
  37                  label='Ceros', linewidth=1.25)
  38 
  39     axis.scatter(np.real(poles), np.imag(poles),
  40                  s=25, marker='x', color='tab:red',
  41                  label='Polos')
  42 
  43     axis.set_xlabel("Real", color="black")
  44     axis.set_ylabel("Imaginario", color="black")
  45 
  46     # Unidad círculo para referencia
  47     # theta = np.linspace(0, 2*np.pi, 100)
  48     # plt.plot(np.cos(theta), np.sin(theta))  # círculo unitario
  49 
  50     # axis.yaxis.set_major_locator(MaxNLocator(nbins=5))
  51 
  52     axis.grid(True, which='major', color='black', linestyle=':', linewidth=1.00)
  53     axis.grid(True, which='minor', color='black', linestyle=':', linewidth=0.50)
  54     axis.xaxis.set_minor_locator(AutoMinorLocator(2))
  55 
  56     plt.grid(True)
  57     plt.axis('equal')
  58     axis.legend()
  59     save_plot(fig, "polos_y_ceros_pasa-bajos_fir")
  60 
  61 
  62 # a diferencia de la funcion `filtro_fir` se genera el filtro mediante
  63 # operaciones elementales, como la multiplicacion por ventana, en lugar de usar
  64 # una funcion de libreria externa como `firwin`
  65 def filtro_fir_deducido():
  66     fs = 44100
  67 
  68     # respuesta ideal pasabajos: sinc centrada en M/2
  69     n = np.arange(M + 1)
  70     wc = 2*np.pi*cutoff / fs
  71 
  72     # h_ideal = sinc(wc*n)/(pi n); wc = 2pi*fc/fs
  73     # se normaliza la ganancia a 1 multiplicando por 2.0*(fc/fs)
  74     h_ideal = np.sinc(2.0 * (cutoff/fs) * (n - M/2))
  75 
  76     # ventana de hamming (de acuerdo a la formula de wikipedia)
  77     v_hamming = 0.54 - 0.46 * np.cos(2*np.pi*n/M)
  78 
  79     # respuesta del filtro FIR (version acotada de la sinc)
  80     h = h_ideal * v_hamming
  81 
  82     # se normaliza para tener ganancia unitaria para frecuancias <= fc
  83     h = h / np.sum(h)
  84     return h, fs
  85 
  86 def filtro_fir_analisis(h, fs):
  87     fig, ax = dtime_plot(M, h, "respuesta_al_impulso_filtro_fir",
  88                          f'Respuesta al impulso filtro FIR grado {M}')
  89 
  90     # respuesta en frecuencia del filtro
  91     w, H = freqz(h, worN=2048, fs=fs)
  92     fase = np.unwrap(np.angle(H)) * 180 / np.pi
  93 
  94     fig, ax1, ax2 = freq_response_plot(w, H, fase, show=False, fc=5e3)
  95     save_plot(fig, "respuesta_en_frecuencia_pasa-bajos_fir")
  96 
  97     # polos y ceros
  98     filtro_fir_polos_y_ceros(h)
  99 
 100 def analisis_freq_ventanas():
 101     N = 1024
 102 
 103     v_rectangular = np.ones(N)
 104     v_hamming = np.hamming(N)
 105 
 106     # freq_plot(44100, v_hamming, "v_hamming_freq", f_max=2000, N=N*8)
 107     # freq_plot(44100, v_rectangular, "v_rectangular_freq", f_max=2000, N=N*8)
 108 
 109     v_rect_fft, _ = freq_compute_fft(44100, v_rectangular, N=N*8)
 110     v_hamm_fft, _ = freq_compute_fft(44100, v_hamming, N=N*8)
 111 
 112     # se centra el lobulo principal (frecuencia 0 en el centro del arreglo)
 113     v_rect_fft = fftshift(v_rect_fft)
 114     v_hamm_fft = fftshift(v_hamm_fft)
 115 
 116     v_rect_potencia = np.abs(v_rect_fft)**2
 117     v_hamm_potencia = np.abs(v_hamm_fft)**2
 118 
 119     # se agrega 1e-12 para evitar dividir por cero
 120     v_rect_potencia_db = 10 * np.log10(v_rect_potencia + 1e-12)
 121     v_hamm_potencia_db = 10 * np.log10(v_hamm_potencia + 1e-12)
 122 
 123     # Normalizar para que el pico del lóbulo principal sea 0 dB
 124     v_rect_potencia_db = v_rect_potencia_db - np.max(v_rect_potencia_db)
 125     v_hamm_potencia_db = v_hamm_potencia_db - np.max(v_hamm_potencia_db)
 126 
 127     # Gráfico de la Ventana Rectangular
 128     f = np.linspace(-0.5, 0.5, N*8)
 129 
 130     fig, ax = freq_graph_data_norm(f, v_rect_potencia_db,
 131                                    x_min=-0.04, x_max=0.04, y_min=-200, y_max=10,
 132                                    show=False)
 133     save_plot(fig, "potencia_db_espectro_ventana_rectangular")
 134 
 135     fig, ax = freq_graph_data_norm(f, v_hamm_potencia_db,
 136                                    x_min=-0.04, x_max=0.04, y_min=-200, y_max=10,
 137                                    show=False)
 138     save_plot(fig, "potencia_db_espectro_ventana_hamming")
 139 
 140 
 141     data_arr = [v_rect_potencia_db, v_hamm_potencia_db]
 142     leg_arr = ["Potencia espectro ventana rectangular",
 143                "Potencia espectro ventana Hamming"]
 144 
 145     fig, ax = freq_graph_multiple_data_norm(f, data_arr, leg_arr,
 146                                    x_min=-0.04, x_max=0.04, y_min=-100, y_max=40,
 147                                    show=False)
 148 
 149     save_plot(fig, "potencia_db_espectro_ventana_comparacion_hamming_rect")
 150 
 151 def comparacion_de_espectrogramas_filtrado_vs_original(h):
 152     for i, cancion in enumerate(canciones_dataset):
 153         spectrogram_plot(cancion.fs, cancion.data,
 154                         f'{cancion.name}_espectrograma_original_44100Hz',
 155                         N=1024, ylim=[0, 20000])
 156 
 157         filter_output = np.convolve(cancion.data, h, mode='same')
 158         spectrogram_plot(cancion.fs, filter_output,
 159                         f'{cancion.name}_espectrograma_filtrado_{cutoff}Hz',
 160                         N=1024, ylim=[0, 20000])
 161 
 162         spectrogram_plot(cancion.fs, filter_output,
 163                         f'{cancion.name}_espectrograma_filtrado_{cutoff}Hz_ylim_3000Hz_inferno',
 164                         cmap='inferno', ylim=[0, 3000], N=4096, win='hamming')
 165 
 166 def generar_base_de_datos():
 167     h, fs = filtro_fir_deducido()
 168 
 169     DB = {
 170         'hash_nbits': 20,      # Cantidad de bits de los hashes
 171         'n_entries': 20,       # Cantidad de columnas de la tabla
 172         'ID_nbits': 12,        # Cantidad de bits para ID numérico de la canción
 173         'tabla': np.zeros((2 ** 20, 20), dtype=np.uint32)
 174     }
 175 
 176     for i, cancion in enumerate(canciones_dataset):
 177         data_filtered = np.convolve(cancion.data, h, mode='same')
 178 
 179         # Sxx es la matriz de energía donde el eje `y` es frecuencia (m) y
 180         # el eje `x` es tiempo (n).
 181         #
 182         # `nperseg`  tamaño de ventana (número de muestras por segmento)
 183         # `noverlap` cantidad de solapamiento entre ventanas
 184         f, t, E = spectrogram(data_filtered, fs=cancion.fs,
 185                 nperseg=1024, noverlap=24, window='hamm')
 186 
 187         # E.shape retorna (num_bandas, num_frames) -> (m, n)
 188         # num_bandas, num_frames = E.shape
 189         # E = obtener_caracteristicas(E)
 190 
 191         H = generar_huella(E, fs)
 192 
 193         # if i == 0:
 194         #     graficar_huella(H, f'{cancion.name}_huella_acustica_completa')
 195 
 196         # graficar_huella(H, f'{cancion.name}_huella_acustica', xlim=[0, 100])
 197         DB = guardar_huella(i, H, DB)
 198 
 199     with open(f'{database_name}', 'wb') as f:
 200         pickle.dump(DB, f)
 201 
 202     print(f'[LOG] Base de datos guardada en `./{database_name}`')
 203     return DB
 204 
 205 def generar_base_de_datos_si_no_existe():
 206     if os.path.exists(database_name):
 207         print(f'[LOG] Base de datos ya existe en `./{database_name}`, pasando...')
 208         try:
 209             with open(database_name, 'rb') as db_file:
 210                 DB = pickle.load(db_file)
 211 
 212             print(f"[LOG] Se cargaron los archivos de `{database_name}`")
 213 
 214         except FileNotFoundError:
 215             print(f"[ERROR] No se encontro el archivo `{database_name}`")
 216         except EOFError:
 217             print(f"[ERROR] El archivo `{database_name}` puede estar incompleto o corrupto")
 218         except Exception as e:
 219             print(f"[ERROR] Ocurrio un error inesperado: {e}")
 220     else:
 221         cargar_canciones_dataset()
 222         DB = generar_base_de_datos()
 223 
 224     return DB
 225 
 226 # calcula la matriz de características dado un espectrograma `E`
 227 def obtener_caracteristicas(E):
 228     bandas_totales = 21  # Número de bandas de frecuencia (M)
 229     f_min = 300          # Frecuencia inferior de la primera banda (300 Hz)
 230     f_max = 2000         # Frecuencia superior de la última banda (2 kHz)
 231 
 232     num_bandas, num_frames = E.shape
 233 
 234     # La matriz F tendrá una fila menos (m+1 en E) y una columna menos (n-1 en E)
 235     # num_bandas_F = num_bandas - 1 (para evitar el desbordamiento de m+1)
 236     # num_frames_F = num_frames - 1 (para evitar el desbordamiento de n-1)
 237     F = np.zeros((num_bandas - 1, num_frames - 1), dtype=int)
 238 
 239     # Se itera sobre todas las bandas y frames que permiten la comparación
 240     for n in range(1, num_frames):      # n: tiempo (frame actual)
 241         for m in range(num_bandas - 1): # m: banda
 242             # Diferencia de energía entre banda m y m+1 en el tiempo 'n'
 243             diff_actual = E[m, n] - E[m + 1, n]
 244 
 245             # Frame anterior 'n-1'
 246             # Diferencia de energía entre banda m y m+1 en el tiempo 'n-1'
 247             diff_anterior = E[m, n - 1] - E[m + 1, n - 1]
 248 
 249             # Lógica de la Función Signo F(m, n)
 250             if diff_actual > diff_anterior:
 251                 F[m, n - 1] = 1 # Se mapea n al índice correcto de la matriz F
 252             else:
 253                 F[m, n - 1] = 0
 254 
 255     return F
 256 
 257 def dividir_en_bandas_logaritmicas(Sxx, fs, n_bandas, fmin, fmax):
 258     # crear la matriz de filtros logarítmicos (Mel)
 259     # `n_fft` para librosa, debe ser el doble del número de filas de frecuencia
 260     # menos 2
 261     n_fft_librosa = (Sxx.shape[0] - 1) * 2
 262 
 263     mel_basis = librosa.filters.mel(
 264         sr=fs,
 265         n_fft=n_fft_librosa,
 266         n_mels=n_bandas,
 267         fmin=fmin,
 268         fmax=fmax)
 269 
 270     # multiplicar la matriz de filtros por el espectrograma de potencia
 271     # E_banda tendrá dimensiones (21 bandas, N_FRAMES) -> E(m, n)
 272     E_banda = np.dot(mel_basis, Sxx)
 273 
 274     # convertir la energía de las bandas a dB para aplicar la función F(m, n)
 275     # F(m,n) debe operar sobre la energía en dB para ser robusta.
 276     # se usa la referencia absoluta (ref=1.0) para que 0dB sea el nivel absoluto.
 277     E_db = librosa.power_to_db(E_banda, ref=1.0)
 278     return E_db
 279 
 280 
 281 def generar_huella(E, fs):
 282     E_db = dividir_en_bandas_logaritmicas(E, fs, 21, 300, 2000)
 283 
 284     # E_db tiene dimensiones (m, n) -> (n_bandas, n_frames)
 285     num_bandas, num_frames = E_db.shape
 286 
 287     # La huella H(m, n) tendrá dimensiones (n_bandas - 1, n_frames - 1)
 288     H = np.zeros((num_bandas - 1, num_frames - 1), dtype=int)
 289 
 290     # n: tiempo (frame), m: banda
 291     for n in range(1, num_frames):    # n: empieza en 1 (segundo frame)
 292         for m in range(num_bandas - 1): # m: va de la primera hasta la penúltima banda
 293             diff_actual = E_db[m, n] - E_db[m + 1, n]
 294             diff_anterior = E_db[m, n - 1] - E_db[m + 1, n - 1]
 295 
 296             if diff_actual > diff_anterior:
 297                 H[m, n - 1] = 1 # Se mapea n-1 al índice de la matriz H
 298             else:
 299                 H[m, n - 1] = 0
 300 
 301     return H
 302 
 303 def graficar_huella(H, save_name="", xlim=[], ylim=[], save_dir=""):
 304     fig, axis = plt.subplots(figsize=(8, 4))
 305     plt.pcolormesh(H, shading='auto', cmap='binary')
 306 
 307     # plt.title('Huella Digital Acústica H(m, n)')
 308     plt.ylabel('Diferencia de Banda Logarítmica')
 309     plt.xlabel('Frame de Tiempo')
 310     # plt.colorbar(ticks=[0, 1], label='Valor de H(m, n)')
 311     plt.gca().invert_yaxis() # invertir el eje y para que la frecuencia baja esté abajo
 312     plt.tight_layout()
 313 
 314     if len(xlim) != 0:
 315         plt.xlim(xlim)
 316 
 317     if len(ylim) != 0:
 318         plt.ylim(ylim)
 319 
 320     if save_name == "":
 321         plt.show()
 322     else:
 323         save_plot(fig, save_name, save_dir=save_dir)
 324 
 325 def guardar_huella(ID, huella, DB):
 326     """
 327     Guarda la huella digital acústica 'huella' en la tabla hash de la estructura
 328     DB, identificando la canción con el identificador numérico.
 329 
 330     Parámetros
 331     ----------
 332     ID : int
 333         Identificador numérico del tema que corresponde a esta huella.
 334     huella : np.ndarray
 335         Huella acústica en forma de matriz binaria (unos y ceros).
 336     DB : dict
 337         Estructura con los siguientes campos:
 338             - 'hash_nbits': número de bits para el hash
 339             - 'n_entries': número de columnas de la tabla
 340             - 'ID_nbits': número de bits para guardar ID numérico de la canción
 341             - 'tabla': tabla hash de tamaño (2**hash_nbits, n_entries)
 342 
 343     Retorna
 344     -------
 345     DB : dict
 346         Estructura con tabla hash actualizada.
 347     """
 348 
 349     # Verificamos que huella sea una matriz binaria
 350     if not np.all((huella == 0) | (huella == 1)):
 351         raise ValueError("La matriz HUELLA contiene elementos no binarios")
 352 
 353     # Verificamos que huella tenga hash_nbits filas
 354     if huella.shape[0] != DB['hash_nbits']:
 355         raise ValueError(
 356             f"La matriz HUELLA tiene {huella.shape[0]} filas en lugar de {DB['hash_nbits']}"
 357         )
 358 
 359     # Generamos el elemento val a guardar, concatenando los bits del ID con los
 360     # bits del tiempo de cada frame
 361     frames_nbits = 32 - DB['ID_nbits']
 362     frames = np.mod(np.arange(1, huella.shape[1] + 1), 2 ** frames_nbits)
 363     val = np.uint32(ID + (2 ** DB['ID_nbits']) * frames)
 364 
 365     # Obtenemos las filas a guardar en la tabla, pasando las características de binario a decimal
 366     # (bi2de en MATLAB toma bits por fila; aquí usamos huella.T para el mismo comportamiento)
 367     hash_vals = np.dot(huella.T, 1 << np.arange(huella.shape[0])) + 1  # +1 por índices base 1
 368 
 369     # Primero grabamos en las filas en que queda espacio
 370     tabla = DB['tabla']
 371     fila_ok = tabla[hash_vals - 1, -1] == 0  # Filas que tienen espacio al final
 372     hash_ok = hash_vals[fila_ok]
 373 
 374     # Cantidad de elementos no nulos por fila
 375     count_nonzero = np.sum(tabla[hash_ok - 1, :] != 0, axis=1)
 376     indices = (hash_ok - 1, count_nonzero)  # (fila, columna disponible)
 377     tabla[indices] = val[fila_ok]
 378 
 379     # Finalmente grabamos en las filas que están llenas, pisando algún valor anterior al azar
 380     hash_col = hash_vals[~fila_ok]
 381     if len(hash_col) > 0:
 382         idx_rand = np.ceil(np.random.rand(len(hash_col)) * DB['n_entries']).astype(int) - 1
 383         tabla[hash_col - 1, idx_rand] = val[~fila_ok]
 384 
 385     DB['tabla'] = tabla
 386     return DB
 387 
 388 def query_DB(DB, huella):
 389     """
 390     Hace un query a la base de datos para obtener el ID de las canciones
 391     coincidentes con la huella. Devuelve los 5 primeros resultados que mejor
 392     coinciden, ordenados en orden de prioridad descendente.
 393 
 394     Parámetros
 395     ----------
 396     DB : dict
 397         Estructura con los siguientes campos:
 398             - 'hash_nbits': número de bits para el hash
 399             - 'n_entries': número de columnas de la tabla
 400             - 'ID_nbits': número de bits para guardar ID numérico de la canción
 401             - 'tabla': tabla hash (np.ndarray de tamaño [2**hash_nbits, n_entries])
 402     huella : np.ndarray
 403         Huella acústica binaria (matriz de 0s y 1s)
 404 
 405     Retorna
 406     -------
 407     ID : np.ndarray
 408         Identificadores numéricos de los primeros 5 resultados que matchean
 409     MATCHES : np.ndarray
 410         Número de matches que tuvo cada ID
 411     """
 412 
 413     # Verificamos que HUELLA sea una matriz binaria
 414     if not np.all((huella == 0) | (huella == 1)):
 415         raise ValueError("La matriz HUELLA contiene elementos no binarios")
 416 
 417     # Verificamos que HUELLA tenga hash_nbits filas
 418     if huella.shape[0] != DB['hash_nbits']:
 419         raise ValueError(
 420             f"La matriz HUELLA tiene {huella.shape[0]} filas en lugar de {DB['hash_nbits']}"
 421         )
 422 
 423     # Obtenemos las filas a buscar en la tabla (binario → decimal)
 424     hash_vals = np.dot(huella.T, 1 << np.arange(huella.shape[0])) + 1  # +1 por índice MATLAB base 1
 425 
 426     # Extraemos los elementos de la tabla que corresponden a los hashes dados
 427     vals = DB['tabla'][hash_vals - 1, :]   # restamos 1 para índice base 0
 428     vals = vals[vals != 0]
 429 
 430     # Si todos los elementos eran nulos, devolvemos ceros
 431     if vals.size == 0:
 432         return np.array([0], dtype=np.uint32), np.array([0], dtype=np.uint32)
 433 
 434     # Extraemos de cada elemento el ID numérico y su frame
 435     ID1 = np.mod(vals, 2 ** DB['ID_nbits'])
 436     frames = np.floor_divide(vals, 2 ** DB['ID_nbits'])
 437 
 438     # Filtrado temporal: para cada ID, contamos coincidencias dentro del frame_span
 439     frame_span = huella.shape[1]
 440     unique_IDs = np.unique(ID1)
 441     MATCHES = np.zeros(len(unique_IDs), dtype=int)
 442 
 443     for k, uid in enumerate(unique_IDs):
 444         frame_aux = frames[ID1 == uid]
 445         matches = 0
 446         for k2 in range(len(frame_aux)):
 447             # Número de matches en intervalo frame_span
 448             match_aux = np.count_nonzero(
 449                 (frame_aux >= frame_aux[k2]) &
 450                 (frame_aux <= frame_aux[k2] + frame_span)
 451             )
 452             if match_aux > matches:
 453                 matches = match_aux
 454         MATCHES[k] = matches
 455 
 456     # Ordenar por número de coincidencias en orden descendente
 457     idx = np.argsort(-MATCHES)  # orden descendente
 458     MATCHES = MATCHES[idx]
 459     unique_IDs = unique_IDs[idx]
 460 
 461     # Mantener solo los 5 primeros resultados
 462     Nm = min(5, len(unique_IDs))
 463     ID = unique_IDs[:Nm]
 464     MATCHES = MATCHES[:Nm]
 465 
 466     return ID.astype(np.uint32), MATCHES.astype(np.uint32)
 467 
 468 def evaluar_cancion_raw(DB, cancion_data, cancion_fs, cancion_name):
 469     h, fs = filtro_fir_deducido()
 470 
 471     nro_pruebas = 0
 472     data = np.convolve(cancion_data, h, mode='same')
 473     print(f'[LOG] Evaluando cancion `{cancion_name}`')
 474     duracion_cancion = len(cancion_data) / cancion_fs
 475     print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s')
 476 
 477     for r in range(0, 10):
 478         for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos
 479             t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T)
 480             t_inicial = 0 if t_inicial < 0 else t_inicial
 481             print(f'[LOG] Intervalo {t_inicial:05.02f}:{t_inicial+T:05.02f}s', end='')
 482             f, t, E = generate_spectrogram(cancion_fs, data, t=t_inicial, dt=T)
 483             H = generar_huella(E, cancion_fs)
 484 
 485             nro_pruebas += 1
 486             id, matches = query_DB(DB, H)
 487             print(f' mejor coincidencia: `{canciones_dataset[id[0]].name}`')
 488 
 489     print("")
 490 
 491 def evaluar_cancion(DB, cancion):
 492     h, fs = filtro_fir_deducido()
 493 
 494     nro_pruebas = 0
 495     data = np.convolve(cancion.data, h, mode='same')
 496     print(f'[LOG] Evaluando cancion `{cancion.path}`')
 497     duracion_cancion = len(data) / cancion.fs
 498     print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s')
 499 
 500     for r in range(0, 10):
 501         for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos
 502             t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T)
 503             t_inicial = 0 if t_inicial < 0 else t_inicial
 504             print(f'[LOG] Intervalo {t_inicial:05.02f}:{t_inicial+T:05.02f}s', end='')
 505             f, t, E = generate_spectrogram(cancion.fs, data, t=t_inicial, dt=T)
 506             H = generar_huella(E, cancion.fs)
 507 
 508             nro_pruebas += 1
 509             id, matches = query_DB(DB, H)
 510             print(f' mejor coincidencia: `{canciones_dataset[id[0]].name}`')
 511 
 512     print("")
 513 
 514 def agregar_ruido_a_cancion(data, sigma=0.1):
 515     ruido = np.random.normal(loc=0, scale=sigma, size=len(data))
 516     cancion_con_ruido = data + ruido
 517     cancion_con_ruido = np.clip(cancion_con_ruido, -1.0, 1.0)
 518     return cancion_con_ruido
 519 
 520 def calcular_potencia_ruido(potencia_orig, snr_db):
 521     snr_lineal = 10**(snr_db / 10)
 522     potencia_ruido = potencia_orig / snr_lineal
 523     return potencia_ruido
 524 
 525 def evaluar_aciertos_agregar_ruido(DB):
 526     h, fs = filtro_fir_deducido()
 527 
 528     incorrectos = 0
 529     nro_pruebas = 0
 530 
 531     for i, cancion in enumerate(canciones_dataset):
 532         lista_a_probar = []
 533         lista_a_probar.append(cancion)
 534 
 535         for snr_db in [0, 10, 20]:
 536             potencia_cancion = np.mean(cancion.data**2)
 537             potencia_ruido = calcular_potencia_ruido(potencia_cancion, snr_db)
 538             sigma_ruido = np.sqrt(potencia_ruido)
 539             data = agregar_ruido_a_cancion(cancion.data, sigma_ruido)
 540             name = cancion.name + f"_ruido_SNR_{snr_db}dB"
 541             lista_a_probar.append(SimpleNamespace(
 542                 name=name,
 543                 data=data,
 544                 fs=cancion.fs))
 545 
 546         for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos
 547             duracion_cancion = len(cancion.data) / cancion.fs
 548             print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s')
 549 
 550             t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T)
 551             t_inicial = 0 if t_inicial < 0 else t_inicial
 552             print(f'[LOG] Intervalo {T:05.02f}s {t_inicial:05.02f}:{t_inicial+T:05.02f}s')
 553 
 554             for elemento in lista_a_probar:
 555                 data = np.convolve(elemento.data, h, mode='same')
 556                 print(f'[LOG] Evaluando cancion `{elemento.name}`', end='')
 557 
 558                 f, t, E = generate_spectrogram(elemento.fs, elemento.data, t=t_inicial, dt=T)
 559                 H = generar_huella(E, elemento.fs)
 560 
 561                 nro_pruebas += 1
 562                 id, matches = query_DB(DB, H)
 563                 print(f' mejor coincidencia: `{canciones_dataset[id[0]].name}`')
 564                 if id[0] != i:
 565                     incorrectos += 1
 566 
 567         print("")
 568 
 569 
 570     print(f'[LOG] cantidad de pruebas incorrectas: {incorrectos} de {nro_pruebas}')
 571     print(f'[LOG] error porcentual: {(incorrectos/nro_pruebas)*100:0.02f}%')
 572 
 573 
 574 def evaluar_aciertos(DB):
 575     h, fs = filtro_fir_deducido()
 576 
 577     incorrectos = 0
 578     nro_pruebas = 0
 579     for i, cancion in enumerate(canciones_dataset):
 580         data = np.convolve(cancion.data, h, mode='same')
 581         print(f'[LOG] Evaluando cancion `{cancion.path}`')
 582         duracion_cancion = len(data) / cancion.fs
 583         print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s')
 584         for r in range(0, 5):
 585             for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos
 586                 t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T)
 587                 t_inicial = 0 if t_inicial < 0 else t_inicial
 588                 print(f'[LOG] Intervalo {t_inicial:05.02f}:{t_inicial+T:05.02f}s', end='')
 589                 f, t, E = generate_spectrogram(cancion.fs, data, t=t_inicial, dt=T)
 590                 H = generar_huella(E, cancion.fs)
 591 
 592                 nro_pruebas += 1
 593                 id, matches = query_DB(DB, H)
 594 
 595                 print(f' mejor coincidencia: `{canciones_dataset[id[0]].name}`')
 596                 if id[0] != i:
 597                     incorrectos += 1
 598 
 599         print("")
 600 
 601     print(f'[LOG] cantidad de pruebas incorrectas: {incorrectos} de {nro_pruebas}')
 602     print(f'[LOG] error porcentual: {(incorrectos/nro_pruebas)*100:0.02f}%')
 603