Ir al contenido
Esta documentación describe la versión 4.0.0, todavía sin publicar. La versión actual en PyPI es la 3.3.0 y no incluye todo lo que se describe aquí.

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.

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 np
from 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 s
print(sig.source.format_name, sig.source.bit_depth) # PCM 24

El 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 llamadaUnidades con un Signal calibrado
Una forma de onda: weighting_filter, linkwitz_riley, parametric_eq, envelope, time_synchronous_average, resample_signal, fractional_delay, align_impulse_responsesPa
Una envolvente al cuadrado: time_weightingPa²
Un nivel: octave_filter, OctaveFilterBank y las funciones de niveldB SPL re 20 µPa
Una densidad espectral: power_spectral_density, cross_spectral_density, coherent_output_spectrum, multitaper_psd, spectrogram, miso_coherencePa²/Hz, o Pa² con scaling='spectrum'
Una correlación: correlationPa²
Un espectro de amplitud: zoom_fft, envelope_spectrumPa
Un filtro inverso: regularized_inverse_filter1/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 24
print(meta.frames, f"{meta.duration:.1f} s") # 480000 10.0 s

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) / fs
io.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.0

Sin 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_loudness y sus partes), dynamic_range e idle_channel_noise cuentan desde una sinusoide a fondo de escala y no desde 20 µPa. Escalar sus muestras movería cada lectura 20 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.sensitivity es 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 phonometry
from 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 dBSPL
full_scale_spl = float(declaration.split()[2])
xl2_cal = 2e-5 * 10 ** (full_scale_spl / 20) # qué es 0 dBFS en pascales
xl2_leq = float(signals.leq(xl2_take, calibration_factor=xl2_cal))
print(f"Leq = {xl2_leq:.1f} dB") # 113.7, el nivel publicado

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

Diez segundos de una medición nocturna calibrada dibujados en pascales junto a una tarjeta de procedencia con el archivo, el contenedor, la frecuencia de muestreo, el originador, la fecha y hora de origen, la referencia temporal exacta a la muestra, el historial de codificación y la calibración del sidecarDiez segundos de una medición nocturna calibrada dibujados en pascales junto a una tarjeta de procedencia con el archivo, el contenedor, la frecuencia de muestreo, el originador, la fecha y hora de origen, la referencia temporal exacta a la muestra, el historial de codificación y la calibración del sidecar

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 plt
import numpy as np
from phonometry import io
fig_fs = 48000
fig_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 sidecar
ax = night_sig.plot(language="es") # pascales, porque está calibrado
print(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 LossyCompressionWarning
print(note.source.lossy) # True: el hecho sobrevive al aviso

El 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 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.0
total_samples = 0
for 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 dB

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

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)

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 argumentos
print(f"{again.calibration_factor:.3f}") # 3.172, del sidecar
print(f"Leq = {float(signals.leq(again)):.1f} dB") # 70.0: el nivel sobrevivió al disco

Ese 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) # True
print(float(signals.leq(flac)) == float(signals.leq(again))) # True: idéntico

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

Instalación base (NumPy + SciPy)Con pip install phonometry[audio]
LeerWAV/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/µ)
EscribirWAV/BWF: PCM de 16/24/32, float de 32/64, RF64 automático, bext, sidecar+ FLAC (PCM hasta 24 bits, bext transportado)
Por bloquesread_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ámetroTipoPor defectoNotas
read(path)str / PathEl 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, opcionalNoneMultiplicador 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, opcionaldatos 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"NoneNone 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", opcionalNoneSolo con subtype="PCM_16"; rechazado en el resto
write(..., sidecar=)boolFalseExige un Signal calibrado: un sidecar sin calibración sería una promesa sin nada detrás

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