{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bacb34e1",
   "metadata": {},
   "source": [
    "# Calcular un espectrograma con librosa\n",
    "\n",
    "Cuaderno complementario del artículo [*Cómo calcular un espectrograma con\n",
    "librosa*](https://labs.tever.es/blog/espectrograma-con-librosa) en\n",
    "[Tever Labs](https://labs.tever.es).\n",
    "\n",
    "Partimos de un extracto de audio real —un fragmento de\n",
    "[*Fayendo*](https://tever.es/musica#recording-fayendo) (2021), tema del\n",
    "EP *Suañu de Gaita*, grabación propia de gaita asturiana— y recorremos el\n",
    "camino completo: cargar el audio, calcular su transformada de Fourier de\n",
    "tiempo corto (STFT), convertirla a espectrograma en decibelios y\n",
    "dibujarla. Todo con [librosa](https://librosa.org/), la librería de\n",
    "referencia para análisis de audio en Python.\n",
    "\n",
    "**Requisitos:** `librosa`, `numpy`, `matplotlib`. Instálalos con:\n",
    "\n",
    "```bash\n",
    "pip install librosa numpy matplotlib\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ccb42cad",
   "metadata": {},
   "outputs": [],
   "source": [
    "import librosa\n",
    "import librosa.display\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "print(\"librosa\", librosa.__version__)\n",
    "print(\"numpy\", np.__version__)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5144b41c",
   "metadata": {},
   "source": [
    "## 1. Cargar el audio\n",
    "\n",
    "`librosa.load` decodifica el fichero y lo devuelve como un array de numpy\n",
    "(`y`) junto con la frecuencia de muestreo (`sr`, *sample rate*). Por\n",
    "defecto, librosa remuestrea a 22050 Hz — suficiente para el rango de\n",
    "interés en análisis musical y más ligero de procesar.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96a85290",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sustituye por la ruta a tu propio audio, o descarga el original (Fayendo, 2021):\n",
    "# https://labs.tever.es/notebooks/tever-extracto.mp3\n",
    "ruta_audio = \"tever-extracto.mp3\"\n",
    "\n",
    "y, sr = librosa.load(ruta_audio)\n",
    "\n",
    "duracion = librosa.get_duration(y=y, sr=sr)\n",
    "print(f\"Frecuencia de muestreo: {sr} Hz\")\n",
    "print(f\"Duración: {duracion:.1f} s\")\n",
    "print(f\"Muestras totales: {len(y)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55a14ca2",
   "metadata": {},
   "source": [
    "## 2. La forma de onda\n",
    "\n",
    "Antes de transformar nada, veamos qué tenemos: la representación\n",
    "**temporal** del sonido, tal como la explica\n",
    "[*La transformada de Fourier*](https://labs.tever.es/blog/la-transformada-de-fourier).\n",
    "En cada instante, un único valor de presión de aire.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ff81d69",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(12, 3))\n",
    "librosa.display.waveshow(y, sr=sr, ax=ax)\n",
    "ax.set(title=\"Forma de onda — dominio del tiempo\", xlabel=\"Tiempo (s)\", ylabel=\"Amplitud\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a17dbe68",
   "metadata": {},
   "source": [
    "## 3. La transformada de Fourier de tiempo corto (STFT)\n",
    "\n",
    "La forma de onda no dice fácilmente qué notas suenan. Para eso hace falta\n",
    "pasar al dominio de la frecuencia — pero no de golpe sobre todo el audio,\n",
    "sino en ventanas cortas y solapadas que se desplazan en el tiempo. Ese es\n",
    "el truco de la **STFT** (*Short-Time Fourier Transform*): calcular muchas\n",
    "transformadas de Fourier pequeñas, una por ventana, y apilarlas.\n",
    "\n",
    "- `n_fft`: tamaño de cada ventana de análisis, en muestras. 2048 es un\n",
    "  valor habitual: suficiente resolución en frecuencia sin perder\n",
    "  demasiada resolución temporal.\n",
    "- `hop_length`: cuánto se desplaza la ventana entre cálculos sucesivos.\n",
    "  Con solape (`hop_length` < `n_fft`), el espectrograma queda más suave.\n",
    "\n",
    "El resultado, `D`, es una matriz de números **complejos**: cada columna es\n",
    "una ventana temporal, cada fila una frecuencia, y el valor combina\n",
    "magnitud (energía) y fase.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f8aa0d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "n_fft = 2048\n",
    "hop_length = 512\n",
    "\n",
    "D = librosa.stft(y, n_fft=n_fft, hop_length=hop_length)\n",
    "\n",
    "print(f\"Forma de D: {D.shape}  (frecuencias x ventanas temporales)\")\n",
    "print(f\"Tipo de dato: {D.dtype}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81628349",
   "metadata": {},
   "source": [
    "## 4. De magnitud compleja a espectrograma en decibelios\n",
    "\n",
    "Para dibujar el espectrograma solo necesitamos la **magnitud** (cuánta\n",
    "energía hay), no la fase. Y como el oído humano percibe la intensidad de\n",
    "forma logarítmica, se convierte a decibelios con `amplitude_to_db` —así\n",
    "las diferencias pequeñas en zonas de baja energía se ven tan bien como\n",
    "las grandes en zonas de alta energía.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4af7e4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)\n",
    "\n",
    "print(f\"Rango en dB: [{S_db.min():.1f}, {S_db.max():.1f}]\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5336822",
   "metadata": {},
   "source": [
    "## 5. Dibujar el espectrograma\n",
    "\n",
    "`librosa.display.specshow` sabe interpretar los ejes de frecuencia y\n",
    "tiempo directamente a partir de `sr` y `hop_length`. El eje Y en escala\n",
    "logarítmica (`y_axis=\"log\"`) es el habitual en música: refleja mejor cómo\n",
    "se distribuyen las notas en el rango audible que una escala lineal.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d647dcc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(12, 5))\n",
    "img = librosa.display.specshow(\n",
    "    S_db, sr=sr, hop_length=hop_length,\n",
    "    x_axis=\"time\", y_axis=\"log\", ax=ax, cmap=\"magma\",\n",
    ")\n",
    "ax.set(title=\"Espectrograma (STFT, escala logarítmica)\")\n",
    "fig.colorbar(img, ax=ax, format=\"%+2.0f dB\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c427af62",
   "metadata": {},
   "source": [
    "## 6. Bandas horizontales: los armónicos\n",
    "\n",
    "*Fayendo* es una pieza melódica de gaita asturiana, así que en el\n",
    "espectrograma se ven bandas horizontales que suben y bajan juntas: son la\n",
    "frecuencia **fundamental** de cada nota y sus **armónicos** (múltiplos\n",
    "enteros de esa frecuencia). Ese patrón de bandas paralelas, y cómo se\n",
    "reparte la energía entre ellas, es una de las señales que un sistema de\n",
    "[transcripción automática de música](https://labs.tever.es/blog/que-es-amt)\n",
    "usa para decidir qué nota se está tocando.\n",
    "\n",
    "## Siguientes pasos\n",
    "\n",
    "Este cuaderno se queda en el espectrograma como imagen. Para ir más allá:\n",
    "\n",
    "- **Cromagrama** (`librosa.feature.chroma_stft`): agrupa la energía en\n",
    "  las 12 clases de altura (C, C#, D…), útil para detectar acordes o\n",
    "  tonalidad.\n",
    "- **MFCCs** (`librosa.feature.mfcc`): una representación compacta del\n",
    "  timbre, muy usada en clasificación de instrumentos y géneros.\n",
    "- **Detección de onsets** (`librosa.onset.onset_detect`): encontrar el\n",
    "  instante exacto en que empieza cada nota, primer paso hacia una\n",
    "  transcripción automática real.\n",
    "\n",
    "## Licencia y atribución\n",
    "\n",
    "Audio: fragmento de *Fayendo* (2021), tema del EP\n",
    "[*Suañu de Gaita*](https://tever.es/musica#recording-fayendo) de Tever\n",
    "Díaz García. Cuaderno bajo la misma licencia que el código del sitio —\n",
    "ver [/aviso-legal](https://labs.tever.es/aviso-legal).\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
