Leer y escribir audio de medición
Normas aplicables: EBU Tech 3285ITU-R BS.2088RFC 9639ITU-R BS.1770Referencias: Lipshitz et al. 1992
Un WAV de medición solo es interpretable junto a tres cosas que las muestras
no llevan: la frecuencia de muestreo, la calibración que convierte el fondo
de escala digital en pascales, y la procedencia que dice dónde y cuándo se
hizo la grabación. phonometry.io trata todo eso como parte del significado
del archivo. read() devuelve un Signal — muestras más frecuencia,
calibración, etiquetas de canal y procedencia bext en un único objeto
inmutable — y todas las funciones de esta página siguen los mismos valores
por defecto: se conserva la frecuencia de muestreo nativa, los canales no se
mezclan nunca, y ninguna muestra se normaliza jamás, porque cada una de esas
«comodidades» destruye en silencio el nivel o los tiempos de los que depende
una medición.
El tipo es phonometry.io.Signal. Se escribe io.Signal tras
from phonometry import io, o phonometry.Signal, que es la misma clase: el
nivel superior la publica porque siete paquetes la aceptan, y esas son las dos
formas admitidas. El módulo donde está definida es privado, y entrar por ahí
devuelve el mismo objeto, sin que nada garantice que siga siendo alcanzable
así.
La instalación base (NumPy y SciPy, nada más) lee toda la familia WAV lineal
que escribe el equipamiento de medida: PCM a cualquier profundidad, incluido
el de 24 bits, IEEE float, WAVE_FORMAT_EXTENSIBLE con su máscara de
canales, los metadatos BWF bext y las grabaciones RF64/BW64 de más de
4 GiB. El extra [audio] añade FLAC, AIFF, Ogg Vorbis, Opus, MP3 y los
códecs WAV comprimidos que algunos sonómetros usan para copias de escucha.
Nada metrológico necesita el extra.
Leer una medición
Sección titulada «Leer una medición»read() toma una ruta y devuelve el Signal. El archivo se escribe aquí
primero para que la página funcione; en un flujo real viene del instrumento.
import numpy as npfrom phonometry import io
fs = 48000# recording: un sustituto del WAV del sonómetro, sintetizado para que la# guía funcione; en una medición real este archivo viene del sonómetro.rng = np.random.default_rng(11)recording = 0.02 * rng.standard_normal(10 * fs)io.write("measurement.wav", recording, fs, subtype="PCM_24")
sig = io.read("measurement.wav")print(sig.fs, sig.n_channels, f"{sig.duration:.1f} s") # 48000 1 10.0 sprint(sig.source.format_name, sig.source.bit_depth) # PCM 24El objeto sustituye al array desnudo en cualquier parte: np.asarray(sig)
entrega las muestras (1D para un canal, (canales, muestras) para varios),
así que toda función (x, fs, ...) de la biblioteca ya lo admite en lugar
del array, pasando la frecuencia igualmente al lado. Buena parte de la
biblioteca va más allá y acepta el propio Signal: el objeto ya conoce su
frecuencia y su calibración, y pedirte que repitas cualquiera de las dos es
pedir un error de transcripción. El objeto pone la frecuencia cuando se omite
fs, y una fs explícita que discrepe lanza en vez de arbitrarse. Eso abarca las funciones
de nivel (leq, laeq, ln_levels, sel, lc_peak, sound_exposure y
lex_8h), los filtros (octave_filter, weighting_filter, time_weighting,
linkwitz_riley, parametric_eq, y los objetos de proceso por bloques
OctaveFilterBank, WeightingFilter, TimeWeighting y ParametricEQ), y
los estimadores de signals: espectros (power_spectral_density,
cross_spectral_density, coherent_output_spectrum, multitaper_psd,
miso_coherence), tiempo-frecuencia (spectrogram, zoom_fft), correlación
y retardo (correlation, time_delay, impulse_response_delay,
align_impulse_responses), envolvente (envelope, envelope_spectrum),
cepstrum (cepstrum, lifter, echo_detection),
time_synchronous_average, regularized_inverse_filter, resample_signal y
fractional_delay, junto con las pruebas de cualificación de datos de
metrology (stationarity_test, level_crossing_rate, peak_statistics).
Un Signal calibrado se procesa en pascales. Es una sola regla, pero no una
sola unidad: lo que compra la calibración depende de lo que devuelva la
llamada.
| Lo que devuelve la llamada | Unidades con un Signal calibrado |
|---|---|
Una forma de onda: weighting_filter, linkwitz_riley, parametric_eq, envelope, time_synchronous_average, resample_signal, fractional_delay, align_impulse_responses | Pa |
Una envolvente al cuadrado: time_weighting | Pa² |
Un nivel: octave_filter, OctaveFilterBank y las funciones de nivel | dB SPL re 20 µPa |
Una densidad espectral: power_spectral_density, cross_spectral_density, coherent_output_spectrum, multitaper_psd, spectrogram, miso_coherence | Pa²/Hz, o Pa² con scaling='spectrum' |
Una correlación: correlation | Pa² |
Un espectro de amplitud: zoom_fft, envelope_spectrum | Pa |
Un filtro inverso: regularized_inverse_filter | 1/Pa |
Las razones y los tiempos no tienen escala y no se mueven en absoluto: toda
coherencia, todo coeficiente de correlación normalizado, toda fase y todo
retardo en segundos o en muestras, y por eso time_delay,
impulse_response_delay y echo_detection devuelven con un Signal
calibrado exactamente lo mismo que con el registro crudo. El cepstrum es el
caso raro, por ser una transformada del espectro logarítmico: el factor cae
entero sobre la quefrencia cero y deja intacta cualquier otra, así que
lifter desplaza sus dos espectros en dB en 20 lg del factor y no mueve nada
más. level_crossing_rate es el que te pide algo: sus levels se comparan
con las muestras, así que con un Signal calibrado hay que darlos en
pascales.
Las rutas dbfs son la excepción deliberada: su referencia
es el fondo de escala, así que ignoran el factor. Por eso mismo no encadenes un
filtro con una lectura en dBFS: leq(weighting_filter(sig, curve="A"), dbfs=True) mide en pascales una escala que dice ser de fondo de escala, y
sale 20 log10(factor) desviada. Para eso está laeq(sig, dbfs=True), que
pondera la señal cruda antes de leerla.
Las muestras enteras se escalan dividiendo exactamente por para un contenedor de bits: una potencia de dos, así que la conversión a float64 es exacta en coma flotante binaria, y el mismo convenio que libsndfile y MATLAB. El eterno debate 32767 contra 32768 es irrelevante aquí por una razón mejor que el gusto: el tono del calibrador se lee por este mismo lector, así que cualquier constante fija de escalado se cancela exactamente en todo nivel calibrado.
info() responde solo con las cabeceras — no se descodifica ni una muestra,
así que es instantáneo sobre un RF64 de 12 horas, y sigue describiendo un
WAV comprimido para el que read() necesitaría el extra:
meta = io.info("measurement.wav")print(meta.container, meta.format_name, meta.bit_depth) # WAV PCM 24print(meta.frames, f"{meta.duration:.1f} s") # 480000 10.0 sCalibrar desde la toma del calibrador
Sección titulada «Calibrar desde la toma del calibrador»La guía de calibración
deriva el factor de sensibilidad de una grabación del tono del calibrador;
la única regla nueva aquí es que los dos archivos pasan por el mismo
lector. El factor viaja entonces en el Signal y las funciones de nivel
lo usan por su cuenta:
from phonometry import metrology, signals
# calibrator.wav: la toma del calibrador de 94 dB, sintetizada para que la# guía funcione (tono a -10 dBFS RMS); en una sesión real graba tu calibrador.t = np.arange(5 * fs) / fsio.write("calibrator.wav", np.sqrt(2) * 0.316 * np.sin(2 * np.pi * 1000 * t), fs, subtype="PCM_24")
cal_take = io.read("calibrator.wav")cal = metrology.sensitivity(cal_take, target_spl=94.0)print(f"S = {cal:.3f} Pa por unidad de fondo de escala") # 3.172
sig = io.read("measurement.wav", calibration_factor=cal)print(f"Leq = {float(signals.leq(sig)):.1f} dB") # 70.0Sin argumento calibration_factor y sin argumento fs en la llamada de
nivel: el Signal lleva los dos, y cada uno se rige por una regla distinta.
Un calibration_factor explícito sigue ganando al que viene en el objeto —
quien llama sabe más que un objeto, tras una recalibración o para un supuesto
deliberado. Un fs explícito no gana: la frecuencia de muestreo es un hecho
de la grabación y no una preferencia, así que un valor que discrepe del que
lleva el objeto lanza en vez de imponerse, y solo se acepta el que coincide.
Un array desnudo se rechaza nombrando el argumento en vez de
adivinarlo en las funciones que necesitan frecuencia, que no es el caso de
leq: integra el registro entero, así que nunca tuvo un fs que omitir.
Las dos reglas valen para toda función de la biblioteca que consuma un registro, con cuatro exenciones en la mitad de la calibración. Cada una es un caso en el que los pascales serían lo equivocado que entregarle a la función, y cada una lo dice en su propio docstring:
- Referenciado al fondo de escala digital. La familia EBU R 128
(
program_loudnessy sus partes),dynamic_rangeeidle_channel_noisecuentan desde una sinusoide a fondo de escala y no desde 20 µPa. Escalar sus muestras movería cada lectura20 lg(factor)y la seguiría llamando LUFS o dBFS. - No es una presión. Un registro de vibración de cuerpo completo es una aceleración en m/s² y uno de impacto pesado es una fuerza en newtons, así que un factor de unidades digitales a pascales no es una conversión que ninguno de los dos pida.
- Produce el factor.
metrology.sensitivityes de donde sale un factor, como en el fragmento de arriba; aplicar uno antes sería calibrar la calibración. - Una ruta con
dbfs=True, por la misma razón que la primera.
Todo lo demás lo toma, incluidas las métricas submarinas: su referencia de 1 µPa cambia desde dónde se cuenta el decibelio, no en qué unidad tienen que estar las muestras.
La misma cadena sobre el archivo de un sonómetro real
Sección titulada «La misma cadena sobre el archivo de un sonómetro real»El repositorio versiona una toma de calibración real exactamente para este
flujo: tests/data/audio/xl2/calibration_113_7dB.wav, grabada por un
sonómetro NTi Audio XL2 durante una campaña de pasos de tráfico rodado de
la Universidad de Amberes (CC BY 4.0; procedencia, licencia y atribución
en tests/data/audio/README.md). Como instrumento que es, el sonómetro
escribió su propio chunk bext y declaró en él su fondo de escala
digital. Esa declaración más el RMS del archivo es toda la cadena de
calibración, y tiene que caer en el nivel que el dataset publica para
esta toma: 113,7 dB.
import pathlib
import phonometryfrom phonometry import io, signals
# La toma versionada vive en el repositorio, así que esto funciona desde un# checkout (o instalación editable); con el WAV de tu sonómetro, pasa su ruta.repo = pathlib.Path(phonometry.__file__).resolve().parents[2]xl2_take = io.read(repo / "tests/data/audio/xl2/calibration_113_7dB.wav")print(xl2_take.provenance.originator) # NTi Audio XL2 A2A-17367-E0
declaration = xl2_take.provenance.description.splitlines()[0]print(declaration) # 0dBFS = 129.3 dBSPLfull_scale_spl = float(declaration.split()[2])
xl2_cal = 2e-5 * 10 ** (full_scale_spl / 20) # qué es 0 dBFS en pascalesxl2_leq = float(signals.leq(xl2_take, calibration_factor=xl2_cal))print(f"Leq = {xl2_leq:.1f} dB") # 113.7, el nivel publicadoSin sensibilidad tecleada a mano en ninguna parte: el archivo del instrumento llevaba su propio fondo de escala, el lector lo sacó a la superficie, y por el otro extremo salió el 113,7 dB publicado. Una grabación hecha con el mismo sonómetro se calibra con las mismas dos líneas — por eso la única regla que importa es que los dos archivos pasen por el mismo lector.
La figura de abajo es esta página en una imagen: la misma API escribió un
BWF de 24 bits con su chunk bext y su sidecar de calibración, lo volvió a
leer, y el Signal resultante dibujó su forma de onda calibrada junto a la
tarjeta de lo que viajó con las muestras.


