<!-- canonical: https://jmrplens.github.io/phonometry/es/devices/broadcast/program-loudness/ -->
Source: https://jmrplens.github.io/phonometry/es/devices/broadcast/program-loudness/

La **Rec. UIT-R BS.1770-5** define cómo miden la sonoridad de un programa la
radiodifusión y el streaming: **ponderación K**, potencia cuadrática media en
**bloques de 400 ms con puertas** y suma ponderada por canal, expresada en
**LKFS/LUFS**. **EBU R 128** construye encima la práctica de normalización
(todo programa se nivela a **−23,0 LUFS** con un techo de pico verdadero de
**−1 dBTP**), y sus documentos compañeros EBU Tech 3341 y Tech 3342 añaden el
medidor en *modo EBU* (sonoridad momentánea, de corto plazo e integrada) y el
*rango de sonoridad* (LRA). phonometry implementa la cadena completa en el
espacio de nombres `broadcast` y valida cada señal de prueba EBU
sintetizable con su tolerancia oficial.

Toda la Recomendación es una cadena de medición, y cada sección de abajo
detalla uno de sus bloques. El diagrama la despliega primero, con los
números de los ejemplos de esta página.

## 1. La ponderación K y la medida de sonoridad (Anexo 1)

La señal atraviesa primero un prefiltro de dos etapas: un realce de agudos
de ~+4 dB que modela la cabeza como una esfera rígida y después el paso alto
RLB. Su concatenación es la **ponderación K**. La sonoridad en un intervalo
es la suma ponderada por canal de las potencias cuadráticas medias $z_i$
(Fórmula 2):

$$
L_\mathrm{K} = -0{,}691 + 10 \log_{10} \sum_i G_i\, z_i \quad \text{LKFS},
$$

donde la constante cancela la ganancia de la ponderación K a 997 Hz y $G_i$
pondera cada canal (1,0 los canales frontales, 1,41 los envolventes, LFE
excluido, Tabla 3). La Recomendación ancla la escala: una sinusoide de 997 Hz a
0 dB FS en un canal frontal marca −3,01 LKFS. La unidad se escribe LKFS en
la UIT y LUFS en la EBU; son idénticas, y 1 LU es 1 dB.

```python

from phonometry import broadcast

fs = 48000
t = np.arange(20 * fs) / fs
x = np.zeros((5, t.size))                     # L, R, C, Ls, Rs
x[0] = np.sin(2 * np.pi * 997.0 * t)          # 0 dB FS en el canal izquierdo
print(round(broadcast.integrated_loudness(x, fs), 2))   # -3.01  LKFS

# La misma forma de onda en dos canales en vez de en uno (ver más abajo):
mono = np.sin(2 * np.pi * 997.0 * t)
print(round(broadcast.integrated_loudness(mono, fs), 2))               # -3.01
print(round(broadcast.integrated_loudness(np.vstack([mono, mono]), fs), 2))  # 0.00
```

### Los canales se suman, no se promedian

La Fórmula 2 suma $G_i z_i$ sobre los canales, así que el número de canales
forma parte de la medida: la misma forma de onda llevada en dos canales es
exactamente $10\log_{10} 2 = 3,01$ LU más sonora que en uno. El anclaje de
arriba lo enseña — esa sinusoide de 997 Hz a plena escala marca −3,01 LKFS en un
solo canal y 0,00 LKFS como mono dual, que es lo que imprimen las dos últimas
líneas del fragmento anterior.

Se mide el formato de entrega, no un stem. Un pódcast mono entregado como un
canal cumple a −23 LUFS siendo un canal; duplicarlo a estéreo antes de medir
hace que marque −20 LUFS e invita a una corrección de 3 dB en el sentido
equivocado. La misma aritmética explica por qué se excluye el LFE (Tabla 3,
$G = 0$) y por qué las ponderaciones de los envolventes son 1,41: la suma es
una suma de sonoridad sobre la disposición de reproducción, de modo que
cambiar la disposición cambia el número legítimamente. El `np.vstack` de los
fragmentos siguientes es exactamente esto — mono dual, la entrega estéreo de
una fuente mono — y es la razón de que marquen 3,01 LU por encima del valor
de un solo canal de la misma forma de onda.

Los coeficientes de los biquads están tabulados a 48 kHz (Tablas 1-2) y a esa
frecuencia de muestreo se devuelven literalmente; a cualquier otra se rederivan
a través del prototipo analógico para que la respuesta coincida con la
especificación (dentro de 0,02 dB a 32 kHz y por encima; las frecuencias de
muestreo por debajo de 16 kHz se rechazan):

