Sesión 2: análisis de propagación del sonido con baRulho

Author

AMAG & SG- Grupo de estudio E’ira & Semillero de Bioacústica y ecoacustica

Propósito de esta sesión

En esta segunda sesión vamos a:

  1. organizar y cargar los datos del experimento
  2. alinear los sonidos regrabados con el archivo maestro
  3. calcular tres métricas de propagación:
    • excess_attenuation
    • signal_to_noise_ratio
    • blur_ratio
  4. Comparar cómo cambian estas métricas con la distancia por tipo de sonido

Importante
En esta práctica trabajaremos un grabador a la vez. Este archivo está escrito para que se use primero con la carpeta zoom y luego, si se quiere, se repita el flujo con grillo.

Estructura esperada de carpetas

Se asume una estructura como esta:

propagacion/
├── zoom/
│   ├── cerrado_5m.wav
│   ├── cerrado_10m.wav...
│   └── master.wav
|   └── master_annotations.txt
└── grillo/
    ├── cerrado_5m.wav
    ├── cerrado_10m.wav.....
    └── master.wav
|   └── master_annotations.txt

1. Instalar y leer Paquetes

paquetes <- c(
  "baRulho", "ohun", "warbleR", "tidyverse", "stringr"
)

instalar <- paquetes[!paquetes %in% installed.packages()[, "Package"]]
if (length(instalar) > 0) install.packages(instalar)


library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.3.2
Warning: package 'tidyr' was built under R version 4.3.2
Warning: package 'readr' was built under R version 4.3.2
Warning: package 'dplyr' was built under R version 4.3.2
Warning: package 'stringr' was built under R version 4.3.2
Warning: package 'lubridate' was built under R version 4.3.2
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   4.0.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(stringr)
library(baRulho)
Loading required package: warbleR
Warning: package 'warbleR' was built under R version 4.3.3
Loading required package: tuneR
Warning: package 'tuneR' was built under R version 4.3.2
Loading required package: seewave
Warning: package 'seewave' was built under R version 4.3.2

Attaching package: 'seewave'

The following object is masked from 'package:lubridate':

    duration

The following object is masked from 'package:readr':

    spec

Loading required package: NatureSounds
Loading required package: knitr
Warning: package 'knitr' was built under R version 4.3.2
Loading required package: ohun
Warning: package 'ohun' was built under R version 4.3.3

Please cite 'ohun' as: 

 Araya-Salas, M., Smith-Vidaurre, G., Chaverri, G., Brenes, J. C., Chirino, F., Elizondo-Calvo, J., & Rico-Guevara, A. 2023. ohun: an R package for diagnosing and optimizing automatic sound event detection. Methods in Ecology and Evolution. https://doi.org/10.1111/2041-210X.14170

Please cite 'baRulho' as: 

Araya-Salas, M., Grabarczyk, E. E., Quiroz-Oliva, M., Garcia-Rodriguez, A., & Rico-Guevara, A. (2025). Quantifying degradation in animal acoustic signals with the R package baRulho. Methods in Ecology and Evolution, 00, 1-12. https://doi.org/10.1111/2041-210X.14481

Attaching package: 'baRulho'

The following object is masked from 'package:seewave':

    attenuation
library(ohun)
library(warbleR)

2. Configuración inicial

2.1 Elegir grabador

grabador <- "grillo"   # usar "zoom" o "grillo"

2.2 Definir rutas

proyecto <- normalizePath(".", winslash = "/", mustWork = TRUE)
ruta_audios <- file.path(proyecto, grabador)

ruta_audios
[1] "C:/Users/anaga/Desktop/practica-semillero/propagacion/grillo"
list.files(ruta_audios)
[1] "cerrado_10m_20260324_142700.wav" "cerrado_15m_20260324_143000.wav"
[3] "cerrado_20m_20260324_143300.wav" "cerrado_25m_20260324_143600.wav"
[5] "cerrado_30m_20260324_143900.wav" "cerrado_5m_20260324_142400.wav" 
[7] "master.wav"                      "master_annotations.txt"         

2.3 Leer tabla de anotaciones del master

master_annotations<- read.delim(
  "./grillo/master_annotations.txt",
  check.names = FALSE,
  stringsAsFactors = FALSE
)  #cambiar el path segun la grabadora que estemos analizando

