<!-- canonical: https://jmrplens.github.io/phonometry/es/signals/filters/filter-banks/ -->
Source: https://jmrplens.github.io/phonometry/es/signals/filters/filter-banks/

phonometry ofrece varios tipos de filtro, cada uno con su propia función de
transferencia característica. Butterworth, Chebyshev II y Bessel sitúan sus
**puntos de −3 dB en los bordes de banda de ANSI S1.11**, de modo que sus
niveles por banda son directamente comparables. Los dos diseños de rizado
constante (Chebyshev I, Elíptico) no: toman esos bordes como borde de *rizado*,
lo que ensancha la banda y sesga todos los niveles por banda en unas décimas de
decibelio fijas — ver el apartado 1, porque el sesgo es sistemático y no se
promedia a cero.

Al terminar esta página sabrás diseñar un banco para cualquier fracción $1/b$,
leer todos los argumentos en una sola tabla, encadenar secciones de EQ RBJ,
extraer las señales temporales por banda y filtrar con fase cero. El apartado 1
son las matemáticas sobre las que se apoya el diseño; si solo quieres llamar a
la función, empieza por el apartado 2. En qué se diferencian las cinco
arquitecturas es la
[Galería de arquitecturas de filtro](/phonometry/es/signals/filters/filter-gallery/),
y qué significa «clase 1» para el resultado es
[Verificación de clase de filtros](/phonometry/es/signals/filters/filter-compliance/).

## 1. Bandas de fracción de octava: las matemáticas

IEC 61260-1:2014 construye cada banda a partir de la razón de octava en base 10
$G = 10^{3/10} \approx 1{,}99526$ (es decir, «una octava» *no* es exactamente 2).
Para la fracción de banda $1/b$, las frecuencias centrales y los bordes de
banda siguen (5.2-5.5):

$$
f_\mathrm{m} = 1000 \cdot G^{x/b}\ (b\ \text{impar}), \qquad
f_\mathrm{m} = 1000 \cdot G^{(2x+1)/2b}\ (b\ \text{par}), \qquad
f_1 = f_\mathrm{m} G^{-1/2b}, \quad f_2 = f_\mathrm{m} G^{+1/2b}
$$

de modo que cada banda de tercio de octava abarca
$G^{1/3} \approx 1{,}2589 \approx 10^{1/10}$: diez bandas por década, y por eso
las frecuencias nominales (25, 31,5, 40 …) se repiten escaladas por 10.
phonometry diseña cada banda como una cascada SOS sobre esos bordes, pero dónde
acaba cayendo el punto de −3 dB depende de qué entiende cada arquitectura por su
frecuencia de diseño, y solo tres de las cinco lo ponen en $f_1$ y $f_2$.

Las dos formas existen para que la frecuencia de referencia de 1000 Hz sea una
frecuencia **central** de banda cuando $b$ es impar y un **borde** de banda
cuando $b$ es par. Eso es lo que mantiene anidadas las fracciones impares (cada
banda de octava son exactamente tres bandas de tercio de octava) mientras que
las pares parten justo en las fronteras de octava; las fórmulas de los bordes
son las mismas en ambos casos. La consecuencia práctica es fácil de pisar: un
espectro con `fraction=2`, `12` o `24` no tiene banda de 1 kHz en absoluto, sus
etiquetas nominales salen del redondeo a cifras significativas del Anexo E.3 y
no de la serie preferente de ISO 266, y compararlo banda a banda con una tabla
de tercios de octava no significa nada.

### Dónde cae de verdad el punto de −3 dB de cada arquitectura

La frecuencia de diseño de SciPy para Butterworth *es* su punto de −3 dB, así
que no necesita corrección. Chebyshev II se diseña por sus bordes de banda
eliminada, obtenidos analíticamente desde los bordes de banda, y Bessel se
normaliza con `norm="mag"` en lugar de con la normalización de fase por defecto
de SciPy: las dos correcciones existen para que sus puntos de −3 dB caigan
también en los bordes. A Chebyshev I y al Elíptico se les entregan los bordes de
banda directamente, y para esas dos familias SciPy los lee como borde de la
*banda de paso de rizado constante*: la respuesta ahí es la profundidad del
rizado, no −3 dB. Medido sobre la banda de octava de 1 kHz a 48 kHz con orden 6
y el `ripple=0.1` por defecto:

| Arquitectura | Respuesta en $f_1$ y $f_2$ | Ancho de banda de ruido / $(f_2 - f_1)$ | Desviación del nivel de banda |
| :--- | :---: | :---: | :---: |
| `butter` | −3,01 dB | 1,003 | referencia |
| `cheby2` | −3,01 dB | 1,003 | −0,02 dB |
| `bessel` | −3,01 dB | 0,971 | +0,05 dB |
| `cheby1` | **−0,10 dB** | 1,066 | **+0,33 dB** |
| `ellip` | **−0,10 dB** | 1,052 | **+0,25 dB** |

Las tres primeras columnas son la banda de octava de 1 kHz; la última es la
diferencia mediana de nivel de banda respecto a Butterworth sobre un banco
entero de tercios de octava analizando 20 s de ruido blanco. Esa última columna
importa porque es un *sesgo*, no dispersión: no se reduce con el tiempo de
promediación, es mayor que todo el corredor de banda de paso de clase 1
(±0,4 dB) y mueve todas las bandas a la vez, así que un espectro medido con
`cheby1` queda en bloque por encima de uno medido con `butter`. Conviene elegir
una arquitectura por campaña y no mezclar nunca arquitecturas dentro de un mismo
espectro. Subir el `order` reduce la desviación, porque una banda de transición
más estrecha devuelve el punto de −3 dB hacia el borde de rizado: para `cheby1`
la mediana pasa de +0,76 dB con orden 4 a +0,33 dB con orden 6, +0,16 dB con
orden 8 y +0,04 dB con orden 12. Bajar `design.ripple` hace lo contrario de lo
que sugiere la intuición y hace la desviación *mayor* (+0,62 dB con
`ripple=0.02`), porque a orden fijo una banda de paso más plana se paga con una
caída más lenta.

### Polos, ceros y estabilidad

Un paso banda digital es una constelación de polos y ceros en el plano z: los
ceros en DC y Nyquist, o cerca de ellos, sujetan la respuesta lejos de la
banda (hasta el suelo de la banda atenuada, en los diseños de rizado
constante), y los polos se agrupan justo dentro del círculo unidad en los
ángulos $\omega = 2\pi f / f_\mathrm{s}$ que abarca la banda de paso. De ahí salen dos
intuiciones. Primera, la selectividad es proximidad: cuanto más cerca del
círculo unidad están los polos, más abrupta es la banda y más tiempo resuena
el filtro (los picos de retardo de grupo de la sección 4 son esa resonancia,
medida). Segunda, la estabilidad es un margen, no una propiedad de la
arquitectura: un filtro IIR es estable solo mientras todos sus polos
permanecen estrictamente dentro del círculo unidad, y una banda estrecha a
una frecuencia de muestreo alta los empuja hacia fuera (radio del polo
$\approx 1 - \pi B / f_\mathrm{s}$ para un ancho de banda $B$) y los aprieta entre
sí, hasta que los coeficientes en doble precisión ya no pueden representar
sus posiciones con exactitud. Las secciones de segundo orden (SOS) desactivan
la mitad del problema: cada par de polos conserva sus propios coeficientes,
de modo que los errores de redondeo quedan localizados en lugar de acumularse
en un único polinomio de orden alto. La otra mitad, la minúscula razón
$B / f_\mathrm{s}$ en sí, es lo que arregla el diezmado.

*La selectividad es proximidad, y el precio es el condicionamiento. A la
frecuencia completa, los polos de la banda de 25 Hz quedan a menos de $10^{-4}$
del círculo unidad y unos encima de otros; esa misma banda realizada a su
frecuencia diezmada los tiene repartidos y 500 veces más lejos. El panel derecho
es la decisión de diseño resumida en una curva: el banco multitasa mantiene
todas las bandas en la misma región bien condicionada, sea cual sea su
frecuencia central.*

<details>
<summary>Cómo se midió esta figura</summary>

Los dos bancos salen de la tabla de parámetros del apartado 2, uno con el
`design` por defecto y otro con `design=FilterDesign(resample=False)`. Las
posiciones de los polos se leen directamente del banco: `bank.sos[idx]` es la
cascada SOS de la banda `idx` y `scipy.signal.sos2zpk` la convierte en ceros,
polos y ganancia, de modo que la distancia al círculo unidad es
`1 - abs(poles).max()`. Para la banda de 25,1 Hz de un banco de tercios de
octava a 48 kHz esa distancia es **8.73e-05** a la frecuencia completa y
**4.62e-02** a la frecuencia diezmada que el banco usa realmente
(`bank.factor[idx]` vale 500, así que la banda corre a 96 Hz). SciPy lanza
`BadCoefficients` al leer el diseño a la frecuencia completa: ese aviso es el
argumento de este apartado, escrito por la biblioteca que se lo encontró.