```python

from phonometry import broadcast

(b1, a1), (b2, a2) = broadcast.k_weighting_coefficients(48000)
print(b1)   # [ 1.53512486 -2.69169619  1.19839281]  (Tabla 1, literal)
y = broadcast.k_weighting(np.random.default_rng(0).standard_normal(48000),
                          48000)              # la propia señal filtrada
```

`k_weighting_response` evalúa esos mismos biquads como función de
transferencia y devuelve un `KWeightingResponse` inmutable que lleva la
magnitud combinada (`magnitude_db`) y las dos etapas (`shelf_db`,
`highpass_db`) sobre una rejilla de frecuencia logarítmica; su `.plot()`
dibuja la respuesta, con el realce de cabeza esférica de +4 dB y la caída
del paso alto RLB:

*Las dos etapas, y toda la dependencia con la frecuencia que hay en BS.1770. El
paso alto RLB elimina la energía por debajo de 100 Hz que el oído no integra en
la sonoridad, y el realce de cabeza esférica de +4 dB por encima de unos 2 kHz
hace las veces de la ganancia por difracción de una cabeza en un campo sonoro.
La constante de −0,691 de $L_\mathrm{K}$ es lo que hace que las dos juntas marquen
exactamente −3,01 LKFS con una sinusoide de 997 Hz a plena escala.*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

from phonometry import broadcast

broadcast.k_weighting_response(48000).plot(language="es")
plt.show()
```

</details>

Ese es todo el modelo: dos biquads y una media cuadrática. Conviene decir con
claridad qué no puede hacer una medida así, porque la pregunta con la que llega
el lector suele ser «¿es esta *la* sonoridad?». La cadena es lineal e
independiente del nivel por construcción — duplicar la ganancia de un programa
suma exactamente 6 dB a $L_\mathrm{K}$, que es justo lo que convierte la normalización en
una sola multiplicación, y justo lo que un oído real no hace. No hay
enmascaramiento, ni suma por bandas críticas, ni curvas isofónicas, ni suma
binaural en ninguna parte. Así que BS.1770 ordena con fiabilidad material
*parecido*, que es lo que necesita la entrega en radiodifusión, y se sabe que es
menos fiable entre espectros muy distintos — música con mucho grave frente a
diálogo es el ejemplo de manual, y la razón de que exista el paso alto RLB.
Cuando la pregunta es de verdad la magnitud percibida y no el nivel de entrega,
los modelos psicoacústicos están en
[Sonoridad (ISO 532)](/phonometry/es/perception/psychoacoustics/loudness/), que da sonios y
un patrón de sonoridad específica en lugar de un único número con puertas.

## 2. Las puertas y la sonoridad de programa

La sonoridad **integrada** (de programa) divide la medición en bloques de
400 ms con solape del 75 % y les aplica dos puertas (Fórmulas 3-7): los
bloques por debajo del umbral absoluto de **−70 LKFS** se descartan; la
sonoridad de los supervivientes menos 10 LU fija el **umbral relativo**, y
los bloques por encima de ambas puertas definen el resultado. Las puertas
evitan que los pasajes silenciosos largos (ambientes, pausas, colas de
aplausos) arrastren hacia abajo el nivel del primer plano:

```python

from phonometry import broadcast

fs = 48000
def tono(nivel_dbfs, segundos):
    t = np.arange(int(segundos * fs)) / fs
    return 10 ** (nivel_dbfs / 20) * np.sin(2 * np.pi * 1000.0 * t)

