Acouvero
Descargar
TECHNICAL DOC · v0.3.1

Acústica de salas,
de la medición
al plan.

Acouvero es una herramienta de análisis acústico de salas y planificación de tratamiento para iPhone / iPad. Este documento es para lectores interesados en la ingeniería acústica o la implementación cliente: algoritmos, sistema geométrico, métodos de medición, motor de recomendación y notas de ingeniería.

Bandas de octava
6bandas
Formas
4geometrías
Reflexiones
1–3órdenes
§ 01

Visión general

Entrada: el usuario introduce dimensiones / forma / materiales, posiciones de altavoces y de escucha — o la geometría se captura con LiDAR (RoomPlan).

Salida
  • Localización de reflexiones tempranas
  • Conjunto completo RT60 / EDT / C50 / C80 / D50 (medido + simulado)
  • Lista de frecuencias modales
  • Plan de paneles (posición, cantidad, SKU)
  • Visualización 3D + mapa de energía de reflexión
  • Exportación PDF nivel manual de instalación
  • Medición con el micrófono del iPhone (barrido / palmada)

Se admiten salas rectangulares, en L, en U y polígonos simples arbitrarios; los cálculos usan el polígono real, no su caja envolvente.

§ 02

Modelo acústico

2.1 Sabine RT60

La fórmula clásica de Sabine:

SABINE · RT60 $$ RT_{60} = \frac{0.161 \cdot V}{\sum_i S_i \alpha_i} $$
$V$
Volumen (m³); para polígonos, área de Shoelace × altura, menos obstáculos (columnas / vigas).
$\sum_i S_i \alpha_i$
Superficie × coeficiente de absorción por cara. Coeficientes en 6 bandas (125 / 250 / 500 / 1k / 2k / 4k Hz).
$0.161$
= 24·ln(10)/c, con c = 343 m/s (20 °C).

La app incluye 5 preajustes de acabado (vacío / duro / mobiliario / ya tratado / estudio), cada uno con curva por banda. El RT60 objetivo también es una curva de 6 bandas, acorde a normas como EBU Tech 3276 / THX que permiten una cola más larga en graves.

2.2 Método de fuentes imagen (ISM)

Las primeras reflexiones usan espejo: se refleja la fuente respecto al muro; la intersección del segmento «fuente espejo → oyente» con el muro es el punto de reflexión.

  • Sala rectangular: espejo en los 6 planos — solución cerrada.
  • No rectangular (PolygonReflectionCalculator): espejo en cada segmento de RoomPolygon.walls(), validar s ∈ [0, 1] y que el XY esté dentro del polígono.

Las reflexiones multi-orden (1–3) las implementa MultiOrderReflectionCalculator según el enfoque recursivo de pyroomacoustics:

  1. Enumerar todas las secuencias de muros [s₁, …, sₙ], muros adyacentes distintos
  2. Iteración de fuente imagen: S₀ = fuente, Sₖ = mirror(Sₖ₋₁, sₖ)
  3. Trazado inverso desde el oyente: Sₙ → oyente corta sₙ en Pₙ; Sₙ₋₁ → Pₙ corta sₙ₋₁ en Pₙ₋₁; y así sucesivamente
  4. Cada intersección debe estar en el rango válido del muro y t ∈ (0, 1)
APROXIMACIÓN En salas no rectangulares, el multi-orden aproxima por la caja envolvente («reflexiones fantasma» cerca de esquinas cóncavas). El análisis descarta toda ruta cuyo punto esté a > 0,3 m de cada segmento real; solo las rutas válidas alimentan la detección de flutter / slapback.

2.3 Energía de trayectos

Energía de banda ancha según número de reflexiones y atenuación por distancia:

PATH ENERGY $$ E_{rel} = (1 - \bar{\alpha})^N \cdot \frac{d_{direct}^2}{d_{total}^2} $$
  • α se acota a [0, 0.99] para que α = 1 no produzca log(0) ni pow(negativo, N) = NaN
  • La atenuación sigue la divergencia esférica 1/r²
  • Conversión a dB con 10·log₁₀ (dominio de energía)