</details>

### Diezmado multitasa

Una banda de tercio de octava de 25 Hz a 48 kHz abarca unos 5,8 Hz, el
0,024 % de Nyquist, con coeficientes tan rígidos que se vuelven
numéricamente inestables. El banco lo evita filtrando las bandas bajas a una
frecuencia diezmada:

Diezmar por $M$ reescala el problema: el mismo ancho de banda de 5,8 Hz se
vuelve $M$ veces mayor respecto al nuevo Nyquist, el radio de los polos se
separa del círculo unidad y los coeficientes SOS regresan a un rango bien
condicionado. El precio es una contabilidad que el banco paga internamente:
antes de cada etapa de diezmado debe ejecutarse un paso bajo antialiasing,
porque una componente por encima del nuevo Nyquist que se pliegue hacia abajo
cae *dentro* de las bandas bajas que se están midiendo, y ningún filtro
posterior puede eliminarla.

### Trampas de aliasing

El banco protege sus propias etapas de diezmado, pero solo puede analizar lo
que la cadena de captura le entrega:

- **Plegado en el ADC.** La energía por encima de $f_\mathrm{s}/2$ que llega al
  conversor sin un filtro antialiasing analógico se pliega dentro del rango
  de análisis y es indistinguible del sonido real dentro de banda. Las
  tarjetas de sonido lo filtran internamente; las cadenas de instrumentación
  a medida pueden no hacerlo.
- **Remuestreo barato.** Convertir una grabación de 44,1 kHz a 48 kHz con un
  remuestreador de baja calidad deja imágenes que sesgan las bandas más
  altas. Usa un remuestreador polifásico (`scipy.signal.resample_poly`) o,
  más sencillo, analiza a la frecuencia nativa: todas las funciones de
  phonometry aceptan `fs` directamente.
- **Bandas cerca de Nyquist.** Una banda cuyo borde superior se acerca a
  $f_\mathrm{s}/2$ no puede realizar su respuesta de diseño: la transformada bilineal
  comprime allí el eje de frecuencias (el mismo efecto que los filtros de
  ponderación contrarrestan con `high_accuracy`). Mantén el borde de la banda
  superior holgadamente por debajo de Nyquist o sube `fs`, y deja que
  `verify_filter_class` informe del margen restante.

### ¿Cuánto tiene que durar el registro?

Un nivel de banda no se lee de una señal, se *estima* a partir de ella, y cuanto
más estrecha es la banda más tarda esa estimación. El ancho de banda de una
banda de $1/b$ de octava es $B = f_\mathrm{m}\,(G^{1/2b} - G^{-1/2b})$, que para tercios
de octava vale $0{,}231\,f_\mathrm{m}$, y de ahí se siguen dos consecuencias.

El filtro tiene que **asentarse**: una banda resuena durante unas pocas veces
$1/B$, así que ese trozo del principio del registro es transitorio y no nivel, y
hay que descartarlo (alrededor de un segundo en la banda de 12,5 Hz, doce
milisegundos a 1 kHz). Y la estimación arrastra un **error aleatorio** de unos
$4{,}34/\sqrt{BT}$ dB con sonido de tipo ruido, así que una desviación típica de
0,4 dB exige $BT = 100$:

| Banda más baja informada | $B$ [Hz] | $T$ para una desviación típica de 0,4 dB |
| :--- | ---: | ---: |
| 12,5 Hz | 2,9 | 35 s |
| 125 Hz | 29 | 3,5 s |
| 1 kHz | 231 | 0,4 s |

La regla que se sigue cabe en una línea: **la banda más baja que pienses
informar fija la duración del registro de todo el banco.** Analizar un
fragmento de 1 s hasta 12,5 Hz e informar el resultado con un decimal no es una
medida de esa banda, y ni `octave_filter` ni `OctaveFilterBank` te avisan. La
ley general $1/\sqrt{BT}$ se deduce en
[Correlación, retardo y envolvente](/phonometry/es/signals/spectra/correlation-delay/);
esa misma aritmética aplicada por trama en vez de por registro es la nota sobre
`window_time` de
[Niveles integrados y estadísticos](/phonometry/es/signals/levels/levels/).

