Ir al contenido

Sonoridad de programa y pico verdadero (BS.1770 / R 128)

Normas aplicables: Recomendación UIT-R BS.1770EBU R 128EBU Tech 3341EBU Tech 3342EBU Tech 3343Referencias: Steinmetz y Reiss 2021

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.

Diagrama de bloques de la cadena de sonoridad de programa de BS.1770 y EBU R 128: el programa ponderado por canal, anclado para que una sinusoide de 997 hercios a plena escala en un canal frontal marque menos 3,01 LKFS, pasa la ponderación K de realce de cabeza esférica de más 4 decibelios y paso alto RLB, se mide en bloques de media cuadrática de 400 milisegundos con 75 por ciento de solape, se filtra con la puerta absoluta de menos 70 LUFS y la relativa 10 unidades de sonoridad bajo los supervivientes, menos 39,0 LUFS en el ejemplo de 10 segundos de programa más 30 segundos de silencio, y da una sonoridad integrada de menos 23,1 LUFS frente al objetivo de menos 23,0; sendas ramas muestran el rango de sonoridad de 10,0 unidades en el caso de dos escalones de Tech 3342 y el pico verdadero de más 0,12 dBTP donde el pico muestral marca menos 3,01 decibelios, bajo el techo de producción de menos 1 dBTPDiagrama de bloques de la cadena de sonoridad de programa de BS.1770 y EBU R 128: el programa ponderado por canal, anclado para que una sinusoide de 997 hercios a plena escala en un canal frontal marque menos 3,01 LKFS, pasa la ponderación K de realce de cabeza esférica de más 4 decibelios y paso alto RLB, se mide en bloques de media cuadrática de 400 milisegundos con 75 por ciento de solape, se filtra con la puerta absoluta de menos 70 LUFS y la relativa 10 unidades de sonoridad bajo los supervivientes, menos 39,0 LUFS en el ejemplo de 10 segundos de programa más 30 segundos de silencio, y da una sonoridad integrada de menos 23,1 LUFS frente al objetivo de menos 23,0; sendas ramas muestran el rango de sonoridad de 10,0 unidades en el caso de dos escalones de Tech 3342 y el pico verdadero de más 0,12 dBTP donde el pico muestral marca menos 3,01 decibelios, bajo el techo de producción de menos 1 dBTP

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

Sección titulada «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 (Fórmula 2):

donde la constante cancela la ganancia de la ponderación K a 997 Hz y 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.

import numpy as np
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

La Fórmula 2 suma 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 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, ) 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):

import numpy as np
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:

Respuesta en frecuencia de magnitud de la ponderación K en un eje de frecuencia logarítmico: la curva azul combinada cae por debajo de unos pocos cientos de hercios a través del paso alto RLB y sube hasta una meseta de +4 dB por encima de 2 kHz fijada por el realce de cabeza esférica, con las dos etapas dibujadas como curvas acompañantes tenuesRespuesta en frecuencia de magnitud de la ponderación K en un eje de frecuencia logarítmico: la curva azul combinada cae por debajo de unos pocos cientos de hercios a través del paso alto RLB y sube hasta una meseta de +4 dB por encima de 2 kHz fijada por el realce de cabeza esférica, con las dos etapas dibujadas como curvas acompañantes tenues

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 es lo que hace que las dos juntas marquen exactamente −3,01 LKFS con una sinusoide de 997 Hz a plena escala.

Mostrar el código de esta figura
import matplotlib.pyplot as plt
from phonometry import broadcast
broadcast.k_weighting_response(48000).plot(language="es")
plt.show()

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 , 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), que da sonios y un patrón de sonoridad específica en lugar de un único número con puertas.

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:

import numpy as np
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:

Sesenta segundos de un programa de cinco secciones se miden en 597 bloques de puerta, cada uno dibujado como un cuadrado macizo mientras cuenta y hueco en cuanto la puerta lo excluye. El umbral relativo a trazos se recalcula con los supervivientes después de cada bloque: empieza bajo, así que cuentan todos los bloques, y sube a medida que llega material más fuerte hasta asentarse en -34,3 LUFS, momento en el que bloques que contaban antes se han quedado huecos por detrás. El histograma que acompaña a la traza reparte esos mismos bloques en intervalos de sonoridad, y sus barras se vuelven grises por debajo del umbral deslizante. Cuatro lecturas se asientan: una sonoridad integrada de -23,0 LUFS frente a una media energética sin puerta de -24,3 LUFS, una diferencia de 1,28 LU, y 154 de los 597 bloques que no cuentan nunca. Un tramo final aplica la puerta más profunda del rango de sonoridad en -44,1 LUFS y desliza los bordes de los percentiles 10 y 95 hasta -36,8 y -19,1 LUFS, para un rango de sonoridad de 17,7 LU.

