---
title: "Medición de sistemas: Golay, barridos, inversión"
description: "Pares complementarios de Golay con deconvolución exactamente libre de ruido hacia una respuesta al impulso, barridos sintetizados para seguir un espectro de magnitud objetivo arbitrario conformando el retardo de grupo, y la inversión espectral regularizada de una respuesta medida con regularización dependiente de la frecuencia de Kirkeby."
references:
  - type: book
    authors: ["Havelock, D.", "Kuwano, S.", "Vorländer, M. (eds.)"]
    year: 2008
    title: "Handbook of signal processing in acoustics"
    publisher: "Springer"
    doi: "10.1007/978-0-387-30441-0"
    note: "Parte I, capítulo 6 (Xiang, Digital Sequences): la recursión de Golay del §1, la identidad de autocorrelaciones complementarias de la Ec. (2) y el procedimiento de recuperación en frecuencia de la Ec. (4) y la Fig. 2. ISBN 978-0-387-77698-9."
  - type: article
    authors: ["Müller, S.", "Massarani, P."]
    year: 2001
    title: "Transfer-function measurement with sweeps"
    journal: "Journal of the Audio Engineering Society"
    volume: 49
    issue: 6
    pages: "443-471"
    url: "https://aes2.org/publications/elibrary-page/?id=10189"
    note: "El capítulo de síntesis de barridos tras el §2: construcción en el dominio de la frecuencia a partir de magnitud y retardo de grupo (Sec. 4.2), la recursión del retardo de grupo para espectros de magnitud arbitrarios (Sec. 4.3, Ecs. (11)-(12)) y la discusión de inversión limitada en banda de las Secs. 3.1 y 4.5. Se consultó la edición extendida \"Director's Cut\"."
  - type: article
    authors: ["Kirkeby, O.", "Nelson, P. A."]
    year: 1999
    title: "Digital filter design for inversion problems in sound reproduction"
    journal: "Journal of the Audio Engineering Society"
    volume: 47
    issue: 7/8
    pages: "583-595"
    url: "https://aes2.org/publications/elibrary-page/?id=12098"
    note: "La regularización de Tikhonov dependiente de la frecuencia del §3 (su Ec. (17)) y el retardo de modelado que hace causal la inversa de fase mixta (Sec. 2.4)."
  - type: article
    authors: ["Golay, M. J. E."]
    year: 1961
    title: "Complementary series"
    journal: "IRE Transactions on Information Theory"
    volume: 7
    issue: 2
    pages: "82-87"
    doi: "10.1109/TIT.1961.1057620"
    note: "La construcción original de los pares complementarios del §1."
---

import ThemeImage from '../../../../components/ThemeImage.astro';

La [guía de acústica de salas](/phonometry/es/guides/room-acoustics/)
recupera respuestas al impulso con los dos caballos de batalla de la
ISO 18233: el barrido exponencial y la MLS. Esta página añade la capa de
ingeniería de medición a su alrededor, tomada de la literatura de medición de
funciones de transferencia y no de una norma: los **pares complementarios de
Golay** (una tercera excitación cuya deconvolución está *exactamente* libre
de ruido de correlación), los **barridos conformados** cuyo espectro de
magnitud sigue cualquier objetivo prescrito conservando el factor de cresta
de un seno barrido, y la **inversión regularizada** de una respuesta medida -
la herramienta que convierte la respuesta medida de un altavoz o micrófono en
un ecualizador seguro, y el puente entre las dos anteriores: la inversa de
una respuesta medida es el objetivo espectral natural para el barrido de la
siguiente medición.

## 1. Pares complementarios de Golay

Un par de Golay son dos secuencias binarias `a`, `b` de longitud `L = 2**n`,
construidas con una recursión de dos líneas - anexar `b` a `a`, y `-b` a `a`
(Havelock, Parte I, Cap. 6, Ec. (1)). Su propiedad definitoria es
*algebraica*, no aproximada: las dos autocorrelaciones periódicas suman