str(master_annotations)
'data.frame':   25 obs. of  9 variables:
 $ Selection     : int  1 2 3 4 5 6 7 8 9 10 ...
 $ View          : chr  "Spectrogram 1" "Spectrogram 1" "Spectrogram 1" "Spectrogram 1" ...
 $ Channel       : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Begin Time (s): num  1 2.29 5.22 9.05 11.97 ...
 $ End Time (s)  : num  1.99 4.92 8.75 11.67 14.97 ...
 $ Begin File    : chr  "master.wav" "master.wav" "master.wav" "master.wav" ...
 $ Low Freq (Hz) : num  1780 6023 5313 6002 800 ...
 $ High Freq (Hz): num  2040 9734 9720 9693 1200 ...
 $ sound.id      : chr  "start_marker" "s._11" "s._21" "s._31" ...

2.4 Convertir al formato que usa baRulho

master_annotations <- master_annotations |>
  transmute(
    sound.files = "master.wav",
    selec = Selection,
    start = `Begin Time (s)`,
    end = `End Time (s)`,
    bottom.freq = `Low Freq (Hz)` / 1000,
    top.freq = `High Freq (Hz)` / 1000,
    sound.id = sound.id
  )

head(master_annotations)
  sound.files selec     start       end bottom.freq top.freq     sound.id
1  master.wav     1  1.000000  1.990476    1.780021 2.040462 start_marker
2  master.wav     2  2.290476  4.915306    6.022998 9.734321        s._11
3  master.wav     3  5.215306  8.748209    5.313200 9.720356        s._21
4  master.wav     4  9.048209 11.668571    6.002264 9.692854        s._31
5  master.wav     5 11.968571 14.968571    0.800000 1.200000     freq:1_1
6  master.wav     6 15.268571 18.268571    1.800000 2.200000     freq:2_1

3. Preparar archivos de prueba

3.1 Listar audios de prueba

archivos_test <- list.files(
  ruta_audios,
  pattern = "\\.wav$",
  full.names = FALSE
)

archivos_test <- archivos_test[archivos_test != "master.wav"]

archivos_test
[1] "cerrado_10m_20260324_142700.wav" "cerrado_15m_20260324_143000.wav"
[3] "cerrado_20m_20260324_143300.wav" "cerrado_25m_20260324_143600.wav"
[5] "cerrado_30m_20260324_143900.wav" "cerrado_5m_20260324_142400.wav" 

4. Detectar marcadores con ohun

¿Por qué cambiamos el paso de find_markers()?

En teoría, baRulho::find_markers() debería detectar automáticamente los marcadores del archivo maestro en los audios regrabados. Sin embargo, en este proyecto encontramos un problema: la función sí alcanzaba a correr la correlación y la detección, pero luego fallaba con un error de ruta (invalid 'path' argument). Como ohun::template_correlator() y ohun::template_detector() sí funcionaron por separado, en esta guía usamos ese flujo de forma explícita.

Además, al revisar los resultados vimos que:

  • el umbral inicial era demasiado bajo y producía muchísimas detecciones falsas;
  • mezclar start_marker y end_marker hacía que align_test_files() a veces usara el marcador equivocado;
  • por eso decidimos quedarnos solo con el start_marker;
  • y luego hacer una revisión visual del alineamiento y, si hace falta, usar manual_realign(). ## 4.1 Elegir solo los marcadores del master
markers_ref <- master_annotations |>
  filter(sound.id %in% c("start_marker", "end_marker"))

markers_ref
  sound.files selec    start       end bottom.freq top.freq     sound.id
1  master.wav     1  1.00000  1.990476    1.780021 2.040462 start_marker
2  master.wav    25 77.96857 78.959048    1.780021 2.040462   end_marker

4.2 Correlacionar plantillas

tc <- ohun::template_correlator(
  templates = markers_ref,
  files = archivos_test,
  path = ruta_audios,
  cores = 1,
  pb = FALSE
)

class(tc)
[1] "list"                  "template_correlations"

4.3 Detectar picos de correlación

pks <- ohun::template_detector(
  template.correlations = tc,
  threshold = 0.3,
  cores = 1,
  pb = FALSE,
  verbose = FALSE
)