# 10 s de programa a -23 dBFS seguidos de 30 s de ambiente silencioso.
x = np.concatenate([tono(-23.0, 10.0), tono(-50.0, 30.0)])
res = broadcast.program_loudness(np.vstack([x, x]), fs)
print(round(res.integrated, 1))            # -23.1  LUFS (la cola se descarta)
print(round(res.relative_threshold, 1))    # -39.0  LUFS
res.plot()   # la traza de sonoridad: la línea integrada ignora la cola (necesita matplotlib)
```

Una media sin puerta sobre los mismos 40 s quedaría cerca de −29 LUFS: las
puertas son lo que hace que los programas de gran rango de sonoridad casen
en antena. EBU R 128 normaliza este valor integrado a **−23,0 LUFS**; cuando
el objetivo no es alcanzable en la práctica (programas en directo, por
ejemplo) se permite una tolerancia de ±1,0 LU, y los flujos de control de
calidad admiten ±0,2 LU por error de medida.

Las puertas fijan además un suelo por debajo del cual no hay nada que medir. La
sonoridad integrada necesita **al menos un bloque de puerta de 400 ms por encima
de la puerta absoluta de −70 LUFS**; por debajo de eso — un elemento más corto
que un bloque, o silencio digital de principio a fin — `integrated_loudness`
devuelve `-inf`, que es una respuesta de *medida inexistente* y no un programa
muy silencioso. Justo por encima de ese suelo la respuesta es débil, no
inexistente: un elemento de unos pocos segundos deja apenas un puñado de bloques
supervivientes, así que el valor integrado hereda su dispersión, y por eso las
especificaciones de entrega de piezas breves suelen normalizar por el máximo
momentáneo o de corto plazo en vez de por I. (Las muestras no finitas se
rechazan directamente, no se filtran con las puertas).

El orden de esas dos pasadas es lo que hace difícil imaginarse la puerta a
partir de una traza ya terminada. El umbral relativo no es una constante que el
medidor conozca de antemano: se calcula con los bloques que han sobrevivido a
la puerta absoluta, así que solo existe cuando hay material con el que
calcularlo, y sigue moviéndose mientras suena el programa. Un bloque que
contaba al principio puede, por tanto, dejar de contar más adelante sin que
nada de ese bloque haya cambiado. El clip ejecuta la decisión bloque a bloque
sobre un programa más fuerte, de cinco secciones, para poder ver deslizarse el
umbral:

La figura hace visible la puerta sobre un programa de ruido conformado con una
cola silenciosa larga:

*La puerta relativa (10 LU por debajo de los supervivientes) descarta todos
los bloques de la cola, así que la sonoridad integrada mantiene el primer
plano en −23,0 LUFS mientras la media energética sin puerta se hunde hacia
−27,7 LUFS — y seguiría hundiéndose con cada minuto extra de ambiente. Sin la
puerta, los pasajes silenciosos dejarían al primer plano de una mezcla de cine
muy por encima del objetivo.*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

from scipy import signal
from phonometry import broadcast

fs = 48000
rng = np.random.default_rng(3341)
sos = signal.butter(2, 2000.0, fs=fs, output="sos")
trozos = []
# 20 s de material de programa y 40 s de ambiente de sala ~29 LU más bajo.
for nivel, segundos in [(-23.0, 20.0), (-52.0, 40.0)]:
    ruido = signal.sosfilt(sos, rng.standard_normal(int(segundos * fs)))
    ruido /= np.sqrt(np.mean(ruido ** 2))
    trozos.append(10 ** (nivel / 20) * ruido)
x = np.concatenate(trozos)
# Normalizar la sonoridad del programa al objetivo de R 128 y medirlo.
x *= 10 ** ((-23.0 - broadcast.integrated_loudness(np.vstack([x, x]), fs)) / 20)
res = broadcast.program_loudness(np.vstack([x, x]), fs)

ax = res.plot(language="es")
finita = res.momentary[np.isfinite(res.momentary)]
sin_puerta = 10 * np.log10(np.mean(10 ** (finita / 10)))
ax.axhline(sin_puerta, ls="-.", color="#2ca02c",
           label=f"Media sin puerta {sin_puerta:.1f} LUFS")
ax.legend(loc="center right")
plt.show()
```

</details>

## 3. Modo EBU: momentánea, corto plazo, integrada

EBU Tech 3341 define las tres escalas temporales de un medidor conforme, y
una sola llamada las calcula todas:

* **Momentánea (M)**: ventana deslizante de 400 ms, sin puertas;
* **Corto plazo (S)**: ventana deslizante de 3 s, sin puertas;
* **Integrada (I)**: la sonoridad de programa con puertas anterior,

más **Max M** y **Max S**, el pico verdadero y el LRA:

```python

from phonometry import broadcast

fs = 48000
def tono(nivel_dbfs, segundos):
    t = np.arange(int(segundos * fs)) / fs
    return 10 ** (nivel_dbfs / 20) * np.sin(2 * np.pi * 1000.0 * t)

# Caso de prueba 3 de EBU Tech 3341: escalones de -36 / -23 / -36 dBFS.
x = np.concatenate([tono(-36.0, 10.0), tono(-23.0, 60.0), tono(-36.0, 10.0)])
res = broadcast.program_loudness(np.vstack([x, x]), fs)
print(round(res.integrated, 1), round(res.max_momentary, 1),
      round(res.max_short_term, 1))         # -23.0 -23.0 -23.0
res.plot()   # trazas M/S, línea integrada y banda de LRA (necesita matplotlib)
```

El `ProgramLoudnessResult` congelado lleva las series M y S con sus ejes de
tiempo, los máximos, los umbrales, el LRA con sus bordes percentiles, los
picos verdaderos por canal y las ponderaciones de canal; su `.plot()` dibuja
la traza de sonoridad del programa:

*Un mismo programa sintético a través de las tres escalas temporales: ambiente
silencioso, después diálogo, después música y un fundido de salida. La traza
momentánea (400 ms) respira con cada sílaba; la de corto plazo (3 s) es la que
sigue un operador, y se separa de M allí donde el material es denso o transitorio
en lugar de estacionario. La línea integrada es plana por definición — es un solo
número para todo el programa — y la banda sombreada es la separación entre P10 y
P95 que declara el rango de sonoridad.*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

from scipy import signal
from phonometry import broadcast

fs = 48000
rng = np.random.default_rng(1770)
sos = signal.butter(2, 2000.0, fs=fs, output="sos")
trozos = []
for nivel, segundos in [(-38, 8), (-23, 16), (-17, 12), (-25, 16), (-45, 8)]:
    ruido = signal.sosfilt(sos, rng.standard_normal(int(segundos * fs)))
    ruido /= np.sqrt(np.mean(ruido ** 2))
    t = np.arange(ruido.size) / fs
    vaiven = 1 + 0.22 * np.sin(2 * np.pi * 0.9 * t) \
        + 0.14 * np.sin(2 * np.pi * 2.83 * t + 1.0)
    trozos.append(10 ** (nivel / 20) * ruido * vaiven)
x = np.concatenate(trozos)

# Normalizar el programa al objetivo de R 128 y medirlo.
ganancia = -23.0 - broadcast.integrated_loudness(np.vstack([x, x]), fs)
x *= 10 ** (ganancia / 20)
broadcast.program_loudness(np.vstack([x, x]), fs).plot(language="es")
plt.show()
```

</details>

## 4. Rango de sonoridad (EBU Tech 3342)

El **rango de sonoridad** cuantifica cuánto varía la sonoridad en una escala
temporal macroscópica, en LU. Es la separación entre los **percentiles 10 y
95** de la distribución de sonoridad de corto plazo tras una puerta en
cascada: un umbral absoluto de −70 LUFS y después un umbral relativo
**−20 LU** por debajo del nivel de lo que sobrevivió (deliberadamente más
profundo que los −10 LU de la medida integrada, para que el primer plano
silencioso pero real siga contando). Los percentiles impiden que un disparo
aislado o un fundido inflen el valor:

```python

from phonometry import broadcast

fs = 48000
def tono(nivel_dbfs, segundos):
    t = np.arange(int(segundos * fs)) / fs
    return 10 ** (nivel_dbfs / 20) * np.sin(2 * np.pi * 1000.0 * t)

# Caso de prueba 1 de EBU Tech 3342: 20 s a -20 dBFS y 20 s a -30 dBFS.
x = np.concatenate([tono(-20.0, 20.0), tono(-30.0, 20.0)])
res = broadcast.program_loudness(np.vstack([x, x]), fs)
print(round(res.loudness_range, 1))         # 10.0  LU
res.plot()   # la banda sombreada de LRA abarca la separación P10-P95 (necesita matplotlib)
```

*En el caso de referencia de Tech 3342 la distribución de corto plazo tiene
dos mesetas separadas 10 LU, y la banda sombreada entre los percentiles 10 y
95 marca exactamente $\mathrm{LRA} = 10{,}0\ \text{LU}$; la sonoridad integrada
se asienta entre las mesetas. En programas reales, la misma banda distingue de
un vistazo un drama normalizado al diálogo (LRA en torno a 10-20 LU) de un
anuncio comprimido (unos pocos LU).*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

from phonometry import broadcast

fs = 48000
def tono(nivel_dbfs, segundos):
    t = np.arange(int(segundos * fs)) / fs
    return 10 ** (nivel_dbfs / 20) * np.sin(2 * np.pi * 1000.0 * t)

# Caso de prueba 1 de EBU Tech 3342: 20 s a -20 dBFS y 20 s a -30 dBFS.
x = np.concatenate([tono(-20.0, 20.0), tono(-30.0, 20.0)])
res = broadcast.program_loudness(np.vstack([x, x]), fs)
res.plot(language="es")   # la banda de LRA abarca exactamente los 10 LU entre mesetas
plt.show()
```

</details>

`loudness_range()` también está disponible de forma independiente sobre
cualquier vector de sonoridad de corto plazo, siguiendo la implementación de
referencia de Tech 3342 (incluida su indexación de percentiles por rango más
cercano). La EBU no recomienda el LRA para programas de menos de un minuto:
demasiado pocas ventanas de 3 s.

## 5. Pico verdadero (Anexo 2)