$$
\sum_j a_j a_{j+k} + b_j b_{j+k} \;=\;
\begin{cases} 2L & k = 0\\ 0 & k \ne 0, \end{cases}
$$

una delta exacta (Ec. (2)). La autocorrelación de una MLS tiene un residuo
`-1` en cada retardo no nulo; los lóbulos laterales de Golay se cancelan
idénticamente. La medición emite cada código por turnos - reproducir `a`
periódicamente, grabar un período en régimen estacionario, y lo mismo con
`b` - y suma las dos correlaciones cruzadas circulares (Ec. (4)): para un
sistema lineal e invariante en el tiempo sin ruido, la respuesta al impulso
vuelve con precisión de máquina, una identidad de forma cerrada que los tests
y el [informe de conformidad](/phonometry/es/reference/conformance/) fijan
en `1e-13`.

```python
import numpy as np
from scipy import signal
from phonometry import golay_impulse_response, golay_pair

fs = 48000
pair = golay_pair(14)                       # dos códigos de 16384 muestras

# Medición simulada: reproducir cada código periódicamente y grabar un
# período en régimen estacionario de un sistema paso banda tipo sala.
b, a = signal.butter(2, [80.0, 12000.0], btype="bandpass", fs=fs)
length = pair[0].size
rec_a = signal.lfilter(b, a, np.tile(pair[0], 3))[2 * length:]
rec_b = signal.lfilter(b, a, np.tile(pair[1], 3))[2 * length:]

ir = golay_impulse_response(rec_a, rec_b, pair, fs=fs)
print(ir.method, ir.size)                   # golay 16384
ir.plot(language="es")
```

El resultado es el mismo `ImpulseResponseResult` que devuelven los frontales
de barrido y MLS, así que todo lo que viene después -
[parámetros de sala](/phonometry/es/guides/room-acoustics/), curvas de
caída, [STI](/phonometry/es/guides/speech-transmission/) - lo consume sin
cambios. Las grabaciones de varios períodos se promedian síncronamente, de
modo que el ruido de fondo no correlado cae 3 dB por duplicación mientras la
parte determinista permanece exacta. Los compromisos son un espejo de la
MLS: la recuperación es circular (el sistema debe decaer dentro de un
período del código, o un `ImpulseResponseWarning` señala la cola con
aliasing), los productos de distorsión se reparten por el período en vez de
separarse como en un barrido, y los dos regímenes estacionarios secuenciales
hacen del par el más expuesto de la familia a la variación temporal (Sec. 2
de Xiang) - el precio de la complementariedad exacta.

## 2. Barridos con un espectro objetivo arbitrario

La energía de un barrido en una frecuencia dada puede fijarse de dos maneras:
con su amplitud, o con *cuánto tiempo permanece en ella*. La síntesis en el
dominio de la frecuencia de Mueller y Massarani (Secs. 4.2-4.3) usa la
segunda palanca: definir la magnitud objetivo `|H(f)|`, hacer crecer el
retardo de grupo en proporción a la potencia del objetivo,

$$
\tau_G(f) = \tau_G(f - df) + C\,|H(f)|^2,\qquad
C = \frac{\tau_G(f_{end}) - \tau_G(f_{start})}{\sum |H(f)|^2},
$$

(Ecs. (11)-(12)), integrar el retardo de grupo en una fase, y aplicar la FFT
inversa. El barrido sigue entonces *cualquier* forma espectral con una
envolvente casi constante, manteniendo el factor de cresta cerca del ideal
de 3,02 dB de un seno barrido - a diferencia de una señal de ruido con el
mismo espectro, que queda ~6 dB por encima. `shaped_sweep_signal` implementa
la construcción con los detalles de limitación en banda y de fase en Nyquist
del artículo; `target` es `"pink"` (el énfasis clásico de medición de salas,
por defecto), `"white"`, o cualquier par `(frecuencias_hz, magnitud_db)`:

```python
from phonometry import shaped_sweep_signal

fs = 48000
sweep = shaped_sweep_signal(fs, 50.0, 5000.0, 2.0, target="pink")
print(round(sweep.crest_factor_db, 1))      # 4.2 (dB; el ideal es 3,02)
sweep.plot(language="es")
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/shaped_sweep_es.svg" alt="Dos paneles para un barrido conformado rosa de 50 hercios a 5 kilohercios: la forma de onda temporal con una envolvente casi constante durante dos segundos y, debajo, el espectro de Welch del barrido cayendo tres decibelios por octava exactamente sobre la línea discontinua del objetivo rosa dentro de la banda sombreada del barrido" width="88%" loading="eager" />

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

```python
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal as sp_signal
from phonometry import shaped_sweep_signal

fs = 48000
res = shaped_sweep_signal(fs, 50.0, 5000.0, 2.0, target="pink")
x = np.asarray(res)

nperseg = 8192   # 75 % de solape: con 50 % un barrido riza ~2 dB
freqs, psd = sp_signal.welch(x, fs=fs, nperseg=nperseg,
                             noverlap=3 * nperseg // 4)
welch_db = 10.0 * np.log10(psd)
welch_db -= welch_db[(freqs >= 50.0) & (freqs <= 5000.0)].max()
target_db = 20.0 * np.log10(np.maximum(res.magnitude, 1e-300))
target_db -= target_db[(res.frequencies >= 50.0)
                       & (res.frequencies <= 5000.0)].max()

fig, axes = plt.subplots(2, 1, figsize=(10, 7))
axes[0].plot(np.arange(x.size) / fs, x, lw=0.5)
axes[0].set_xlabel("Tiempo [s]")
axes[0].set_ylabel("Amplitud")
axes[1].semilogx(freqs[1:], welch_db[1:], lw=1.3,
                 label="Espectro de Welch")
axes[1].semilogx(res.frequencies[1:], target_db[1:], "r--",
                 label="Objetivo rosa (-3 dB por octava)")
axes[1].axvspan(50.0, 5000.0, alpha=0.08, label="Banda del barrido")
axes[1].set_xlabel("Frecuencia [Hz]")
axes[1].set_ylabel("Nivel re máximo en banda [dB]")
axes[1].set_ylim(-60.0, 8.0)
axes[1].legend()
plt.tight_layout()
plt.show()
```

</details>

Los metadatos de la síntesis viajan con el resultado: `frequencies` y
`magnitude` son la magnitud exacta, limitada en banda, impuesta al espectro;
`group_delay` es la trayectoria tiempo-frecuencia del barrido, y
`crest_factor_db` la relación pico/RMS conseguida. El propósito de la
conformación es ingeniería de la relación señal-ruido: adaptar el espectro
emitido al ruido de fondo del recinto (o a los límites de potencia del
altavoz) compra una SNR independiente de la frecuencia que ningún
posprocesado puede recuperar (Mueller y Massarani, Sec. 3).

La deconvolución no necesita nada nuevo: el resultado actúa como su propio
array de referencia para el
[método espectral](/phonometry/es/guides/room-acoustics/) de
`impulse_response`, que divide de nuevo la coloración del barrido:

```python
import numpy as np
from scipy import signal
from phonometry import impulse_response, shaped_sweep_signal

fs = 48000
freqs = np.array([50.0, 200.0, 1000.0, 8000.0])
emphasis = np.array([12.0, 6.0, 0.0, 0.0])   # refuerzo LF contra el retumbe
sweep = shaped_sweep_signal(fs, 50.0, 8000.0, 3.0,
                            target=(freqs, emphasis))

# Medición simulada de un sistema conocido (sustituir por emitir/grabar):
b, a = signal.butter(2, [200.0, 4000.0], btype="bandpass", fs=fs)
excitation = np.concatenate([np.asarray(sweep), np.zeros(fs)])
recorded = signal.lfilter(b, a, excitation)

ir = impulse_response(recorded, excitation, fs, length=fs)
ir.plot(language="es")
```

El énfasis vuelve a ponderar el suelo de ruido de la medición, no la
respuesta recuperada. Una sutileza heredada de la limitación dura en banda:
el núcleo limitador de banda de la deconvolución es de fase cero, así que una
pequeña cola anticausal queda al final del búfer completo de deconvolución -
use `return_full=True` cuando persiga décimas de dB en los bordes de la
banda.

## 3. Inversión espectral regularizada

Ecualizar con una respuesta medida significa invertirla, y un recíproco puro
`1/H(f)` estalla allí donde el sistema radia poca energía - un valle en
banda, todo lo que queda fuera de banda - convirtiendo el ecualizador en un
amplificador de ruido. Mueller y Massarani confinan la inversión con un paso
banda (Secs. 3.1, 4.5); la forma general de ese confinamiento es la
regularización de Tikhonov dependiente de la frecuencia de Kirkeby y Nelson,

$$
H_{inv}(f) = \frac{H^*(f)}{|H(f)|^2 + \varepsilon(f)},
$$

con `ε(f)` pequeña dentro de la banda a ecualizar y grande fuera. En banda,
la magnitud ecualizada `|H·H_inv|` se desvía de la unidad exactamente en
`ε/(|H|² + ε)` - una forma cerrada que la suite de conformidad comprueba bin
a bin - y fuera de banda la ganancia del filtro nunca puede superar
`1/(2·√ε)`, el máximo analítico de `x/(x² + ε)`. Un retardo de modelado de
medio bloque del filtro hace causal la inversa, en general anticausal, de una
respuesta de fase mixta (Kirkeby y Nelson, Sec. 2.4).

```python
import numpy as np
from scipy import signal
from phonometry import regularized_inverse_filter

fs = 48000.0
# Una respuesta medida tipo altavoz (sustituir por una RI medida).
b, a = signal.butter(2, [100.0, 8000.0], btype="bandpass", fs=fs)
imp = np.zeros(2048)
imp[0] = 1.0
h = signal.lfilter(b, a, imp)

inv = regularized_inverse_filter(h, fs, f_range=(200.0, 4000.0))
print(round(inv.flatness_db, 5))    # 1e-05 (dB: desviación en banda de 0 dB)
print(round(inv.max_gain_db, 1))    # -6.0 (dB: refuerzo fuera de banda acotado)
inv.plot(language="es")
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/regularized_inversion_es.svg" alt="Magnitudes sobre frecuencia logarítmica para una inversión regularizada de una respuesta paso banda: la respuesta medida en azul, el filtro inverso en rojo reflejándola dentro de la banda ecualizada sombreada de 200 hercios a 4 kilohercios, y el producto ecualizado en verde leyendo exactamente cero decibelios en la banda y cayendo fuera, donde la regularización limita la ganancia" width="88%" loading="eager" />

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

```python
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
from phonometry import regularized_inverse_filter

fs = 48000.0
b, a = signal.butter(2, [100.0, 8000.0], btype="bandpass", fs=fs)
imp = np.zeros(2048)
imp[0] = 1.0
h = signal.lfilter(b, a, imp)

res = regularized_inverse_filter(h, fs, f_range=(200.0, 4000.0))
f = res.frequencies[1:]
h_mag = np.abs(res.response_spectrum)[1:]
inv_mag = np.abs(res.spectrum)[1:]
peak = h_mag.max()

fig, ax = plt.subplots(figsize=(10, 6))
ax.semilogx(f, 20 * np.log10(h_mag / peak), label="Medida $|H|$")
ax.semilogx(f, 20 * np.log10(inv_mag * peak),
            label=r"Inversa $|H_{\mathrm{inv}}|$")
ax.semilogx(f, 20 * np.log10(h_mag * inv_mag), lw=1.8,
            label=r"Ecualizada $|H \cdot H_{\mathrm{inv}}|$")
ax.axvspan(200.0, 4000.0, alpha=0.08, label="Banda ecualizada")
ax.set_ylim(-50.0, 15.0)
ax.set_xlabel("Frecuencia [Hz]")
ax.set_ylabel("Magnitud [dB]")
ax.legend()
plt.show()
```

</details>

Ambos niveles de regularización son fracciones del pico de `|H|²`,
generalizando la `regularization` escalar de `impulse_response`:
`regularization_inside` (por defecto `1e-6`) fija con qué exactitud se
aplana la banda, `regularization_outside` (por defecto `1.0`) limita el
refuerzo fuera de banda 6 dB *por debajo* de la unidad en banda, y un fundido
geométrico de `transition_octaves` octavas (por defecto 1/3) conecta ambos
suavemente. El `apply()` del resultado ecualiza cualquier grabación con el
retardo de modelado ya eliminado, y la función acepta directamente un
`ImpulseResponseResult` - su frecuencia de muestreo viaja con él:

```python
import numpy as np
from scipy import signal
from phonometry import (impulse_response, regularized_inverse_filter,
                        sweep_signal)

fs = 48000
sweep = sweep_signal(fs, 50.0, 20000.0, 1.0)
excitation = np.concatenate([sweep, np.zeros(fs // 2)])
b, a = signal.butter(2, [100.0, 8000.0], btype="bandpass", fs=fs)
recorded = signal.lfilter(b, a, excitation)     # en la práctica: emitir/grabar

ir = impulse_response(recorded, excitation, fs)  # vale cualquier frontal
inv = regularized_inverse_filter(ir, f_range=(200.0, 10000.0))
flat_recording = inv.apply(recorded)             # retardo ya eliminado
```

### Cerrando el círculo: preénfasis desde una respuesta medida

Las dos mitades de esta página componen el propio flujo de trabajo de
Mueller y Massarani (su Fig. 18): medir el altavoz, invertir su respuesta en
la banda de transmisión y entregar la magnitud inversa a la síntesis del
barrido como objetivo - la siguiente medición radia entonces un espectro
acústico *plano*, con la ecualización hecha por la excitación en vez de por
un posprocesado que amplifica ruido:

```python
import numpy as np
from scipy import signal
from phonometry import regularized_inverse_filter, shaped_sweep_signal

fs = 48000.0
b, a = signal.butter(2, [100.0, 8000.0], btype="bandpass", fs=fs)
imp = np.zeros(2048)
imp[0] = 1.0
h = signal.lfilter(b, a, imp)                    # la respuesta medida

inv = regularized_inverse_filter(h, fs, f_range=(200.0, 4000.0))
target_db = 20.0 * np.log10(
    np.abs(inv.spectrum[1:]) * np.abs(inv.response_spectrum).max()
)
sweep = shaped_sweep_signal(int(fs), 200.0, 4000.0, 3.0,
                            target=(inv.frequencies[1:], target_db))
```

## Relación con las demás herramientas

El par de Golay se une a `sweep_signal` y `mls_signal` en la
[familia de adquisición ISO 18233](/phonometry/es/guides/room-acoustics/);
elija el barrido por el rechazo de la distorsión, la MLS por hardware
heredado, y el par cuando importe la deconvolución exactamente libre de ruido
de un sistema invariante en el tiempo (bancos de HRTF, útiles de
calibración). El análisis armónico de lo que los barridos *descartan* vive en
[distorsión con seno barrido](/phonometry/es/guides/swept-sine-distortion/).
La maquinaria de Welch usada para verificar el barrido conformado es la
página de [análisis espectral calibrado](/phonometry/es/guides/spectral-analysis/),
y las respuestas ecualizadas alimentan la misma cadena posterior que
cualquier [respuesta al impulso](/phonometry/es/guides/room-acoustics/).