pks
Object of class 'selection_table'
* The output of the following call:
ohun::template_detector(template.correlations = tc, cores = 1, threshold = 0.3, pb = FALSE, verbose = FALSE)

Contains: 
*  A selection table data frame with 78 rows and 6 columns:
Warning: 'xfun::attr()' is deprecated.
Use 'xfun::attr2()' instead.
See help("Deprecated")
|sound.files                     | selec|   start|     end|template      | scores|
|:-------------------------------|-----:|-------:|-------:|:-------------|------:|
|cerrado_10m_20260324_142700.wav |     1|  1.6859|  2.6763|master.wav-1  | 0.3831|
|cerrado_10m_20260324_142700.wav |     2|  1.7207|  2.7112|master.wav-1  | 0.3834|
|cerrado_10m_20260324_142700.wav |     3|  1.7440|  2.7345|master.wav-1  | 0.3991|
|cerrado_10m_20260324_142700.wav |     4| 78.4914| 79.4819|master.wav-1  | 0.5485|
|cerrado_10m_20260324_142700.wav |     5|  1.7091|  2.6996|master.wav-25 | 0.4823|
|cerrado_10m_20260324_142700.wav |     6| 78.4100| 79.4005|master.wav-25 | 0.3043|
... and 72 more row(s)

* A data frame (check.results) with 78 rows generated by check_sels() (as attribute)
created by warbleR 1.1.33

5. Construir la tabla de marcadores para alinear

5.1 Relacionar cada template con el marcador real

marker_lookup <- master_annotations |>
  filter(sound.id %in% c("start_marker", "end_marker")) |>
  transmute(
    template = paste0(sound.files, "-", selec),
    marker = sound.id
  )

marker_lookup
       template       marker
1  master.wav-1 start_marker
2 master.wav-25   end_marker

5.2 Quedarnos solo con un start_marker por archivo

pks_df <- as.data.frame(pks) |>
  left_join(marker_lookup, by = "template")

start_markers <- pks_df |>
  filter(marker == "start_marker") |>
  filter(start < 10) |>
  filter(sound.files != "master.wav") |>
  group_by(sound.files) |>
  slice_max(order_by = scores, n = 1, with_ties = FALSE) |>
  ungroup() |>
  select(sound.files, selec, start, end, scores, marker)

start_markers
# A tibble: 6 × 6
  sound.files                     selec start   end scores marker      
  <chr>                           <int> <dbl> <dbl>  <dbl> <chr>       
1 cerrado_10m_20260324_142700.wav     3 1.74   2.73  0.399 start_marker
2 cerrado_15m_20260324_143000.wav     2 1.20   2.19  0.380 start_marker
3 cerrado_20m_20260324_143300.wav     2 1.06   2.05  0.462 start_marker
4 cerrado_25m_20260324_143600.wav     2 2.06   3.05  0.341 start_marker
5 cerrado_30m_20260324_143900.wav     1 1.36   2.35  0.439 start_marker
6 cerrado_5m_20260324_142400.wav      2 0.930  1.92  0.468 start_marker

6. Alinear los audios regrabados

6.1 Alineamiento automático

aligned_tests <- align_test_files(
  X = master_annotations,
  Y = start_markers,
  path = ruta_audios,
  cores = 1,
  pb = FALSE
)

head(aligned_tests)
                      sound.files selec     start       end bottom.freq
1 cerrado_10m_20260324_142700.wav     1  1.743995  2.734471    1.780021
2 cerrado_10m_20260324_142700.wav     2  3.034471  5.659301    6.022998
3 cerrado_10m_20260324_142700.wav     3  5.959301  9.492203    5.313200
4 cerrado_10m_20260324_142700.wav     4  9.792203 12.412566    6.002264
5 cerrado_10m_20260324_142700.wav     5 12.712566 15.712566    0.800000
6 cerrado_10m_20260324_142700.wav     6 16.012566 19.012566    1.800000
  top.freq     sound.id       marker
1 2.040462 start_marker start_marker
2 9.734321        s._11 start_marker
3 9.720356        s._21 start_marker
4 9.692854        s._31 start_marker
5 1.200000     freq:1_1 start_marker
6 2.200000     freq:2_1 start_marker

6.4 Pasos opcionales: Ajuste manual si hace falta y graficos de la aliniacion