Las matemáticas de banda anteriores son comunes a todas las arquitecturas.
Cómo difieren realmente las arquitecturas, la comparación en el cruce a
−3 dB, la galería completa de respuestas en octava y tercio de octava y los
ejemplos de uso por arquitectura, hasta el crossover Linkwitz-Riley, es la
[Galería de arquitecturas de filtro](/phonometry/es/signals/filters/filter-gallery/).

## 2. Parámetros de `octave_filter()` / `OctaveFilterBank`

Los argumentos de cada día van primero y por posición — `x`, `fs`, `fraction`,
`order`, `limits` — y todo lo demás se agrupa en cuatro dataclases congeladas
que se pasan por palabra clave: `design` (`FilterDesign`), `calibration`
(`LevelCalibration`), `block_processing` (`BlockProcessing`) y `response_plot`
(`ResponsePlot`). Al ser congeladas son hashables, que es lo que permite que dos
grupos de opciones iguales acierten en la misma caché de diseño. La tabla de
abajo nombra cada opción por el grupo en el que viaja.

Tres puntos de llamada comparten esta tabla, y no aceptan las mismas opciones,
así que la columna **Dónde** nombra aquel al que pertenece cada una:
`octave_filter()` es la función de un solo uso, `OctaveFilterBank(...)` es el
constructor y `bank.filter()` es el método por llamada.

| Parámetro | Dónde | Tipo | Unidades | Rango / por defecto | Notas |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `x` | función, `.filter()` | array 1D o 2D | unidades digitales | no vacío | 2D es `[channels, samples]` |
| `fs` | función, constructor | int | Hz | > 0 | |
| `fraction` | función, constructor | int | — | por defecto `1`; habitual `3`; cualquier $b \ge 1$ | Bandas por octava = $b$ |
| `order` | función, constructor | int | — | por defecto `6` | Orden SOS por banda |
| `limits` | función, constructor | lista `[lo, hi]` | Hz | por defecto `[12, 20000]` | Rango de análisis |
| `design.filter_type` | función, constructor | str | — | `'butter'` (por defecto), `'cheby1'`, `'cheby2'`, `'ellip'`, `'bessel'` | Consulta la [Galería de arquitecturas de filtro](/phonometry/es/signals/filters/filter-gallery/) |
| `design.ripple` / `design.attenuation` | función, constructor | float | dB | `ripple` defecto `0.1`; `attenuation` defecto `72.0` | Rizado de banda de paso / atenuación en banda atenuada (cheby/ellip); `cheby2` necesita `attenuation` $\ge 70$ para clase 1, ya que scipy fija su suelo de rizado constante en exactamente este valor |
| `design.resample` | función, constructor | bool | — | por defecto `True` | Filtra cada banda a una frecuencia diezmada (multitasa) |
| `response_plot.show` | función, constructor | bool | — | por defecto `False` | Dibuja la respuesta del banco (requiere matplotlib) |
| `response_plot.file` | función, constructor | str o `None` | — | por defecto `None` | Guarda la gráfica de respuesta del banco en esta ruta |
| `calibration.factor` | función, constructor | float | — | por defecto `1.0` | Escala la entrada a pascales (consulta la guía de Calibración) |
| `calibration.dbfs` | función, constructor | bool | — | por defecto `False` | Referencia los niveles a fondo de escala digital en vez de 20 µPa |
| `block_processing.stateful` / `.steady_ic` | solo constructor | bool | — | por defecto `False` | Estado en streaming; consulta [Procesado por bloques](/phonometry/es/signals/filters/block-processing/) |
| `sigbands` | función, `.filter()` | bool | — | por defecto `False` | Devuelve también las señales temporales por banda |
| `mode` | función, `.filter()` | str | — | `'rms'` (por defecto) o `'peak'` | Estadístico por banda devuelto |
| `nominal` | función, `.filter()` | bool | — | por defecto `False` | Devuelve etiquetas nominales (p. ej. `1000`) en vez de las frecuencias centrales exactas |
| `detrend` | función, `.filter()` | bool | — | por defecto `True` | Elimina el offset DC de cada banda antes del nivel (mejora la precisión en graves) |
| `zero_phase` | solo `.filter()` | bool | — | por defecto `False` | Filtrado adelante-atrás (offline); ver el apartado 5 |
| `calculate_level` | solo `.filter()` | bool | — | por defecto `True` | Con `False` devuelve las señales de banda sin calcular niveles (usar con `sigbands=True`) |