Los picos por muestra engañan: el máximo verdadero de la forma de onda
reconstruida cae en general *entre* muestras, y un medidor de pico por
muestra infravalora 3 dB un tono a $f_\mathrm{s}/4$ con la fase desafortunada. El peor
caso está acotado por $20\log_{10}\cos(\pi f_{\mathrm{norm}}/n)$, donde $n$ es
la razón de sobremuestreo y $f_{\mathrm{norm}} = f/f_\mathrm{s}$ es la frecuencia del tono
normalizada a la de muestreo — que es exactamente por lo que $f_\mathrm{s}/4$ es el peor
caso, ya que $f_{\mathrm{norm}} = 1/4$ maximiza la cota en la banda de audio.
Por eso el Anexo 2 de BS.1770-5 mide el **pico
verdadero** sobre la señal sobremuestreada hasta al menos 192 kHz (4× a
48 kHz), en **dBTP** (dB respecto al 100 % de la escala completa):

```python

from phonometry import broadcast

fs = 48000
t = np.arange(fs) / fs
# Un tono a fs/4 a plena escala cuyos picos caen justo entre muestras.
x = np.sin(2 * np.pi * (fs / 4) * t + np.pi / 4)
print(round(float(broadcast.true_peak_level(x, fs, oversample=1)), 2))  # -3.01
print(round(float(broadcast.true_peak_level(x, fs)), 2))                #  0.12
```

El interpolador recupera la excursión entre muestras que la rejilla de
muestreo perdió (el residuo de +0,12 dB es rizado de interpolación de los
bordes abruptos del tono, dentro de la tolerancia de +0,2/−0,4 dB que deben
cumplir los medidores en modo EBU).

*Izquierda: el mecanismo. Muestrear un tono a $f_\mathrm{s}/4$ a plena escala con la fase
equivocada deja **todas** las muestras en $1/\sqrt{2}$, así que un medidor de
pico por muestra marca −3,01 dBFS mientras que la forma de onda que de verdad
saldrá de un convertidor alcanza la escala completa. Derecha: la cota, frente a
la razón de sobremuestreo. Con el 4× que pide BS.1770 aún se pierden 0,17 dB de
la excursión en la frecuencia más desfavorable — que es parte de por qué el techo
es −1 dBTP y no 0.*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

# La propia señal de la página, medida entera y dibujada de doce en doce muestras.
pico_muestral = float(broadcast.true_peak_level(x, fs, oversample=1))
pico_verdadero = float(broadcast.true_peak_level(x, fs))
t_fino = np.linspace(0.0, 11 / fs, 2000)
fino = np.sin(2 * np.pi * (fs / 4) * t_fino + np.pi / 4)

fig, (axi, axd) = plt.subplots(1, 2, figsize=(12, 5))
axi.plot(t_fino * 1000.0, fino, label="reconstrucción de banda limitada")
axi.plot(np.arange(12) / fs * 1000.0, x[:12], "o", label="muestras a 48 kHz")
axi.axhline(np.abs(x[:12]).max(), linestyle="--")   # pico muestral, -3,01 dBFS
axi.axhline(1.0, linestyle="--")                    # la excursión verdadera
axi.set(xlabel="Tiempo [ms]", ylabel="Amplitud [FS]")
axi.legend()

f_norm = np.linspace(0.0, 0.5, 400)
for razon in (1, 2, 4, 8):
    axd.plot(f_norm, 20.0 * np.log10(np.cos(np.pi * f_norm / razon)),
             label=f"n = {razon}")
axd.set(xlabel="Frecuencia del tono / frecuencia de muestreo",
        ylabel="Infravaloración [dB]", ylim=(-7.0, 0.4))
axd.legend()
plt.show()
```

</details>

EBU R 128 limita la producción a
**−1 dBTP**; los códecs de distribución suelen necesitar más margen. Es la
misma maquinaria de pico sobremuestreado que sostiene el `lc_peak` ponderado
C de [Niveles integrados y estadísticos](/phonometry/es/signals/levels/levels/).

## 6. Programas multicanal y el Anexo 3

Con 1, 2, 5 o 6 canales las ponderaciones de la Tabla 3 se aplican solas
(orden de canales `L, R, C, Ls, Rs`, o `L, R, C, LFE, Ls, Rs` con el LFE
excluido). Para cualquier otra disposición de altavoces (22.2, 4+7+0 y el
resto de sistemas de sonido avanzados de BS.2051), el Anexo 3 deriva la
ponderación de cada canal de la posición de su altavoz: 1,41 (+1,5 dB) para
los altavoces laterales de la capa media
($60^\circ \le |\text{acimut}| \le 120^\circ$,
$|\text{elevación}| < 30^\circ$), 1,0 en el resto:

```python
from phonometry import broadcast