aligned_tests <- manual_realign(
  X = aligned_tests,
  Y = master_annotations,
  path = ruta_audios,
  marker = "start_marker",
  flim = c(0, 12)
)
plot_aligned_sounds(
  X = aligned_tests,
  path = ruta_audios,
  dest.path = "./resultados",
  duration = 4,
  ovlp = 0
)
plotting aligned sounds (step 0 of 0):
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\resultados'

7. Agregar distancia

aligned_tests <- aligned_tests |>
  mutate(
    distance = str_extract(sound.files, "\\d+(?=m)") |> as.numeric()
  )

table(aligned_tests$distance, useNA = "ifany")

 5 10 15 20 25 30 
25 25 25 25 25 25 

8. Definir sonidos de referencia

test_data <- set_reference_sounds(aligned_tests, method =1, path=ruta_audios)
all selections are OK 
head(test_data)
                      sound.files selec     start       end bottom.freq
1 cerrado_10m_20260324_142700.wav     1  1.743995  2.734471    1.780021
2 cerrado_10m_20260324_142700.wav     2  3.034471  5.659301    6.022998
3 cerrado_10m_20260324_142700.wav     3  5.959301  9.492203    5.313200
4 cerrado_10m_20260324_142700.wav     4  9.792203 12.412566    6.002264
5 cerrado_10m_20260324_142700.wav     5 12.712566 15.712566    0.800000
6 cerrado_10m_20260324_142700.wav     6 16.012566 19.012566    1.800000
  top.freq     sound.id       marker distance                        reference
1 2.040462 start_marker start_marker       10                             <NA>
2 9.734321        s._11 start_marker       10 cerrado_5m_20260324_142400.wav-2
3 9.720356        s._21 start_marker       10 cerrado_5m_20260324_142400.wav-3
4 9.692854        s._31 start_marker       10 cerrado_5m_20260324_142400.wav-4
5 1.200000     freq:1_1 start_marker       10 cerrado_5m_20260324_142400.wav-5
6 2.200000     freq:2_1 start_marker       10 cerrado_5m_20260324_142400.wav-6

OPCIONAL gráficar la degradación

ids_plot <- c("s._11", "freq:5_1", "freq:10_1", "freq:15_1", "freq:20_1")

test_data_plot <- test_data |>
  filter(sound.id %in% ids_plot)

table(test_data_plot$sound.id)

freq:10_1 freq:15_1 freq:20_1  freq:5_1     s._11 
        6         6         6         6         6 
table(test_data_plot$distance, test_data_plot$sound.id)
    
     freq:10_1 freq:15_1 freq:20_1 freq:5_1 s._11
  5          1         1         1        1     1
  10         1         1         1        1     1
  15         1         1         1        1     1
  20         1         1         1        1     1
  25         1         1         1        1     1
  30         1         1         1        1     1
for (id in ids_plot) {
  cat("\nProbando:", id, "\n")
  
  tmp <- test_data_plot |>
    dplyr::filter(sound.id == id)
  
  carpeta_id <- file.path(proyecto, "plots", make.names(id))
  dir.create(carpeta_id, recursive = TRUE, showWarnings = FALSE)
  options(dest.path = carpeta_id)
  
  try(
    plot_degradation(
      X = tmp,
      path = ruta_audios,
      nrow = 6,
      ovlp = 70,
      wl = 200,
      margins = c(1, 0.5),
      spectrum = FALSE,
      pb = FALSE
    )
  )
}

Probando: s._11 
Warning: assuming all sound files have the same sampling rate
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\plots\s._11'

Probando: freq:5_1 
Warning: assuming all sound files have the same sampling rate
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\plots\freq.5_1'

Probando: freq:10_1 
Warning: assuming all sound files have the same sampling rate
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\plots\freq.10_1'

Probando: freq:15_1 
Warning: assuming all sound files have the same sampling rate
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\plots\freq.15_1'

Probando: freq:20_1 
Warning: assuming all sound files have the same sampling rate
The image files have been saved in the directory path 'C:\Users\anaga\Desktop\practica-semillero\propagacion\plots\freq.20_1'

9. Limpiar la tabla antes de calcular métricas

test_data_clean <- test_data |>
  filter(!sound.id %in% c("start_marker", "end_marker")) |>
  filter(sound.files != "master.wav")