Descargar la animación (WebM)

La puerta es una decisión que se toma bloque a bloque, no un filtro sobre la señal. Como el umbral relativo se deriva de los supervivientes de la primera pasada, depende de los datos y es retroactivo: la entrada silenciosa y el desvanecimiento acaban excluidos no porque cayeran por debajo de un nivel fijo, sino porque el resto del programa resultó ser lo bastante fuerte como para subir el umbral por encima de ellos.

Sesenta segundos de un programa de cinco secciones se miden en 597 bloques de puerta, cada uno dibujado como un cuadrado macizo mientras cuenta y hueco en cuanto la puerta lo excluye. El umbral relativo a trazos se recalcula con los supervivientes después de cada bloque: empieza bajo, así que cuentan todos los bloques, y sube a medida que llega material más fuerte hasta asentarse en -34,3 LUFS, momento en el que bloques que contaban antes se han quedado huecos por detrás. El histograma que acompaña a la traza reparte esos mismos bloques en intervalos de sonoridad, y sus barras se vuelven grises por debajo del umbral deslizante. Cuatro lecturas se asientan: una sonoridad integrada de -23,0 LUFS frente a una media energética sin puerta de -24,3 LUFS, una diferencia de 1,28 LU, y 154 de los 597 bloques que no cuentan nunca. Un tramo final aplica la puerta más profunda del rango de sonoridad en -44,1 LUFS y desliza los bordes de los percentiles 10 y 95 hasta -36,8 y -19,1 LUFS, para un rango de sonoridad de 17,7 LU.

Descargar la animación (WebM)

La puerta es una decisión que se toma bloque a bloque, no un filtro sobre la señal. Como el umbral relativo se deriva de los supervivientes de la primera pasada, depende de los datos y es retroactivo: la entrada silenciosa y el desvanecimiento acaban excluidos no porque cayeran por debajo de un nivel fijo, sino porque el resto del programa resultó ser lo bastante fuerte como para subir el umbral por encima de ellos.

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

Medición EBU R 128 de 20 segundos de programa sobre el objetivo de -23 LUFS seguidos de 40 segundos de ambiente silencioso unos 29 LU más bajo: las trazas momentánea y de corto plazo bajan un escalón a los 20 segundos, la línea integrada discontinua se mantiene en -23,0 LUFS porque la puerta relativa descarta la cola, y una línea de trazo y punto marca la media sin puerta hundiéndose hasta -27,7 LUFSMedición EBU R 128 de 20 segundos de programa sobre el objetivo de -23 LUFS seguidos de 40 segundos de ambiente silencioso unos 29 LU más bajo: las trazas momentánea y de corto plazo bajan un escalón a los 20 segundos, la línea integrada discontinua se mantiene en -23,0 LUFS porque la puerta relativa descarta la cola, y una línea de trazo y punto marca la media sin puerta hundiéndose hasta -27,7 LUFS

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.

Mostrar el código de esta figura
import matplotlib.pyplot as plt
import numpy as np
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()

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

Sección titulada «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:

import numpy as np
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:

Medición EBU R 128 de un programa sintético de un minuto con secciones de ambiente, diálogo, música y fundido: la sonoridad momentánea gris respira alrededor de la traza azul de corto plazo, la sonoridad integrada discontinua roja cae exactamente sobre el objetivo de -23 LUFS y una banda sombreada marca el rango de sonoridad entre sus percentiles 10 y 95Medición EBU R 128 de un programa sintético de un minuto con secciones de ambiente, diálogo, música y fundido: la sonoridad momentánea gris respira alrededor de la traza azul de corto plazo, la sonoridad integrada discontinua roja cae exactamente sobre el objetivo de -23 LUFS y una banda sombreada marca el rango de sonoridad entre sus percentiles 10 y 95

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.

Mostrar el código de esta figura
import matplotlib.pyplot as plt
import numpy as np
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()

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:

import numpy as np
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)
Medición EBU R 128 del caso de referencia de Tech 3342 de 20 segundos a -20 dBFS seguidos de 20 segundos a -30 dBFS: la traza de corto plazo escalona entre dos mesetas separadas 10 LU, la banda sombreada del rango de sonoridad abarca exactamente esas mesetas para un LRA igual a 10,0 LU, y la línea integrada queda entre ambasMedición EBU R 128 del caso de referencia de Tech 3342 de 20 segundos a -20 dBFS seguidos de 20 segundos a -30 dBFS: la traza de corto plazo escalona entre dos mesetas separadas 10 LU, la banda sombreada del rango de sonoridad abarca exactamente esas mesetas para un LRA igual a 10,0 LU, y la línea integrada queda entre ambas

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 ; 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).