print(broadcast.channel_weight(110.0, 0.0))    # 1.41  (M+110, lateral)
print(broadcast.channel_weight(110.0, 35.0))   # 1.0   (U+110, capa superior)
pesos = broadcast.channel_weight([0, 30, -30, 90, -90], [0, 0, 0, 0, 0])
# -> [1. 1. 1. 1.41 1.41]; se pasa como program_loudness(..., weights=pesos)
```

Son dos desigualdades que describen un dibujo, y el dibujo responde a la pregunta
que se hace de verdad quien monta la sala: qué altavoces de *esta* disposición
caen dentro de la zona:

*La ponderación es una propiedad de la **posición** del altavoz, no del nombre
del canal. Ls y Rs de una disposición 5.1 caen dentro de la zona lateral de la
capa media y se llevan +1,5 dB; el mismo acimut elevado a la capa superior, no.
Por eso la Tabla 3 y el Anexo 3 coinciden en 5.1 y se separan en 22.2.*

<details>
<summary>Mostrar el código de esta figura</summary>

```python

# El mapa es la propia función, evaluada sobre la esfera.
acimut, elevacion = np.meshgrid(np.linspace(-180.0, 180.0, 361),
                                np.linspace(-90.0, 90.0, 181))
ponderacion = broadcast.channel_weight(acimut, elevacion)

fig, ax = plt.subplots(figsize=(11, 5.6))
ax.contourf(acimut, elevacion, ponderacion, levels=[1.2, 2.0])
ax.contour(acimut, elevacion, ponderacion, levels=[1.2])
for nombre, a, e in (("L", -30.0, 0.0), ("R", 30.0, 0.0), ("C", 0.0, 0.0),
                     ("Ls", -110.0, 0.0), ("Rs", 110.0, 0.0),
                     ("U+110", 110.0, 45.0)):
    ax.plot([a], [e], "o")
    ax.annotate(f"{nombre}  ({broadcast.channel_weight(a, e):.2f})", (a, e))
ax.set(xlabel="Acimut [°]", ylabel="Elevación [°]")
plt.show()
```

</details>

El audio basado en objetos (Anexo 4) se mide renderizando primero a una
configuración de altavoces y midiendo el renderizado; el renderizado en sí queda
fuera del alcance de esta página.

## 7. Normalizar al objetivo

Medir es la mitad de R 128; la otra mitad es la única ganancia que viene
después, y el orden de las operaciones importa más que la aritmética.

Lo primero, qué espera `program_loudness`: un array `[canales, muestras]` en
unidades de escala completa, donde 1,0 es 0 dBFS. Un `soundfile.read` devuelve
`(muestras, canales)`, así que hay que transponerlo, y el PCM entero hay que
escalarlo antes a ±1. Lo segundo, *qué* se mide: el programa **tal como se
entrega**. El tono de ajuste, la claqueta, la cuenta atrás y el negro forman
parte del fichero y no del programa, así que se recortan antes de medir — un tono
de ajuste de 1 kHz a −18 dBFS atraviesa la puerta absoluta sin problema y arrastra
consigo el valor integrado. Y se mide el programa entero, no un extracto, porque
la puerta relativa se calcula a partir de los propios supervivientes del programa.

Y luego la ganancia. La normalización es una **única ganancia estática** de
(objetivo − I) dB aplicada a todo el programa:

```python
from scipy import signal

# Un programa estéreo terminado, en lugar del máster entregado: tres secciones
# de ruido conformado separadas 13 LU. En la práctica esto es
#   datos, fs = soundfile.read(ruta)     # (muestras, canales), -1,0 a 1,0
#   programa = datos.T                   # program_loudness quiere [canales, muestras]
rng = np.random.default_rng(128)
sos = signal.butter(2, 2000.0, fs=fs, output="sos")
secciones = []
for nivel_dbfs, segundos in [(-31.0, 12.0), (-24.0, 12.0), (-37.0, 12.0)]:
    ruido = signal.sosfilt(sos, rng.standard_normal(int(segundos * fs)))
    secciones.append(10 ** (nivel_dbfs / 20) * ruido / np.sqrt(np.mean(ruido ** 2)))
programa = np.concatenate(secciones)

res = broadcast.program_loudness(np.vstack([programa, programa]), fs)
ganancia_db = -23.0 - res.integrated
print(round(res.integrated, 2), round(ganancia_db, 2))    # -23.86  0.86