La energía por banda se calcula para las 6 bandas (coeficiente de finishProfile.absorptionByBand).

2.4 Estimación de RT60 (medida)

ImpulseResponseEstimator:

  • Filtrado adaptado: el barrido grabado de 0,45 s se filtra inverso (ESS / Farina) a un impulso, ~+40 dB de SNR efectiva
  • rmsEnvelope: envolvente RMS de ventana deslizante
  • Integración inversa de Schroeder: E_back(t) = ∫ₜ^∞ s²(τ) dτ, normalizada con 10·log₁₀ da la curva EDC
  • Regresión lineal de pendiente: T30 [−5, −35 dB] según SNR, con respaldo T20 [−5, −25] o T10 [−5, −15]
  • RT₆₀ = −60 / pendiente

RT60 por banda: sobre las muestras filtradas, paso de banda en centros ISO (250 / 500 / 1k / 2k / 4k), Q = √2, y Schroeder por banda.

2.5 Simulación de RIR

SimulatedRIRGenerator convierte las rutas multi-orden en impulsos discretos escritos en un búfer PCM de 8 kHz × 0,5 s:

SimulatedRIRGeneratorpseudocode
for path in reflectionPaths:
    t   = path.totalDistance / speedOfSound
    amp = 10^(path.relativeEnergyDB / 20)
    impulse[round(t * sampleRate)] += amp

Añadir el impulso del sonido directo y aplicar integración de Schroeder para EDT / RT60 / C50 / C80 / D50.

Comparación antes / después Cada ruta se atenúa por los paneles de los muros que toca, generando un RIR «tratado». La UI alterna A/B para mostrar la mejora de EDT / C50, y AVAudio convoluciona una señal seca para comparar de oído.

2.6 Modos propios

Frecuencias modales de una sala rectangular:

ROOM MODES $$ f_{n_x, n_y, n_z} = \frac{c}{2} \sqrt{\left(\frac{n_x}{L_x}\right)^2 + \left(\frac{n_y}{L_y}\right)^2 + \left(\frac{n_z}{L_z}\right)^2} $$

Clasificados según cuántos de nₓ, n_y, n_z son no nulos: 1 = axial, 2 = tangencial, 3 = oblicuo.

Las salas no rectangulares estiman los modos con la caja envolvente (los modos exactos requieren FEM); se indica en la UI.

§ 03

Sistema geométrico

3.1 Formas admitidas

  • Rectángulo — ancho × largo × alto
  • Forma en L — quita una muesca nw × nl en una esquina
  • Forma en U — quita una muesca nw × nl en el centro de un muro
  • Personalizado — cualquier polígono simple (≥ 3, ≤ 24 vértices)

Todas las formas no rectangulares pasan a una lista de vértices CCW vía RoomPolygon.from(shape:, bboxWidth:, bboxLength:). El cálculo usa estos vértices, no la caja envolvente.

3.2 Utilidades de geometría poligonal

  • Área — fórmula de Shoelace
  • Perímetro — suma de longitudes de aristas
  • contains(x, y) — ray-casting con epsilon de borde (evita falsos negativos en esquinas)
  • clamped(point) — los rectángulos usan la vía rápida [0, w] × [0, l]; los demás el verdadero [minₓ, maxₓ] × [min_y, max_y] de polygon.boundingBox, prueban polygon.contains y búsqueda binaria hacia el centro si está fuera
  • walls() — divide el polígono en segmentos clasificados por normal interior (leftWall / rightWall / frontWall / backWall), reutilizando el recomendador rectangular
  • PolygonValidator.isSimple — valida un polígono simple sin autointersección

3.3 Escaneo RoomPlan

En iOS 17+ se usa RoomCaptureSession y CapturedRoomMapper mapea el escaneo a ScannedRoom (wallSegments + openings + furniture). Mapeo de coordenadas:

ARKit (x, y, z) → y es vertical App (x, y, z) → z es vertical Extremos de muro (z=0 suelo) App.x = ARKit.x App.y = ARKit.z App.z = 0 Centro de hueco / mueble  App.x = ARKit.x App.y = ARKit.z App.z = ARKit.y

Las salas no rectangulares escaneadas pasan por RectangularRoomSimplifier.polygonShapeFromScan, encadenando wallSegments en vértices. Los extremos de RoomPlan erran 3–8 cm; un bucle de 12 segmentos puede acumular 5 cm, por eso el epsilon de cierre es 12 cm. Si no cierra, recurre a la caja envolvente y pide reescanear.

3.4 Editor de vértices libres

FreeVertexEditorView permite arrastrar vértices en un Canvas de SwiftUI:

  • Mantén pulsado para borrar (con ≥ 3 vértices)
  • El arrastre guarda el último estado válido en dragSnapshot; una posición con autointersección revierte en silencio
  • Ajuste a rejilla de 0,05 m
  • Un margen de max(0,5, bbox × 0,2) m fuera de la bbox permite arrastrar vértices más allá (la vista padre agranda room.width / length)
§ 04

Zonas de escucha y multi-oyente

AcousticConfiguration.listeners admite 1..N posiciones de escucha. El análisis ejecuta el método de imágenes por oyente y fusiona los puntos en un array (el oyente principal primero). El recomendador pondera cada muro por su número de puntos (cap = max(6, speakerCount × listenerCount)); varios asientos compartiendo un muro lo empujan al tope del presupuesto.

Visual de UI
  • En 3D, los oyentes secundarios: opacidad 0,45 + escala 0,75
  • Vista superior (RoomPlanView): puntos primarios a todo color r=5, secundarios opacidad 0,45 r=3
  • La lista (RecommendationView) muestra solo puntos primarios; nota al pie «+ N reflexiones ya incluidas»

Las reflexiones multi-orden / RIR / mapa se calculan una vez para el oyente principal (O(speakers × surfaces³) por oyente es demasiado, y el RIR es de un solo punto).

§ 05

Motor de recomendación

PanelPlacementRecommender produce [PanelRecommendation], cada uno con: posición (muro izq. / der. / techo / frontal / trasero / esquina), cantidad, prioridad (esencial / fuerte / recomendado / opcional), effectivenessMultiplier, targetReflectionPointId.

5.1 Reparto proporcional (mayor resto)

El antiguo add() voraz dejaba muros sin nada: el izquierdo agotaba el presupuesto → el derecho 0; con presupuesto ajustado también techo / frontal / trasero. La nueva versión usa el método del mayor resto:

  • Calcular el «raw desired» por muro (nº de puntos + profundidad + caso de uso)
  • floor(raw × budget / total) como asignación inicial
  • los muros con raw > 0 reciben ≥ 1 panel (ningún muro con demanda queda sin nada)
  • si la suma > presupuesto, recortar desde el menor resto fraccionario
  • pase de simetría izq./der.: si raw[0] == raw[1], forzar alloc[0] == alloc[1]
  • redistribuir el sobrante según la mayor fracción

5.2 Plantillas por caso de uso

Sin altavoces colocados, se aplican plantillas por caso de uso:

  • Sala de reuniones: techo 35 % + muro trasero 20 % + cada muro lateral 12 %
  • Pódcast: justo frente al micro 25 % + cada lado 18 % + techo 20 %
  • Estudio: amortiguación simétrica en 4 muros + 4 trampas de graves en esquina forzadas
  • Escucha general: base simétrica + aviso para añadir altavoces

5.3 Las necesidades físicas saltan el presupuesto

Las trampas de graves de esquina se fuerzan a 4 cuando los modos 80–150 Hz son densos, saltando el tope totalPanel. Razón: en las esquinas la presión es máxima y las trampas son ~1,5× más eficientes — necesidad física.

5.4 Ponderación cercana al oyente