Mostrar el código de esta figura
import matplotlib.pyplot as plt
import numpy as np
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()

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.

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 con la fase desafortunada. El peor caso está acotado por , donde es la razón de sobremuestreo y es la frecuencia del tono normalizada a la de muestreo — que es exactamente por lo que es el peor caso, ya que 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):

import numpy as np
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).

Dos paneles. A la izquierda, doce muestras del tono de 12 kHz a plena escala con un desfase de un cuarto de ciclo: cada punto de muestra se sitúa en 0,707 de la escala completa sobre la línea discontinua de pico muestral en menos 3,01 dBFS, mientras que la reconstrucción de banda limitada trazada a través de ellos toca más y menos uno, y la rejilla sobremuestreada 4 veces se marca en la parte inferior. A la derecha, la infravaloración de peor caso 20 log cos de pi f_norm entre n frente a la frecuencia normalizada para razones de sobremuestreo de 1, 2, 4 y 8, con el mínimo de 4 que pide BS.1770 marcado en f_norm igual a un cuarto, donde quedan menos 0,17 decibeliosDos paneles. A la izquierda, doce muestras del tono de 12 kHz a plena escala con un desfase de un cuarto de ciclo: cada punto de muestra se sitúa en 0,707 de la escala completa sobre la línea discontinua de pico muestral en menos 3,01 dBFS, mientras que la reconstrucción de banda limitada trazada a través de ellos toca más y menos uno, y la rejilla sobremuestreada 4 veces se marca en la parte inferior. A la derecha, la infravaloración de peor caso 20 log cos de pi f_norm entre n frente a la frecuencia normalizada para razones de sobremuestreo de 1, 2, 4 y 8, con el mínimo de 4 que pide BS.1770 marcado en f_norm igual a un cuarto, donde quedan menos 0,17 decibelios

Izquierda: el mecanismo. Muestrear un tono a a plena escala con la fase equivocada deja todas las muestras en , 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.

Mostrar el código de esta figura
import matplotlib.pyplot as plt
# 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()

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.

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 (, ), 1,0 en el resto:

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:

Un mapa de la ponderación de canal del Anexo 3 de BS.1770 sobre el acimut de menos 180 a 180 grados y la elevación de menos 90 a 90 grados. Dos rectángulos sombreados entre 60 y 120 grados de acimut a cada lado y dentro de más menos 30 grados de elevación llevan la ponderación 1,41, más 1,5 decibelios; todo lo demás vale 1,0. Los altavoces L, R y C de un 5.1 quedan en la ponderación 1,00, Ls y Rs a 110 grados caen dentro de las zonas sombreadas en 1,41, y el altavoz de la capa superior U más 110 a 45 grados de elevación queda fuera, en 1,00Un mapa de la ponderación de canal del Anexo 3 de BS.1770 sobre el acimut de menos 180 a 180 grados y la elevación de menos 90 a 90 grados. Dos rectángulos sombreados entre 60 y 120 grados de acimut a cada lado y dentro de más menos 30 grados de elevación llevan la ponderación 1,41, más 1,5 decibelios; todo lo demás vale 1,0. Los altavoces L, R y C de un 5.1 quedan en la ponderación 1,00, Ls y Rs a 110 grados caen dentro de las zonas sombreadas en 1,41, y el altavoz de la capa superior U más 110 a 45 grados de elevación queda fuera, en 1,00

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.

Mostrar el código de esta figura
import matplotlib.pyplot as plt
# 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()

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.

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:

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.

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.

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").

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.

Informe de ejemplo de sonoridad de programa EBU R 128 (PDF)

Ficha de conformidad de sonoridad de programa de una página: un bloque de metadatos, una tabla de conformidad de cuatro columnas con la sonoridad integrada y el pico verdadero máximo como veredicto y el rango de sonoridad y los máximos momentáneo/corto plazo como filas informativas, la gráfica de sonoridad frente al tiempo a todo lo ancho, el número único enmarcado I = −23,0 LUFS (LRA = 10,0 LU, TP máx = −20,4 dBTP) y un veredicto CUMPLE frente al objetivo de −23,0 LUFS con la tolerancia de control de calidad por defecto de ±0,2 LU del punto i) de EBU R 128.

Descargar el informe (PDF)

Ficha de conformidad de sonoridad de programa (ProgramLoudnessResult.report), I en LUFS con LRA en LU y pico verdadero en dBTP.

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 es un contraste útil con grabaciones reales; no se usó como fuente de esta implementación.

  • Cubierto

    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.

  • No cubierto

    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).