dim(test_data_clean)
[1] 138  10
table(test_data_clean$sound.id)

 freq:1_1 freq:10_1 freq:11_1 freq:12_1 freq:13_1 freq:14_1 freq:15_1 freq:16_1 
        6         6         6         6         6         6         6         6 
freq:17_1 freq:18_1 freq:19_1  freq:2_1 freq:20_1  freq:3_1  freq:4_1  freq:5_1 
        6         6         6         6         6         6         6         6 
 freq:6_1  freq:7_1  freq:8_1  freq:9_1     s._11     s._21     s._31 
        6         6         6         6         6         6         6 

10. Calcular métricas de propagación

10.1 Atenuación extra

base_df <- test_data_clean |>
  excess_attenuation(path = ruta_audios)
Warning: assuming all sound files have the same sampling rate
head(base_df)
                      sound.files selec     start       end bottom.freq
1 cerrado_10m_20260324_142700.wav     2  3.034471  5.659301    6.022998
2 cerrado_10m_20260324_142700.wav     3  5.959301  9.492203    5.313200
3 cerrado_10m_20260324_142700.wav     4  9.792203 12.412566    6.002264
4 cerrado_10m_20260324_142700.wav     5 12.712566 15.712566    0.800000
5 cerrado_10m_20260324_142700.wav     6 16.012566 19.012566    1.800000
6 cerrado_10m_20260324_142700.wav     7 19.312566 22.312566    2.800000
  top.freq sound.id       marker distance                        reference
1 9.734321    s._11 start_marker       10 cerrado_5m_20260324_142400.wav-2
2 9.720356    s._21 start_marker       10 cerrado_5m_20260324_142400.wav-3
3 9.692854    s._31 start_marker       10 cerrado_5m_20260324_142400.wav-4
4 1.200000 freq:1_1 start_marker       10 cerrado_5m_20260324_142400.wav-5
5 2.200000 freq:2_1 start_marker       10 cerrado_5m_20260324_142400.wav-6
6 3.200000 freq:3_1 start_marker       10 cerrado_5m_20260324_142400.wav-7
  excess.attenuation
1          -7.016200
2          -6.804837
3          -6.237187
4          -9.022259
5         -11.554910
6          -2.930086

10.2 Calcular SNR fila por fila

Encontramos un problema al calcular signal_to_noise_ratio() en lote: la función devolvía un vector con una fila menos que el data.frame. Como el cálculo sí funcionó fila por fila, en esta guía calculamos la SNR de esa manera para evitar que el análisis se rompa.

snr_vals <- vapply(
  seq_len(nrow(base_df)),
  function(i) {
    signal_to_noise_ratio(
      X = base_df[i, , drop = FALSE],
      mar = 0.05,
      path = ruta_audios
    )$signal.to.noise.ratio[[1]]
  },
  numeric(1)
)

base_df$signal.to.noise.ratio <- snr_vals

head(base_df)
                      sound.files selec     start       end bottom.freq
1 cerrado_10m_20260324_142700.wav     2  3.034471  5.659301    6.022998
2 cerrado_10m_20260324_142700.wav     3  5.959301  9.492203    5.313200
3 cerrado_10m_20260324_142700.wav     4  9.792203 12.412566    6.002264
4 cerrado_10m_20260324_142700.wav     5 12.712566 15.712566    0.800000
5 cerrado_10m_20260324_142700.wav     6 16.012566 19.012566    1.800000
6 cerrado_10m_20260324_142700.wav     7 19.312566 22.312566    2.800000
  top.freq sound.id       marker distance                        reference
1 9.734321    s._11 start_marker       10 cerrado_5m_20260324_142400.wav-2
2 9.720356    s._21 start_marker       10 cerrado_5m_20260324_142400.wav-3
3 9.692854    s._31 start_marker       10 cerrado_5m_20260324_142400.wav-4
4 1.200000 freq:1_1 start_marker       10 cerrado_5m_20260324_142400.wav-5
5 2.200000 freq:2_1 start_marker       10 cerrado_5m_20260324_142400.wav-6
6 3.200000 freq:3_1 start_marker       10 cerrado_5m_20260324_142400.wav-7
  excess.attenuation signal.to.noise.ratio
1          -7.016200             14.703985
2          -6.804837              0.367119
3          -6.237187             10.497880
4          -9.022259              4.803336
5         -11.554910              4.087797
6          -2.930086              7.844649