Lo que read() devuelve además de muestras. La forma de onda está en
pascales porque la calibración del sidecar se aplicó al leer; la tarjeta es
la procedencia bext de la EBU Tech 3285 — quién lo grabó, cuándo y en qué
muestra desde medianoche — más los datos del contenedor y la fuente de la
calibración. Nada de eso hubo que teclearlo de nuevo.
Mostrar el código de esta figura
import matplotlib.pyplot as pltimport numpy as npfrom phonometry import io
fig_fs = 48000fig_rng = np.random.default_rng(2026)night = 0.0012 * fig_rng.standard_normal(10 * fig_fs)for start_s, length_s, amplitude in ((1.6, 2.2, 0.011), (4.9, 1.4, 0.020), (7.8, 1.8, 0.008)): piece = slice(int(start_s * fig_fs), int(start_s * fig_fs) + int(length_s * fig_fs)) envelope = np.hanning(int(length_s * fig_fs)) night[piece] += amplitude * envelope * fig_rng.standard_normal(envelope.size)
bext = io.BroadcastMetadata( description="Night monitoring, facade position P3", originator="Hand-held class 1 analyzer", originator_reference="ESPHN20260621023705000012", origination_date="2026-06-21", origination_time="02:37:05", time_reference=(2 * 3600 + 37 * 60 + 5) * fig_fs, version=2, umid=None, loudness_value=None, loudness_range=None, max_true_peak_level=None, max_momentary_loudness=None, max_short_term_loudness=None, coding_history="A=PCM,F=48000,W=24,M=mono,T=SLM",)io.write("night_p3.wav", io.Signal(data=night, fs=fig_fs, calibration_factor=20.0), subtype="PCM_24", bext=bext, sidecar=True)
night_sig = io.read("night_p3.wav") # calibrado por el sidecarax = night_sig.plot(language="es") # pascales, porque está calibradoprint(night_sig.provenance.originator, night_sig.provenance.origination_time)plt.show()El aviso de pérdidas, y la historia del XL2 que hay detrás
Sección titulada «El aviso de pérdidas, y la historia del XL2 que hay detrás»Por defecto un NTi XL2 graba WAV — comprimido a ADPCM de 4 bits. El manual es explícito sobre la intención: la grabación comprimida es un registro de escucha, y el WAV lineal (con el Extended Acoustic Pack) es lo «necesario para el posprocesado en el PC». La distinción importa porque un descodificador con pérdidas reconstruye una aproximación de la forma de onda: un nivel calculado a partir de ella no es defendible como medición, por plausible que parezca el número.
Así que las fuentes con pérdidas se leen, no se rechazan — una copia de escucha sigue mereciendo abrirse — pero nunca en silencio:
import soundfile as sf
# El aspecto de la copia de escucha por defecto de un sonómetro: IMA ADPCM# de 4 bits en un contenedor WAV. Escrita aquí con soundfile para tener una# que leer.sf.write("voice_note.wav", np.asarray(sig)[: 2 * fs], fs, subtype="IMA_ADPCM")
note = io.read("voice_note.wav") # emite LossyCompressionWarningprint(note.source.lossy) # True: el hecho sobrevive al avisoEl LossyCompressionWarning salta igual con MP3, Ogg Vorbis, Opus y los
códecs WAV comprimidos, y source.lossy queda estampado en la señal para
que el hecho sobreviva cuando el aviso ya se haya perdido de vista.
Escribir formatos con pérdidas no está en la API en absoluto: no hay ningún
motivo de medición para producir uno, y una biblioteca cuyo trabajo es
conservar niveles no debería ponerlo fácil.
Dos casos vecinos que el módulo no cubre a propósito: el AAC/M4A de las
grabadoras de voz, donde la vía honesta es una conversión externa de una
línea (ffmpeg -i note.m4a note.wav, y después read("note.wav") — con la
misma advertencia, porque AAC también es con pérdidas); y los formatos de
adquisición que no son audio (TDMS del hardware de LabVIEW, UFF del
análisis modal), que tienen sus propios lectores en Python (npTDMS,
pyuff).
Una grabación nocturna, por bloques
Sección titulada «Una grabación nocturna, por bloques»Una noche de monitorado a 48 kHz y 24 bits son gigabytes; como float64 son
~5,5 GiB por hora estéreo, que ningún análisis necesita en memoria a la
vez. read_blocks() entrega bloques float64 desnudos, no objetos Signal:
los mismos valores de muestra que np.asarray(read(...)), con el mismo
escalado y el mismo convenio de canales (en la instalación base, con
posicionamiento y descodificación propios para el WAV lineal, RF64
incluido), pero sin calibración ni procedencia a bordo. El factor de
calibración se aplica donde se calcula el nivel, como hace el bucle de
abajo; los filtros con estado de la guía de
proceso por bloques
consumen el flujo sin cambios:
from phonometry import filters
weighter = filters.WeightingFilter(fs, "A", stateful=True)total_energy = 0.0total_samples = 0for block in io.read_blocks("measurement.wav", block_size=1 << 16): weighted = weighter.filter(block) total_energy += float(np.sum(weighted ** 2)) total_samples += weighted.shape[-1]
streamed = 10 * np.log10((cal ** 2 * total_energy / total_samples) / (2e-5) ** 2)whole = float(signals.leq( filters.weighting_filter(np.asarray(sig), fs, curve="A"), calibration_factor=cal))print(f"por bloques {streamed:.4f} dB, archivo entero {whole:.4f} dB")# por bloques 66.4200 dB, archivo entero 66.4200 dBLos dos números no se parecen: son idénticos, hasta el último bit, porque
un filtro con estado arrastra su estado a través de las fronteras de bloque
y el flujo aporta cada muestra exactamente una vez. Ambos lados usan el mismo
diseño por defecto, ya que el procesado por bloques ya no le cuesta nada al
filtro de ponderación (la
guía de ponderación tiene el
detalle). La misma construcción alimenta un OctaveFilterBank con
BlockProcessing(stateful=True), banda a banda.
Escribir un BWF con su procedencia
Sección titulada «Escribir un BWF con su procedencia»write() produce WAV/BWF — PCM de 16, 24 (empaquetado en casa; SciPy no
sabe), 32 bits, float de 32/64 — y promociona a RF64 automáticamente
pasados los 4 GiB. Tres promesas, cada una lo contrario de un valor por
defecto documentado del ecosistema: nunca normaliza, nunca remuestrea, y el
recorte nunca es silencioso — las muestras que superan el fondo de escala en
un subtipo entero se saturan, se cuentan y se comunican con un
ClippingWarning que nombra el recuento y el exceso de pico en dBFS. El
dither="tpdf" opcional añade dither triangular de un LSB al cuantizar a
PCM_16 — la receta de Lipshitz, Wannamaker y Vanderkooy para copias de
escucha de 16 bits — y se rechaza en el resto, porque el dither solo añade
ruido a datos que van a análisis numérico y no a oídos.
La procedencia es de primera clase. El bext propio de un Signal se
transporta por defecto cuando lo tiene; un BroadcastMetadata que
construyas se escribe campo a campo en sus posiciones de la EBU Tech 3285;
y bext="loudness" mide además los cinco valores de sonoridad de la
versión 2 — sonoridad de programa, rango de sonoridad, pico verdadero
máximo, y los máximos momentáneo y a corto plazo — con la
implementación propia de la ITU-R BS.1770
sobre las muestras que se están escribiendo. El CodingHistory de todo
chunk escrito se extiende, nunca se sustituye, con la línea en formato
EBU R98 que describe este paso de codificación.
out = io.Signal(data=np.asarray(sig), fs=fs, calibration_factor=cal)io.write("delivery.wav", out, subtype="PCM_24", bext="loudness", sidecar=True)
delivered = io.info("delivery.wav")print(delivered.bext.loudness_value) # -30.85 (LUFS, medido)print(delivered.bext.max_true_peak_level) # -20.08 (dBTP, medido)El sidecar: cómo viaja la calibración
Sección titulada «El sidecar: cómo viaja la calibración»Ningún contenedor de audio tiene un campo para la calibración del
micrófono — bext no lo tiene, y sus valores de sonoridad son sonoridad de
programa, no sensibilidad. La respuesta de la industria son los compañeros
propietarios (el informe de texto de un sonómetro, el archivo de proyecto de
un fabricante); la de la sismología, un sidecar estandarizado junto a la
forma de onda, es la que merece copiarse. write(sidecar=True) (o
write_sidecar() para un archivo ya existente) deja un pequeño JSON
versionado en <audio>.phonometry.json con el factor de calibración, los
metadatos del tono del calibrador y las etiquetas de canal, y read() lo
aplica automáticamente — un argumento explícito sigue ganando, y un sidecar
escrito por un esquema más nuevo o por otra herramienta se rechaza a las
claras en vez de entenderse a medias.
again = io.read("delivery.wav") # esta vez sin argumentosprint(f"{again.calibration_factor:.3f}") # 3.172, del sidecarprint(f"Leq = {float(signals.leq(again)):.1f} dB") # 70.0: el nivel sobrevivió al discoEse viaje de ida y vuelta es el sentido de toda la página: escribe una señal calibrada, léela de vuelta sin argumentos, y el nivel absoluto está intacto.
Convertir sin que la medición deje de serlo
Sección titulada «Convertir sin que la medición deje de serlo»convert() mueve un archivo entre contenedores sin pérdidas con todo
intacto: las muestras a precisión completa (un viaje WAV de 24 bits a FLAC
y de vuelta devuelve códigos idénticos al bit), el chunk bext
transportado — dentro de FLAC viaja en un bloque APPLICATION que los
lectores de aquí entienden — el sidecar copiado byte a byte, y una línea
añadida al CodingHistory que nombra el paso de codificación. Los destinos
con pérdidas se rechazan por decisión. La conversión va en flujo, así que
una hora de RF64 se convierte con memoria constante:
io.convert("delivery.wav", "delivery.flac")
flac = io.read("delivery.flac")print(flac.provenance is not None) # Trueprint(float(signals.leq(flac)) == float(signals.leq(again))) # True: idénticoLa igualdad es un == entre floats y se cumple: FLAC guarda los mismos
códigos enteros, el lector los escala por la misma potencia de dos, y el
nivel archivado es idéntico, no meramente parecido.
Qué lee y escribe cada instalación
Sección titulada «Qué lee y escribe cada instalación»| Instalación base (NumPy + SciPy) | Con pip install phonometry[audio] | |
|---|---|---|
| Leer | WAV/BWF: PCM a cualquier profundidad, IEEE float de 32/64, EXTENSIBLE (etiquetas de canal), bext, puntos de cue, RF64/BW64 | + FLAC, AIFF, Ogg Vorbis, Opus, MP3, WAV comprimido (ADPCM, ley A/µ) |
| Escribir | WAV/BWF: PCM de 16/24/32, float de 32/64, RF64 automático, bext, sidecar | + FLAC (PCM hasta 24 bits, bext transportado) |
| Por bloques | read_blocks sobre WAV lineal (RF64 incluido) | + read_blocks sobre todo formato legible |
info() | Toda la familia WAV, comprimidos incluidos | + los formatos del extra |
El extra es python-soundfile, cuyo wheel empaqueta libsndfile bajo LGPL-2.1 (enlazada dinámicamente, el mismo patrón que librosa y torchaudio); la instalación base se queda solo con BSD/MIT, que es por lo que todos los formatos metrológicos viven ahí.
Parámetros de read() y write()
Sección titulada «Parámetros de read() y write()»| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
read(path) | str / Path | — | El despacho es por los bytes mágicos iniciales, no por la extensión: un archivo mal etiquetado se despacha por lo que es |
read(..., calibration_factor=) | float, opcional | None | Multiplicador de unidades digitales a pascales; None toma el factor de un sidecar existente, y si no, la señal queda en unidades de fondo de escala |
write(path, x, fs) | — | — | x es un Signal (entonces fs puede omitirse, y uno explícito que discrepe lanza error) o un array en orden (canales, muestras) |
write(..., subtype=) | str, opcional | datos float "FLOAT", datos enteros su propia profundidad | "PCM_16", "PCM_24", "PCM_32", "FLOAT", "DOUBLE"; los destinos FLAC admiten PCM hasta 24 |
write(..., bext=) | BroadcastMetadata / "loudness" | None | None transporta igualmente la procedencia propia de un Signal; "loudness" mide en casa los cinco campos de la R 128 (una pasada extra, por eso es opcional) |
write(..., dither=) | "tpdf", opcional | None | Solo con subtype="PCM_16"; rechazado en el resto |
write(..., sidecar=) | bool | False | Exige un Signal calibrado: un sidecar sin calibración sería una promesa sin nada detrás |
Respuestas rápidas
Sección titulada «Respuestas rápidas»¿Por qué el WAV de mi sonómetro se lee como números diminutos en vez de pascales?
Sección titulada «¿Por qué el WAV de mi sonómetro se lee como números diminutos en vez de pascales?»Porque un WAV no lleva calibración: las muestras vuelven en unidades de
fondo de escala digital (±1,0), y solo una toma del calibrador grabada por
la misma cadena las convierte en pascales. Lee el archivo del calibrador con
read(), deriva el factor con sensitivity() y pásalo como
calibration_factor= — o escríbelo una vez en el sidecar y deja que
read() lo aplique desde entonces.
¿Puedo calcular un Leq de la nota de voz WAV comprimida del sonómetro?
Sección titulada «¿Puedo calcular un Leq de la nota de voz WAV comprimida del sonómetro?»Puedes hacer la llamada, y la biblioteca responderá — tras un
LossyCompressionWarning, con source.lossy estampado en la señal. El
número no es defendible: un descodificador ADPCM o MP3 reconstruye una
aproximación de la forma de onda, y el error depende del programa. Usa el
modo de grabación lineal del sonómetro para todo lo que produzca un nivel.
¿Necesito el extra [audio] para leer mis grabaciones?
Sección titulada «¿Necesito el extra [audio] para leer mis grabaciones?»Para mediciones, casi seguro que no: la instalación base lee todas las variantes de WAV lineal que escriben sonómetros y grabadores de campo, incluidos los de 24 bits, el multicanal EXTENSIBLE y los RF64 de más de 4 GiB. El extra es para archivos FLAC, AIFF/Ogg/Opus/MP3 y las copias WAV comprimidas de escucha — y empaqueta libsndfile bajo LGPL, que la instalación base evita a propósito.
Referencias
Sección titulada «Referencias»- European Broadcasting Union. (2011). Specification of the Broadcast Wave Format (BWF) — A format for audio data files in broadcasting, Version 2.0 (EBU Tech 3285). El chunk bext que esta página lee y escribe campo a campo: originator, la referencia temporal desde medianoche exacta a la muestra, CodingHistory y los cinco valores de sonoridad EBU R 128 de la versión 2.
- International Telecommunication Union. (2023). Algorithms to measure audio programme loudness and true-peak audio level (Recommendation ITU-R BS.1770-5). El algoritmo de sonoridad tras bext="loudness": los cinco campos de la versión 2 se miden con la implementación propia de la biblioteca sobre las muestras que se están escribiendo.
- International Telecommunication Union. (2025). Long-form file format for the international exchange of audio programme materials with metadata (Recommendation ITU-R BS.2088-2). El contenedor de 64 bits RF64/BW64 al que una grabación nocturna se promociona pasados los 4 GiB, leído aquí a través de sus tamaños ds64; sustituyó a la EBU Tech 3306.
- Internet Engineering Task Force. (2024). Free Lossless Audio Codec (RFC 9639). El códec sin pérdidas al que esta página archiva: muestras enteras exactas al bit hasta 32 bits, que es lo que permite que un viaje WAV a FLAC y de vuelta a WAV devuelva códigos idénticos.
- Lipshitz, S. P., Wannamaker, R. A. y Vanderkooy, J. (1992). Quantization and dither: A theoretical survey. Journal of the Audio Engineering Society, 40(5), 355-375. Por qué el dither opcional es triangular de un LSB, por qué se ofrece solo al cuantizar a 16 bits, y por qué es opcional para datos que van a análisis numérico.