normalizado = programa * 10 ** (ganancia_db / 20)
despues = broadcast.program_loudness(np.vstack([normalizado, normalizado]), fs)
print(round(despues.integrated, 2), round(despues.loudness_range, 2))  # -23.0 13.02
print(round(despues.true_peak, 2))                    # -9.96, antes -10.82
print(despues.true_peak <= -1.0)                      # el techo de R 128: True
```

Como la ganancia es lineal y se aplica a todo, desplaza M, S e I exactamente esos
decibelios y deja el **LRA intacto** — el rango de sonoridad es una diferencia de
percentiles, y un desplazamiento constante se cancela en ella. Qué bloques
sobreviven a las puertas tampoco cambia, por la misma razón.

El pico verdadero, en cambio, se mueve con la ganancia, y ese es el único
conflicto que R 128 deja sin resolver. Un programa silencioso y de gran rango que
necesite +8 LU y cuyo pico verdadero máximo ya esté en −6 dBTP acaba en +2 dBTP:
bien normalizado y por encima del techo. El orden es *normalizar y después
comprobar* — nunca reducir la ganancia para que quepa el pico, porque eso rompe
el nivel de entrega del que depende todo lo demás. Las dos soluciones legítimas
son un limitador de pico verdadero aplicado a los momentos más sonoros (que baja
algo el LRA, y hay que declararlo) o renegociar el objetivo de esa entrega.

El registro de entrega son tres números, no uno: la sonoridad integrada **I**, el
rango de sonoridad **LRA** y el pico verdadero máximo **TP máx**. Son exactamente
los tres que enmarca la ficha de abajo.

## 8. Informe de EBU R 128 (`.report()`)

`ProgramLoudnessResult.report(path)` renderiza una ficha PDF de una página con
el formato de una hoja de conformidad de entrega para radiodifusión: la línea
de base normativa, un bloque opcional de metadatos, una tabla de conformidad a todo
lo ancho (`Métrica | Medido | Objetivo / Límite | Resultado`) y, debajo, la
gráfica de sonoridad frente al tiempo a todo lo ancho (el propio `.plot()` del
resultado, con las trazas momentánea y de corto plazo, la línea de la integrada
y la banda del LRA). El veredicto lo determinan **solo** la sonoridad integrada
y el pico verdadero máximo; el rango de sonoridad y los máximos
momentáneo/corto plazo se muestran como filas informativas (una raya en la
columna Resultado, nunca un color de cumple/no cumple). Un número único
enmarcado `I = X LUFS (LRA = Y LU, TP máx = Z dBTP)`, un veredicto combinado
CUMPLE/NO CUMPLE y un pie con el descargo fijo cierran la hoja.

La disposición apilada (tabla de conformidad arriba, gráfica debajo) difiere
del cuerpo estrecho de dos paneles de las demás fichas porque la tabla de
conformidad necesita cuatro columnas y la traza de sonoridad frente al tiempo
es apaisada. Usa el mismo contenedor `ReportMetadata` y el mismo motor de
renderizado que la
[ficha de aislamiento de ISO 717](/phonometry/es/buildings/insulation/insulation-ratings/#informe-de-iso-717-report).

**Qué determina el veredicto.** Un `requirement` proporcionado se interpreta como
la sonoridad de programa objetivo en LUFS (por defecto los −23,0 LUFS de
EBU R 128), y la ficha cumple cuando la sonoridad integrada está dentro de la
tolerancia de R 128 seleccionada *y* el pico verdadero es igual o inferior a
−1,0 dBTP. Nada más vota.

**El selector de tolerancia**, que es el único ajuste que cambia si una entrega
cumple o no. Sigue el argumento `tolerance`: el valor por defecto `"qc"` aplica
el margen de **±0,2 LU** por errores de medida del punto i) de R 128, para flujos
de trabajo de sonoridad como el control de calidad, y `"live"` aplica la
tolerancia de **±1,0 LU** del punto h), permitida solo cuando el nivel objetivo
no es alcanzable en la práctica — un programa en directo, típicamente. La regla
aplicada y su punto de R 128 se imprimen en la ficha, de modo que el lector
siempre puede ver cuál se usó. El veredicto se evalúa después sobre la sonoridad
*redondeada a los 0,1 LU mostrados*, de modo que los números impresos nunca
pueden contradecir al veredicto que tienen al lado.

**Cuestiones prácticas.** El renderizado necesita reportlab y, para la figura que
incrusta la ficha, matplotlib (`pip install "phonometry[report,plot]"`); solo se
admite `engine="reportlab"`. La ficha se genera en inglés por defecto; pasa
`language="es"` para una ficha en español (cadenas fijas traducidas y coma como
separador decimal), p. ej.
`res.report("loudness_fiche_es.pdf", language="es")`.

```python
from phonometry import broadcast, ReportMetadata