10.3 Blur ratio

degrad_df <- blur_ratio(base_df, path = ruta_audios)
Warning: assuming all sound files have the same sampling rate
head(degrad_df)
                      sound.files selec     start       end bottom.freq
1 cerrado_10m_20260324_142700.wav     2  3.034471  5.659301    6.022998
2 cerrado_10m_20260324_142700.wav     3  5.959301  9.492203    5.313200
3 cerrado_10m_20260324_142700.wav     4  9.792203 12.412566    6.002264
4 cerrado_10m_20260324_142700.wav     5 12.712566 15.712566    0.800000
5 cerrado_10m_20260324_142700.wav     6 16.012566 19.012566    1.800000
6 cerrado_10m_20260324_142700.wav     7 19.312566 22.312566    2.800000
  top.freq sound.id       marker distance                        reference
1 9.734321    s._11 start_marker       10 cerrado_5m_20260324_142400.wav-2
2 9.720356    s._21 start_marker       10 cerrado_5m_20260324_142400.wav-3
3 9.692854    s._31 start_marker       10 cerrado_5m_20260324_142400.wav-4
4 1.200000 freq:1_1 start_marker       10 cerrado_5m_20260324_142400.wav-5
5 2.200000 freq:2_1 start_marker       10 cerrado_5m_20260324_142400.wav-6
6 3.200000 freq:3_1 start_marker       10 cerrado_5m_20260324_142400.wav-7
  excess.attenuation signal.to.noise.ratio blur.ratio
1          -7.016200             14.703985 0.24831103
2          -6.804837              0.367119 0.21163261
3          -6.237187             10.497880 0.29825669
4          -9.022259              4.803336 0.20953474
5         -11.554910              4.087797 0.08529778
6          -2.930086              7.844649 0.06307608

11. Clasificar los sonidos

11.1 Tipo de sonido

degrad_df <- degrad_df |>
  mutate(
    tipo_sonido = case_when(
      str_detect(sound.id, "^s") ~ "Sonidos tipo s",
      TRUE ~ "Tonos puros"
    )
  )

table(degrad_df$tipo_sonido)

Sonidos tipo s    Tonos puros 
            18            120 

11.2 Frecuencia aproximada

degrad_df <- degrad_df |>
  mutate(
    frecuencia_khz = (bottom.freq + top.freq) / 2
  )

summary(degrad_df$frecuencia_khz)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    6.00    9.00   10.14   15.00   20.00 

12. Revisar la tabla final

degrad_df |>
  select(sound.files, sound.id, tipo_sonido, distance,
         frecuencia_khz, excess.attenuation,
         signal.to.noise.ratio, blur.ratio) |>
  head(15)
                       sound.files  sound.id    tipo_sonido distance
1  cerrado_10m_20260324_142700.wav     s._11 Sonidos tipo s       10
2  cerrado_10m_20260324_142700.wav     s._21 Sonidos tipo s       10
3  cerrado_10m_20260324_142700.wav     s._31 Sonidos tipo s       10
4  cerrado_10m_20260324_142700.wav  freq:1_1    Tonos puros       10
5  cerrado_10m_20260324_142700.wav  freq:2_1    Tonos puros       10
6  cerrado_10m_20260324_142700.wav  freq:3_1    Tonos puros       10
7  cerrado_10m_20260324_142700.wav  freq:4_1    Tonos puros       10
8  cerrado_10m_20260324_142700.wav  freq:5_1    Tonos puros       10
9  cerrado_10m_20260324_142700.wav  freq:6_1    Tonos puros       10
10 cerrado_10m_20260324_142700.wav  freq:7_1    Tonos puros       10
11 cerrado_10m_20260324_142700.wav  freq:8_1    Tonos puros       10
12 cerrado_10m_20260324_142700.wav  freq:9_1    Tonos puros       10
13 cerrado_10m_20260324_142700.wav freq:10_1    Tonos puros       10
14 cerrado_10m_20260324_142700.wav freq:11_1    Tonos puros       10
15 cerrado_10m_20260324_142700.wav freq:12_1    Tonos puros       10
   frecuencia_khz excess.attenuation signal.to.noise.ratio blur.ratio