El reparto no es arbitrario: la `octave_filter()` de un solo uso diseña un banco
y lo usa una vez, así que solo lleva las opciones de tiempo de diseño.
`zero_phase` y `calculate_level` se deciden en cada llamada y por eso viven en
`OctaveFilterBank.filter()` — `octave_filter(x, fs, zero_phase=True)` lanza
`TypeError` —, mientras que el estado de streaming queda fijado al construir el
banco.

`mode` tiene exactamente dos valores. `'rms'` da el nivel medio energético de la
banda sobre todo el registro, que es lo que toda norma entiende por «nivel de
banda». `'peak'` da $20\log_{10}$ de la mayor muestra en valor absoluto dentro
de la banda, contra la misma referencia de 20 µPa; es un pico instantáneo de
banda, no una lectura de retención de pico, está limitado en banda y sin
ponderar, así que *no* es el pico ponderado C $L_\mathrm{Cpeak}$ de la guía de
[Niveles](/phonometry/es/signals/levels/levels/), y en señales impulsivas lo
domina la propia resonancia del filtro: pertenece al diagnóstico de transitorios,
no a la comunicación de niveles. No hay modo `'sum'`: un total entre bandas es
una suma energética que hace quien llama,
`10*np.log10(np.sum(10**(spl/10)))`, nunca una media aritmética de decibelios.

`verify_filter_class(bank)` comprueba el banco diseñado contra los límites de
aceptación de la Tabla 1 de IEC 61260-1 e informa de la clase (`1`, `2` o `None` si queda fuera de ambas) con los
márgenes por banda.

### Qué compra `order`, y dónde deja de comprar

`order` es el orden de cada par de secciones paso banda, así que fija con qué
rapidez caen las faldas y, por tanto, cuánto se cuela una banda vecina en el
nivel de una banda. El orden 6 es el valor por defecto porque supera la máscara
de IEC 61260-1 con holgura para Butterworth, y la aritmética merece verse: a
48 kHz sobre un banco Butterworth de tercios de octava, el orden 2 se queda a
27,03 dB de la clase 1 en la banda atenuada lejana, mientras que los órdenes 4,
6, 8 y 10 pasan todos con un margen de clase 1 de exactamente +0,40 dB.

Que el margen sea idéntico no es casualidad, y es la mitad útil de la regla. A
partir del orden 4 la restricción que manda se ha desplazado de la banda
atenuada a la banda de paso, donde un diseño máximamente plano se queda en 0 dB
de desviación y el corredor de clase 1 es de ±0,4 dB: subir más el orden compra
profundidad en la banda atenuada, pero ni un decibelio de margen de clase. Y
cuesta: cada par de polos de más alarga la resonancia (los picos de retardo de
grupo del apartado 4 crecen con el orden), que es la razón por la que el trabajo
con transitorios y los espectrogramas de ventana corta prefieren órdenes bajos.
La aritmética del margen en sí es
[Verificación de clase de filtros](/phonometry/es/signals/filters/filter-compliance/).

## 3. EQ paramétrico (`ParametricEQ`)

Secciones biquad de ecualización según el **RBJ Audio EQ Cookbook**
(Bristow-Johnson): campana (peaking), shelving grave/agudo, paso bajo/alto,
paso banda (pico a 0 dB constante o faldas constantes), rechazo de banda
(notch) y paso todo, cada una parametrizada por `fs`, `f0`, `gain_db` y uno
de `q`, `bw` (ancho de banda en octavas) o `slope`, exactamente como los
define el cookbook. Las secciones se encadenan en una cascada SOS
numéricamente robusta, y el diseño es exacto en forma cerrada: una campana
pasa exactamente `gain_db` en `f0` y exactamente 0 dB en DC y Nyquist, los
shelving caen exactamente en `gain_db` en su extremo, y el paso todo tiene
magnitud unidad en todas las frecuencias (solo gira la fase).

```python

from phonometry import EQSection, ParametricEQ

fs = 48000
rng = np.random.default_rng(1)
x = rng.standard_normal(fs)                 # un segundo de ruido

eq = ParametricEQ(fs, [
    EQSection("lowshelf", 100.0, gain_db=4.0),
    EQSection("peaking", 1000.0, gain_db=-6.0, bw=1.0),  # recorte de 1 octava
    EQSection("highshelf", 8000.0, gain_db=3.0),
])
y = eq.filter(x)              # aplicar la cascada
res = eq.response()           # resultado congelado con la cascada SOS
axes = res.plot(language="es")  # magnitud + fase de la cascada
```

Para procesado por bloques pasa `stateful=True` (la misma convención que
`WeightingFilter`); el atajo de un solo uso es
`parametric_eq(x, fs, sections)`.