res = broadcast.program_loudness(x, fs)   # un programa estéreo terminado
res.report(
    "loudness_fiche.pdf",
    metadata=ReportMetadata(
        specimen="Secuencia de tonos de referencia",
        measurement_standard="EBU R 128",
        laboratory="Laboratorio de referencia Phonometry",
        requirement=-23.0,                # sonoridad de programa objetivo (LUFS)
    ),
)                                          # I (LUFS), LRA (LU), pico verdadero (dBTP)
```

La ficha de ejemplo se regenera con `make reports` y se conserva renderizada en
el repositorio; pulsa la vista previa para abrir el PDF.

## 9. Validación

Cada señal sintetizable de «requisitos mínimos» de EBU Tech 3341 (casos 1-6
y 9-23) y de Tech 3342 (casos 1-4) se ejecuta en la batería de tests con su
tolerancia oficial (±0,1 LU en sonoridad, +0,2/−0,4 dB en pico verdadero,
±1 LU en LRA), junto con el ancla de 997 Hz y la cota de infravaloración en
forma cerrada del Anexo 2, Adjunto 1. Los casos 7-8 y los casos 5-6 del LRA
usan material de programa auténtico distribuido por la EBU y no son
sintetizables; se ejecutan contra el conjunto oficial de pruebas de sonoridad
de la EBU (descargado de la propia EBU, cuya licencia cubre solo pruebas
técnicas, así que el audio nunca se incorpora al repositorio) y los cuatro
pasan dentro de tolerancia; las series de sonoridad por bloques medidas de
esos ficheros se incorporan como datos simples, de modo que las etapas de
puerta y de LRA de estos casos se ejecutan también en todas partes sin el
audio. El medidor independiente
[pyloudnorm](https://github.com/csteinmetz1/pyloudnorm) es un contraste útil
con grabaciones reales; no se usó como fuente de esta implementación.

## Qué cubre esta guía

La Rec. UIT-R BS.1770-5, Anexo 1: el prefiltro de ponderación K de las Tablas
1-2, y la sonoridad integrada ponderada por canal con su puerta en dos etapas
(Fórmulas 1-7, Tabla 3). `k_weighting`, `k_weighting_coefficients` y
`program_loudness` los implementan. El nivel de pico verdadero sobremuestreado
del Anexo 2 se ejecuta mediante `true_peak_level`. Las ponderaciones de canal
dependientes de la posición del Anexo 3 para sistemas de sonido avanzados se
ejecutan mediante `channel_weight`. El objetivo de −23,0 LUFS y el techo de −1
dBTP de EBU R 128 se apoyan en lo anterior. El medidor momentánea/corto
plazo/integrada de EBU Tech 3341 y el rango de sonoridad de EBU Tech 3342
provienen del mismo resultado `program_loudness`, con `loudness_range()`
también disponible de forma independiente.

El Anexo 4 de BS.1770-5, el audio basado en objetos, queda fuera de alcance.
La guía señala que el renderizado a una disposición de altavoces debe ocurrir
antes, y phonometry no implementa ningún renderizador de audio espacial para
ese paso. EBU Tech 3343 se cita solo como práctica de producción alrededor de
estos números: es orientación, no un algoritmo, y nada aquí lo ejecuta.
Tampoco hay una *función auxiliar* de normalización: la sección 7 enseña la
ganancia única, y aplicarla es una multiplicación. Y nada de esta página es un
**modelo** de sonoridad — la ponderación K es un filtro lineal fijo, así que
el enmascaramiento, la dependencia con el nivel, las bandas críticas y la suma
binaural quedan fuera por construcción; eso vive en [Sonoridad (ISO
532)](/phonometry/es/perception/psychoacoustics/loudness/).

## Véase también

- [Sonoridad (ISO 532)](/phonometry/es/perception/psychoacoustics/loudness/): los modelos
  psicoacústicos, en sonios y sonoridad específica, que responden a la pregunta
  por la magnitud percibida que los LUFS con puertas de esta página
  deliberadamente no responden.
- [Niveles integrados y estadísticos](/phonometry/es/signals/levels/levels/):
  $L_\mathrm{eq}$, $L_\mathrm{Ceq}$ y la misma maquinaria de pico sobremuestreado que sostiene
  `lc_peak`.
- [Ponderación frecuencial](/phonometry/es/signals/levels/weighting/): A, C y Z
  frente a la ponderación K de esta página.
- [Electroacústica (IEC 60268-3)](/phonometry/es/devices/electroacoustics/electroacoustics/):
  la cadena que transporta el programa una vez normalizado.
- [Radiodifusión](/phonometry/es/devices/broadcast/): el resumen de la sección.
- Referencia de la API: [`broadcast`](/phonometry/es/reference/api/broadcast/program-loudness/).