Una pasada final marca las recomendaciones a menos de 1,5 m de un oyente como .nearListener, effectivenessMultiplier ×1,15, y lo indica en el título / motivo.

§ 06

Medición

6.1 Barrido ESS

Según el método de Farina:

  • Barrido log. 180 Hz → 4 kHz, 0,45 s
  • Filtro inverso (makeInverseSweep) = inversión temporal + compensación 1/freq
  • Grabación con AVAudioRecorder a AAC m4a (44,1 kHz mono)
  • Lectura → filtrado adaptado → analyzeImpulse
Protección de muestreo Si la tasa real ≠ 44,1 kHz (interfaces USB-C / algunas configs Bluetooth), se omite el filtrado adaptado y la UI avisa de baja fiabilidad, sugiriendo desconectar el equipo externo.

6.2 Impulso de palmada

Sin reproducción — el usuario da una palmada fuerte y se graban 3 s de cola. Va directo a analyzeImpulse (sin filtro adaptado; una palmada ya es un impulso estrecho). Cubre la banda de 125 Hz que el barrido (desde 180 Hz) no alcanza.

6.3 Medido vs simulado

MeasuredVsSimulatedCard toma el último RT60 medido y muestra la desviación frente al RT60 simulado:

< 15%el modelo y la medición coinciden
15–30%cierta divergencia (mobiliario / materiales no modelados)
> 30%gran divergencia — revisar geometría / materiales

6.4 Ruido ambiente y calibración

AmbientNoiseEstimator graba un segmento en silencio para el ruido de fondo dBFS / espectro y evaluar el SNR. DelayCalibrationService hace reproducción + grabación en campo cercano para la latencia reproducción-grabación del iPhone (80–150 ms).

§ 07

Visualización y salida

7.1 Vista 3D

Room3DController renderiza con SceneKit: muros / suelo / techo / altavoces (caja fina + marca + píldora de canal) / oyente (glifo FE + tallo) / obstáculos (SCNBox translúcido) / paneles recomendados / segmentos de rutas multi-orden / superposición de mapa de energía.

DISEÑO · DIBUJO TÉCNICO Estilo «dibujo técnico» (aristas finas, iluminación constante, sin PBR), distinto del 3D fotorrealista.

7.2 Mapa de energía de reflexión

ReflectionEnergyMapGenerator acumula la energía de cada ruta en la rejilla uCells × vCells del muro (12×6 por defecto). Un mapa por muro, superpuesto en 3D (azul → rojo).

EnergyHeatmapRenderer convierte la rejilla en UIImage: sobremuestreo bicúbico + colormap Magma + alfa según energía absoluta.

7.3 Vista previa por convolución RIR

RIRPreviewPlayer (AVAudioEngine + AVAudioPlayerNode) convoluciona cualquier señal seca (voz / batería / piano) con el RIR simulado para oír «sin tratar vs tratado».

Convolución temporal RIRConvolver.convolve(dry:rir:) directa O(N × M) (N = muestras secas, M = RIR), normalizada a pico 0,9 para evitar recorte.

7.4 Manual de instalación PDF

ReportExporter genera un PDF A4 (595 × 842 pt) con UIGraphicsPDFRenderer:

  • Resumen de sala + captura 3D
  • Gráfico de barras RT60 de 6 bandas medido vs objetivo
  • Tabla de puntos + tabla de modos + resumen de riesgos
  • Tabla de paneles + lista de compra (BOM)
  • Fuente Geist + respaldo PingFangSC (CoreText cascadeList, para que el CJK no salga como □□□)

7.5 Persistencia de proyectos

ProjectStore (actor) escribe ~/Library/Application Support/AcouVero/projects.json: [RoomProject], cada uno con Room + altavoces + oyentes + recomendaciones + panelCountOverrides.

MeasurementStore (actor) escribe measurements.json: [MeasurementResult], vinculado al proyecto por roomId.

Ambos usan recuperación de decodificación por elemento (envoltorio Throwable<T>) — un registro corrupto no hace fallar toda la lista.