*Las tres secciones del fragmento (en gris) y la cascada que suman; el panel
inferior es la fase que cuesta esa magnitud. Cualquier resultado de la
biblioteca se redibuja así: el `res.plot()` de arriba produjo exactamente esta
figura.*

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

```python

from phonometry import EQSection, ParametricEQ

fs = 48000
familia = [
    EQSection("peaking", 1000.0, gain_db=6.0, q=1.4),
    EQSection("lowshelf", 125.0, gain_db=6.0),
    EQSection("highshelf", 4000.0, gain_db=-6.0),
    EQSection("lowpass", 10000.0),
    EQSection("highpass", 50.0),
    EQSection("bandpass", 500.0, q=2.0),
    EQSection("notch", 2000.0, q=6.0),
]
fig, ax = plt.subplots(figsize=(10, 6))
for section in familia:
    res = ParametricEQ(fs, [section]).response(f_min=20.0, f_max=20000.0)
    ax.semilogx(res.frequencies, res.magnitude_db,
                label=f"{section.filter_type} @ {section.f0:g} Hz")
ax.set(xlim=(20, 20000), ylim=(-27, 9),
       xlabel="Frecuencia [Hz]", ylabel="Magnitud [dB]")
ax.grid(True, which="both", alpha=0.3)
ax.legend(loc="lower center", ncols=2, fontsize=9)
plt.show()
```

</details>

Todo lo anterior es diseño. Demostrar que un banco diseñado cumple una clase
de prestaciones de IEC 61260-1, banda a banda y con su margen en decibelios,
es [Verificación de clase de filtros](/phonometry/es/signals/filters/filter-compliance/):
la máscara de aceptación de la Tabla 1, la clase 0 más estricta de la edición
retirada de 1995, qué aporta una clase en una medición y la ficha de
conformidad acreditada.

## 4. Señales de banda y retardo de grupo