1        7.878659          -7.016200            14.7039853 0.24831103
2        7.516778          -6.804837             0.3671190 0.21163261
3        7.847559          -6.237187            10.4978801 0.29825669
4        1.000000          -9.022259             4.8033360 0.20953474
5        2.000000         -11.554910             4.0877972 0.08529778
6        3.000000          -2.930086             7.8446487 0.06307608
7        4.000000          10.022540             0.8629725 0.21092629
8        5.000000          -3.951127             4.7219936 0.06725804
9        6.000000          -8.708278             4.3772181 0.15432258
10       7.000000         -10.362496             3.8665477 0.13271673
11       8.000000          -6.977798             6.7941617 0.11468298
12       9.000000           5.509840             5.4213404 0.27465450
13      10.000000         -14.499014             2.8871243 0.24348609
14      11.000000          -3.261753             5.1699411 0.12572303
15      12.000000          -2.688906            10.2332508 0.15548912

13. Gráficos

13.1 Atenuación extra vs distancia

ggplot(
  degrad_df,
  aes(x = distance, y = excess.attenuation, color = tipo_sonido)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ tipo_sonido) +
  theme_minimal(base_size = 12) +
  labs(
    x = "Distancia (m)",
    y = "Excess attenuation",
    color = "Tipo de sonido",
    title = "Cambio de la atenuación extra con la distancia"
  )
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 23 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 23 rows containing missing values or values outside the scale range
(`geom_point()`).

13.2 SNR vs distancia

ggplot(
  degrad_df,
  aes(x = distance, y = signal.to.noise.ratio, color = tipo_sonido)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ tipo_sonido) +
  theme_minimal(base_size = 12) +
  labs(
    x = "Distancia (m)",
    y = "Signal-to-noise ratio",
    color = "Tipo de sonido",
    title = "Cambio del SNR con la distancia"
  )
`geom_smooth()` using formula = 'y ~ x'

13.3 Blur ratio vs distancia

ggplot(
  degrad_df,
  aes(x = distance, y = blur.ratio, color = tipo_sonido)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ tipo_sonido) +
  theme_minimal(base_size = 12) +
  labs(
    x = "Distancia (m)",
    y = "Blur ratio",
    color = "Tipo de sonido",
    title = "Cambio de la degradación temporal con la distancia"
  )
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 23 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 23 rows containing missing values or values outside the scale range
(`geom_point()`).

14. Efecto de la frecuencia

14.1 Atenuación extra vs frecuencia

df_puros <- degrad_df |>
  filter(tipo_sonido == "Tonos puros")

mod <- lm(excess.attenuation ~ frecuencia_khz, data = df_puros)

r2 <- summary(mod)$r.squared
r2_adj <- summary(mod)$adj.r.squared
beta <- coef(mod)[2]
ic_beta <- confint(mod)["frecuencia_khz", ]

etiqueta <- paste0(
  "R² = ", round(r2, 3),
  "\nR² ajustado = ", round(r2_adj, 3),
  "\nPendiente = ", round(beta, 3),
  "\nIC95% pendiente = [", round(ic_beta[1], 3), ", ", round(ic_beta[2], 3), "]"
)

ggplot(
  df_puros,
  aes(x = frecuencia_khz, y = excess.attenuation)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = TRUE) +
  annotate(
    "text",
    x = Inf, y = Inf,
    label = paste0("R² = ", round(r2, 3)),
    hjust = 1.1, vjust = 1.1,
    size = 4
  ) +
  theme_minimal(base_size = 12)  +
  labs(
    x = "Frecuencia aproximada (kHz)",
    y = "Atenuación",
    title = "Atenuación según la frecuencia en tonos puros"
  )
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 20 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 20 rows containing missing values or values outside the scale range
(`geom_point()`).

14.2 SNR vs frecuencia

mod <- lm(signal.to.noise.ratio ~ frecuencia_khz, data = df_puros)

r2 <- summary(mod)$r.squared
r2_adj <- summary(mod)$adj.r.squared
beta <- coef(mod)[2]
ic_beta <- confint(mod)["frecuencia_khz", ]

etiqueta <- paste0(
  "R² = ", round(r2, 3),
  "\nR² ajustado = ", round(r2_adj, 3),
  "\nPendiente = ", round(beta, 3),
  "\nIC95% pendiente = [", round(ic_beta[1], 3), ", ", round(ic_beta[2], 3), "]"
)