§ 08

Notas de ingeniería

8.1 SwiftUI + @Observable

El ViewModel (AcouVeroViewModel) usa la macro @Observable de Swift 5.9 — campos observados directamente, sin Combine. Los resultados de AcousticAnalysis se cachean por comparación Equatable de AcousticConfiguration:

AcouVeroViewModel.analysisswift
var analysis: AcousticAnalysis {
    let cfg = configuration
    if let cached = cachedAnalysis, cachedConfiguration == cfg {
        return cached
    }
    let result = analysisService.analyze(cfg)
    cachedConfiguration = cfg
    cachedAnalysis = result
    return result
}

8.2 Aislamiento por actor

ProjectStore, MeasurementStore y RoomPlanService son actors, que serializan IO / async de forma natural. La UI usa async / await; el hilo principal nunca se bloquea por IO de disco.

8.3 Ciclo de vida de AVAudio

AudioSessionManager centraliza la categoría de sesión (.playback para la vista previa RIR, .playAndRecord para la medición). AVAudioSession.interruptionNotification escucha llamadas / Siri y detiene el motor, limpiando isPlaying.

8.4 Cancelación de tareas

La reproducción por convolución es una tarea de fondo; los toques rápidos disparan pendingPlayTask?.cancel() más una doble verificación Task.isCancelled para evitar escrituras desordenadas en MainActor.

8.5 Cobertura de pruebas

AllShapesAndUseCasesTests usa el framework Swift Testing para la matriz completa — cada forma × caso de uso × combinaciones de oyente / altavoz — y comprueba:

  • analysis.reflectionPaths.count >= 1
  • analysis.warnings incluye la nota de aproximación para salas no rectangulares
  • el total de recomendaciones coincide con el presupuesto
  • el asignador garantiza sum ≤ presupuesto y simetría
  • cierre del polígono / vértices &lt; 3 y otros casos límite
§ 09

Limitaciones conocidas

  1. Multi-orden en salas no rectangulares usa la caja envolvente — el riesgo de flutter / slapback de 2.º/3.º orden cerca de esquinas cóncavas puede desviarse. Los puntos de 1.er orden usan el polígono real.
  2. Modos en salas no rectangulares estimados por caja envolvente — modos exactos requieren FEM.
  3. Medición con el micrófono del iPhone — sin calibrar; respuesta / sensibilidad no planas. Fiabilidad siempre .low, como contraste con la estimación geométrica.
  4. Supuesto ISM de fuente única — ignora difracción de caja, directividad de bocina, etc.
  5. Sin modelado de trampas de graves — los problemas bajo 80 Hz quedan fuera del modelo de Sabine y requieren tratamiento dedicado.
  6. Precisión del escaneo AR — extremos de RoomPlan con error de 3-8 cm; un bucle de 12 segmentos puede acumular 5+ cm. No se modela el grosor de los muros.
§ 10

Referencias

  1. Sabine, W. C. (1922). Collected Papers on Acoustics. Harvard University Press.
  2. Allen, J. B., & Berkley, D. A. (1979). Image method for efficiently simulating small-room acoustics. JASA, 65(4), 943–950.
  3. Schroeder, M. R. (1965). New method of measuring reverberation time. JASA, 37(3), 409–412.
  4. Farina, A. (2000). Simultaneous measurement of impulse response and distortion with a swept-sine technique. Audio Engineering Society Convention 108.
  5. Scheibler, R., Bezzam, E., & Dokmanić, I. (2018). Pyroomacoustics: A Python package for audio room simulation and array processing algorithms. ICASSP 2018.
  6. EBU Tech 3276. Listening conditions for the assessment of sound programme material.
  7. ITU-R BS.1116. Methods for the subjective assessment of small impairments in audio systems.
  8. BB93 — Acoustic design of schools (UK).
  9. THX Pro Cinema Certification requirements.
¿Prefieres la versión sencilla?

«Por qué tu sala reescribe la música»

Leer el Deep Read