`sigbands=True` devuelve las señales temporales por banda junto a los niveles, y
es lo que hace visible el comportamiento en fase y en transitorio, y lo que deja
al descubierto el retardo de grupo que cada arquitectura paga por su
selectividad. La estabilidad en sí es asunto del apartado 1, en
[Polos, ceros y estabilidad](#polos-ceros-y-estabilidad); lo que este apartado
añade es la prueba, porque un filtro cuya respuesta al impulso decae a cero es
un filtro estable.

```python

from phonometry import filters

# 1. Generar una señal (suma de 250 Hz y 1000 Hz)
fs = 48000
t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)
y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)

# 2. Comparar arquitecturas (Butterworth vs Chebyshev II)
spl_b, freq, xb_butter = filters.octave_filter(
    y, fs=fs, fraction=1, sigbands=True,
    design=filters.FilterDesign(filter_type='butter'))
spl_c2, _, xb_cheby2 = filters.octave_filter(
    y, fs=fs, fraction=1, sigbands=True,
    design=filters.FilterDesign(filter_type='cheby2'))

# 'xb_butter' y 'xb_cheby2' contienen las señales por banda en el dominio del tiempo
```

*El panel superior es la entrada, los intermedios son sus señales de banda con
**Butterworth** (línea continua) y **Chebyshev II** (discontinua), y el inferior
es la respuesta al impulso de la banda de 1 kHz de cada uno. Las dos respuestas
al impulso decaen a cero — esa es la comprobación de estabilidad —, pero la
resonancia de Chebyshev II dura visiblemente más, que es el precio en
transitorio de su falda más abrupta.*

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

```python

from phonometry import filters

fs = 48000
t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)
y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)

bank_b = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[100.0, 2000.0])
bank_c = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[100.0, 2000.0],
                          design=filters.FilterDesign(filter_type="cheby2"))
_, freq, xb_butter = bank_b.filter(y, sigbands=True)
_, _, xb_cheby2 = bank_c.filter(y, sigbands=True)

# La figura publicada lleva dos paneles más: la entrada arriba y, abajo, la
# banda de 1 kHz de un impulso unitario a través de cada banco.
impulse = np.zeros_like(t)
impulse[0] = 1.0
_, _, imp_butter = bank_b.filter(impulse, sigbands=True)
_, _, imp_cheby2 = bank_c.filter(impulse, sigbands=True)
one_k = int(np.argmin(np.abs(np.asarray(freq) - 1000.0)))

fig, axes = plt.subplots(len(freq) + 2, 1, figsize=(9, 2 * (len(freq) + 2)),
                         sharex=True)
axes[0].plot(t, y)
axes[0].set_title("Señal original (250 Hz + 1000 Hz)")
for ax, fc, xb, xc in zip(axes[1:-1], freq, xb_butter, xb_cheby2):
    ax.plot(t, xb, label="Butterworth")
    ax.plot(t, xc, "--", label="Chebyshev II")
    ax.set_title(f"Banda de {fc:.0f} Hz")
axes[-1].plot(t, imp_butter[one_k], label="Butterworth")
axes[-1].plot(t, imp_cheby2[one_k], "--", label="Chebyshev II")
axes[-1].set_title("Respuesta al impulso, banda de 1 kHz")
axes[1].legend()
axes[0].set_xlim(0, 0.04)
axes[-1].set_xlabel("Tiempo [s]")
plt.tight_layout()
plt.show()
```

</details>

:::note
**¿Por qué las señales parecen desplazadas en el tiempo?**
Los filtros IIR digitales (como Butterworth o Chebyshev) tienen **respuestas de
fase no lineales**, lo que produce un **retardo de grupo** dependiente de la
frecuencia. En la banda de 250 Hz se aprecia que el filtro Chebyshev II tiene un
retardo de propagación distinto al de Butterworth. Es una propiedad física normal
de estas arquitecturas: caídas más agresivas suelen implicar mayor retardo de
grupo y distorsión de fase.
:::

### El retardo de grupo, cuantificado

El retardo de grupo $\tau_\mathrm{g}(\omega) = -\frac{d\phi(\omega)}{d\omega}$ de la
banda de octava de 1 kHz muestra el compromiso directamente: Bessel se mantiene
casi plano en la banda de paso (los transitorios sobreviven), mientras que
Chebyshev I y el Elíptico pagan su caída abrupta con fuertes picos de retardo en
los bordes de banda.

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

```python

from scipy.signal import group_delay
from phonometry import filters

fs = 48000
w = np.logspace(np.log10(500), np.log10(2000), 1024)
fig, ax = plt.subplots(figsize=(9, 5))
for ftype in ("butter", "cheby1", "cheby2", "ellip", "bessel"):
    bank = filters.OctaveFilterBank(fs, fraction=1, order=6, limits=[800, 1200],
                            design=filters.FilterDesign(filter_type=ftype))
    idx = int(np.argmin(np.abs(np.array(bank.freq) - 1000)))
    fsd = fs / bank.factor[idx]
    # Retardo de grupo de una cascada SOS = suma del de sus secciones
    gd = sum(group_delay((sec[:3], sec[3:]), w=w, fs=fsd)[1]
             for sec in bank.sos[idx])
    ax.semilogx(w, gd / fsd * 1000, label=ftype)
ax.set(xlim=(500, 2000), xlabel="Frecuencia [Hz]",
       ylabel="Retardo de grupo [ms]")
ax.grid(True, which="both", alpha=0.3)
ax.legend()
plt.show()
```

</details>

## 5. Filtrado de fase cero

Para análisis offline puedes eliminar por completo el retardo de grupo:
`OctaveFilterBank.filter(…, zero_phase=True)` filtra cada banda hacia delante y
hacia atrás (`scipy.signal.sosfiltfilt`), manteniendo las señales por banda
alineadas con la entrada. Es una opción por llamada del banco, no un argumento
de la `octave_filter()` de un solo uso. La atenuación efectiva se duplica y la banda de paso efectiva se
estrecha, reduciendo el nivel de banda ancha medido en ~0,2 a 0,3 dB por banda
(un tono puro dentro de banda no se ve afectado); prefiere el filtrado hacia
delante cuando el SPL absoluto de banda debe coincidir con el convenio de un
solo paso, y reserva la fase cero para cuando importa la envolvente temporal
(p. ej. decaimiento de reverberación). La opción es incompatible con el
procesado por bloques (stateful).

```python

from phonometry import filters

fs = 48000
t = np.linspace(0, 0.5, int(fs * 0.5), endpoint=False)
y = np.sin(2 * np.pi * 250 * t) + np.sin(2 * np.pi * 1000 * t)

bank = filters.OctaveFilterBank(fs=48000, fraction=3)
spl, freq, xb = bank.filter(y, sigbands=True, zero_phase=True)
```

*El filtrado causal retrasa la ráfaga según el retardo de grupo del filtro; el
filtrado de fase cero la mantiene alineada con la entrada.*

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

```python

from phonometry import filters

fs = 48000
t = np.linspace(0, 0.15, int(fs * 0.15), endpoint=False)
x = np.zeros_like(t)                      # ráfaga de 250 Hz a mitad de trama
start, end = int(0.05 * fs), int(0.10 * fs)
x[start:end] = np.sin(2 * np.pi * 250 * t[start:end]) * np.hanning(end - start)

bank = filters.OctaveFilterBank(fs=fs, fraction=1, order=6, limits=[200.0, 300.0])
_, _, fwd = bank.filter(x, sigbands=True, calculate_level=False)
_, _, zp = bank.filter(x, sigbands=True, calculate_level=False,
                       zero_phase=True)

fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(t, x, color="gray", alpha=0.5, label="Ráfaga de entrada (250 Hz)")
ax.plot(t, fwd[0], label="Causal (retardo de grupo)")
ax.plot(t, zp[0], "--", label="zero_phase=True (alineado)")
ax.set(xlabel="Tiempo [s]", ylabel="Amplitud")
ax.legend()
plt.show()
```

</details>

## Qué cubre esta guía

La matemática de bordes de banda de la IEC 61260-1:2014 (apartados 5.2-5.5) y
la serie de frecuencias preferentes de la ISO 266:1997 tras
`nominal_frequencies`. La biblioteca diseña cada banda como una cascada SOS en
cinco arquitecturas (Butterworth, Chebyshev I/II, elíptico, Bessel), más el
`ParametricEQ` del RBJ Audio EQ Cookbook, con la descomposición por bandas y
el filtrado de fase cero.

Las respuestas comparadas de las cinco arquitecturas, la galería completa de
octava y tercio de octava y los ejemplos de uso por arquitectura, incluido el
crossover Linkwitz-Riley, son la [Galería de arquitecturas de
filtro](/phonometry/es/signals/filters/filter-gallery/). Las máscaras de
aceptación de clase de la Tabla 1 y su verificación se han trasladado a
[Verificación de clase de
filtros](/phonometry/es/signals/filters/filter-compliance/). Cerca de Nyquist,
la transformada bilineal deforma el eje de frecuencia y el banco no tiene
corrección para ello (a diferencia de la opción `high_accuracy` de
`WeightingFilter`): mantén el borde superior de banda con margen cómodo
respecto a Nyquist, o sube `fs`, y confirma el margen con
`verify_filter_class`.

## Véase también

- [Galería de arquitecturas de filtro](/phonometry/es/signals/filters/filter-gallery/):
  las cinco arquitecturas comparadas en los bordes de banda, la galería
  completa de respuestas y el uso por arquitectura, con el crossover
  Linkwitz-Riley.
- [Verificación de clase de filtros (IEC 61260-1)](/phonometry/es/signals/filters/filter-compliance/):
  la máscara de aceptación de la Tabla 1, la clase 0 y la ficha de
  conformidad de los bancos que se diseñan aquí.
- Referencia de la API: [`phonometry`](/phonometry/es/reference/api/filters/phonometry/), [`filters.core`](/phonometry/es/reference/api/filters/core/) y [`filters.weighting`](/phonometry/es/reference/api/filters/weighting/).
- Teoría: [Frecuencias de banda de octava](/phonometry/es/reference/theory/signal-analysis/#frecuencias-de-banda-de-octava-ansi-s111--iec-61260): de dónde salen la rejilla de frecuencias centrales en base 10 y los bordes de banda, y por qué el banco se diseña sobre ellos y no sobre las etiquetas nominales.

## Respuestas rápidas

### ¿Cómo se definen las frecuencias centrales y los bordes de banda de fracción de octava?

IEC 61260-1:2014 (apartados 5.2-5.5) construye cada banda a partir de la
razón de octava en base 10 $G = 10^{3/10} \approx 1{,}99526$, de modo que
una octava no es exactamente 2. Las frecuencias centrales siguen
$f_\mathrm{m} = 1000 \cdot G^{x/b}$ para $b$ impar y
$f_\mathrm{m} = 1000 \cdot G^{(2x+1)/2b}$ para $b$ par — de modo que 1 kHz es una
frecuencia central de banda en las fracciones impares y un *borde* de banda
en las pares — y los bordes son $f_1 = f_\mathrm{m} G^{-1/2b}$ y
$f_2 = f_\mathrm{m} G^{+1/2b}$ en ambos casos; cada banda de tercio de
octava abarca $G^{1/3} \approx 1{,}2589$, diez bandas por década.