ggplot(
  df_puros,
  aes(x = frecuencia_khz, y = signal.to.noise.ratio)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = TRUE) +
  annotate(
    "text",
    x = Inf, y = Inf,
    label = paste0("R² = ", round(r2, 3)),
    hjust = 1.1, vjust = 1.1,
    size = 4
  ) +
  theme_minimal(base_size = 12)  +
  labs(
    x = "Frecuencia aproximada (kHz)",
    y = "SNR",
    title = "SNR según la frecuencia en tonos puros"
  )
`geom_smooth()` using formula = 'y ~ x'


14.3 Blur ratio vs frecuencia

mod <- lm(blur.ratio ~ frecuencia_khz, data = df_puros)

r2 <- summary(mod)$r.squared
r2_adj <- summary(mod)$adj.r.squared
beta <- coef(mod)[2]
ic_beta <- confint(mod)["frecuencia_khz", ]

etiqueta <- paste0(
  "R² = ", round(r2, 3),
  "\nR² ajustado = ", round(r2_adj, 3),
  "\nPendiente = ", round(beta, 3),
  "\nIC95% pendiente = [", round(ic_beta[1], 3), ", ", round(ic_beta[2], 3), "]"
)

ggplot(
  df_puros,
  aes(x = frecuencia_khz, y = blur.ratio)
) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", se = TRUE) +
  annotate(
    "text",
    x = Inf, y = Inf,
    label = paste0("R² = ", round(r2, 3)),
    hjust = 1.1, vjust = 1.1,
    size = 4
  ) +
  theme_minimal(base_size = 12)  +
  labs(
    x = "Frecuencia aproximada (kHz)",
    y = "blur.ratio",
    title = "Desenfoque según la frecuencia en tonos puros"
  )
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 20 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 20 rows containing missing values or values outside the scale range
(`geom_point()`).

15. Resumen por distancia y tipo de sonido

resumen_metricas <- degrad_df |>
  group_by(tipo_sonido, distance) |>
  summarise(
    n = n(),
    mean_excess_attenuation = mean(excess.attenuation, na.rm = TRUE),
    mean_snr = mean(signal.to.noise.ratio, na.rm = TRUE),
    mean_blur = mean(blur.ratio, na.rm = TRUE),
    .groups = "drop"
  )

resumen_metricas
# A tibble: 12 × 6
   tipo_sonido    distance     n mean_excess_attenuation mean_snr mean_blur
   <chr>             <dbl> <int>                   <dbl>    <dbl>     <dbl>
 1 Sonidos tipo s        5     3                  NaN        7.25   NaN    
 2 Sonidos tipo s       10     3                   -6.69     8.52     0.253
 3 Sonidos tipo s       15     3                   -1.51     6.30     0.242
 4 Sonidos tipo s       20     3                   -3.56     5.10     0.215
 5 Sonidos tipo s       25     3                   -2.39     4.51     0.331
 6 Sonidos tipo s       30     3                   -2.77     2.50     0.264
 7 Tonos puros           5    20                  NaN        6.79   NaN    
 8 Tonos puros          10    20                   -3.94     5.87     0.174
 9 Tonos puros          15    20                   -2.21     6.23     0.215
10 Tonos puros          20    20                   -3.54     6.23     0.202
11 Tonos puros          25    20                   -2.29     7.96     0.217
12 Tonos puros          30    20                   -1.94     6.27     0.230

16. Guardar resultados

16.1 Crear carpeta de resultados

dir.create(file.path(proyecto, "resultados"), showWarnings = FALSE)

16.2 Guardar tabla final

write.csv(
  degrad_df,
  file.path(proyecto, paste0("resultados/degradacion_", grabador, ".csv")),
  row.names = FALSE
)

17. Preguntas para discutir

  • ¿Qué métrica cambia más claramente con la distancia?
  • ¿Los sonidos tipo s se degradan igual que los tonos puros?
  • ¿Hay una relación visible entre frecuencia y atenuación?
  • ¿Qué sonidos mantienen mejor su SNR?
  • ¿Qué limitaciones metodológicas tuvo nuestro análisis?
  • ¿Qué parte del flujo fue más difícil: detección de marcador, alineamiento o cálculo de métricas?