Visão geral
Entrada: o usuário informa o tamanho / formato / materiais de acabamento da sala, as posições dos alto-falantes e da posição de audição — ou a geometria da sala é medida com a trena em AR (sem necessidade de LiDAR) ou capturada por um escaneamento com LiDAR (RoomPlan).
- Localização de pontos de reflexão inicial
- RT60 (medido + simulado) mais EDT / C50 / C80 / D50 (derivados da RIR simulada)
- Lista de frequências dos modos da sala
- Plano de disposição dos painéis (posição, quantidade, SKU do produto)
- Visualização 3D + mapa de calor da energia das reflexões
- Exportação de PDF em nível de manual de instalação
- Medição no dispositivo via microfone do iPhone (varredura / palma)
Há suporte para salas retangulares, em L, em U e com qualquer polígono simples; as reflexões de primeira ordem e os cálculos de área / contenção usam o polígono real; as reflexões de 2ª/3ª ordem e os modos da sala são aproximados pela caixa delimitadora (ver §9).
Modelo acústico
2.1 Sabine RT60
A clássica fórmula de Sabine:
- $V$
- Volume da sala (m³); para polígonos, área Shoelace × altura, menos obstáculos como colunas / vigas.
- $\sum_i S_i \alpha_i$
- Área da superfície × coeficiente de absorção por face. Os coeficientes usam 6 bandas de oitava (125 / 250 / 500 / 1k / 2k / 4k Hz).
- $0.161$
- = 24·ln(10)/c, com c = 343 m/s (20 °C).
O app traz 4 predefinições de acabamento (cômodo vazio de superfícies duras / acabamento duro típico / ambiente com estofados / ambiente já tratado), cada uma com uma curva de absorção por banda; a estimativa de RT60 em valor único do topo e o orçamento de painéis usam um coeficiente médio de absorção das bandas médias-altas, enquanto a curva Sabine de 6 bandas alimenta a exibição por banda. O RT60 alvo também é uma curva de 6 bandas (não um valor único), em conformidade com normas como EBU Tech 3276 / THX, que permitem uma cauda de reverberação mais longa nas baixas frequências.
2.2 Método das Fontes de Imagem (ISM)
As reflexões de primeira ordem usam espelhamento: espelha-se a fonte em relação a uma parede; a interseção do segmento “fonte espelhada → ouvinte” com a parede é o ponto de reflexão.
- Sala retangular: espelhar em todos os 6 planos (4 paredes + piso + teto) — forma fechada.
- Não retangular (
PolygonReflectionCalculator): espelha cada segmento de parede deRoomPolygon.walls()e valida o parâmetro s ∈ [0, 1] dentro do segmento (reflexões de piso / teto exigem ainda que o XY da reflexão esteja dentro do polígono); caminhos de primeira ordem também passam por uma verificação de oclusão 2D — qualquer trecho fonte→ponto→ouvinte que cruze outro segmento de parede é descartado.
Reflexões de múltiplas ordens (ordens 1–3) são implementadas pelo MultiOrderReflectionCalculator seguindo a abordagem recursiva do pyroomacoustics:
- Enumera todas as sequências de paredes [s₁, …, sₙ], com paredes adjacentes distintas
- Iteração da fonte imagem: S₀ = source, Sₖ = mirror(Sₖ₋₁, sₖ)
- Traçado reverso a partir do ouvinte: a reta Sₙ → ouvinte cruza sₙ em Pₙ; a reta Sₙ₋₁ → Pₙ cruza sₙ₋₁ em Pₙ₋₁; recursão até o início
- Cada interseção deve estar dentro do intervalo válido da parede e com t ∈ (0, 1) no segmento
2.3 Energia do caminho
Energia de banda larga em função do número de reflexões e da atenuação pela distância:
- α é limitado a [0, 0.99] para que α = 1 não produza log(0) ou pow(negativo, N) = NaN, o que contaminaria todo o caminho
- A atenuação pela distância segue o espalhamento esférico 1/r²
- A conversão para dB usa 10·log₁₀ (domínio da energia)
A energia por banda é calculada separadamente para as 6 bandas de oitava (cada uma usando o coeficiente da respectiva banda de finishProfile.absorptionByBand).
2.4 Estimativa de RT60 (medida)
ImpulseResponseEstimator:
- Filtragem casada: a varredura gravada de 0,45 s passa por filtragem inversa (ESS / Farina) e é comprimida em um único impulso, ganhando ~+32 dB de SNR efetiva
- rmsEnvelope: envoltória RMS de janela deslizante
- Integração reversa de Schroeder: E_back(t) = ∫ₜ^∞ s²(τ) dτ, normalizada com 10·log₁₀ para gerar a curva EDC
- Regressão linear da inclinação: prefira T30 [−5, −35 dB] conforme o SNR, com fallback para T20 [−5, −25] ou T10 [−5, −15]
- RT₆₀ = −60 / inclinação
RT60 por banda: sobre as amostras após o filtro casado, aplica-se passa-banda nos centros de oitava ISO (250 / 500 / 1k / 2k / 4k) com Q = √2, e então executa-se o Schroeder em cada banda.
2.5 Simulação de RIR
SimulatedRIRGenerator transforma os caminhos de reflexão multi-ordem em eventos de impulso discretos gravados em um buffer PCM de 8 kHz × 0,5 s:
for path in reflectionPaths:
t = path.totalDistance / speedOfSound
amp = 10^(path.relativeEnergyDB / 20)
impulse[round(t * sampleRate)] += amp
Some o impulso do som direto e depois execute a integração reversa de Schroeder para extrair EDT / RT60 / C50 / C80 / D50.
2.6 Modos da sala
Frequências modais de uma sala retangular:
Classificados por quantos dos índices nₓ, n_y, n_z são não nulos: 1 = axial, 2 = tangencial, 3 = oblíquo.
A enumeração cobre n ≤ 4 por eixo até 300 Hz. Salas não retangulares estimam os modos a partir da caixa delimitadora (modos exatos exigem FEM); isso é indicado explicitamente na interface.
Sistema de geometria
3.1 Formatos suportados
- Retângulo — width × length × height
- Formato em L — remove um recorte nw × nl em um canto escolhido
- Formato U — remove um recorte de nw × nl no meio de uma parede escolhida
- Personalizado — qualquer polígono simples (≥ 3, ≤ 24 vértices)
Todos os formatos não retangulares viram uma lista de vértices em CCW via RoomPolygon.from(shape:, bboxWidth:, bboxLength:). As reflexões de primeira ordem, os cálculos de área / perímetro e de contenção usam esses vértices diretamente; um polígono personalizado inválido (< 3 / > 24 vértices, com autointerseção, área quase nula) degrada silenciosamente para o retângulo delimitador.
3.2 Utilitários de geometria de polígonos
- Área — fórmula de Shoelace
- Perímetro — soma dos comprimentos das arestas
- contains(x, y) — ray-casting com fallback de epsilon na fronteira (evita falsos negativos nos cantos)
- clamped(point) — retângulos usam o caminho rápido [0, w] × [0, l]; não retângulos usam o verdadeiro [minₓ, maxₓ] × [min_y, max_y] do
polygon.boundingBox, testam polygon.contains e fazem busca binária em direção ao centro do bbox se estiver fora - walls() — divide o polígono em segmentos de parede classificados pela normal interna (agrupados automaticamente em leftWall / rightWall / frontWall / backWall), permitindo reutilizar o recomendador de painéis da versão retangular
- PolygonValidator.isSimple — valida um polígono simples sem autointerseção
3.3 Escaneamento com RoomPlan
Usa-se a RoomCaptureSession (API do iOS 16+; no app, o escaneamento exige iOS 17), e o CapturedRoomMapper mapeia o escaneamento para um ScannedRoom (wallSegments + openings + furniture). Mapeamento de coordenadas:
Os escaneamentos são classificados pela contagem de paredes: exatamente 4 segmentos de parede são tratados diretamente como retângulo; qualquer outro caso passa pelo RectangularRoomSimplifier.polygonShapeFromScan, encadeando os wallSegments em vértices de polígono. Os endpoints do RoomPlan têm erro típico de 3–8 cm; um loop de 12 segmentos pode acumular 5 cm, então o epsilon de fechamento é 12 cm. Se a cadeia não fechar, ele recorre ao bounding box e pede para o usuário escanear de novo.
3.4 Trena AR
Em dispositivos sem LiDAR, o ARTapeMeasureView (uma ARView do RealityKit com world tracking) assume: mire a cruz de mira na parede e toque duas vezes para registrar os pontos inicial / final, passando por largura → comprimento → altura, depois confirme para gerar uma sala retangular — com orientação sobre permissão de câmera e dicas para falha de rastreamento.
3.5 Editor de vértices livres
FreeVertexEditorView permite ao usuário arrastar vértices em um Canvas do SwiftUI:
- Pressione e segure para excluir (quando o número de vértices > 3, de modo que restem ≥ 3)
- Cada quadro do arrasto é validado com
isSimple; uma nova posição com autointerseção simplesmente não é aplicada (o vértice fica onde está). Tocar em um espaço vazio insere um vértice na aresta mais próxima; há também um editor numérico por vértice e um botão de redefinir para o retângulo delimitador - Snap na grade de 0,05 m
- Uma margem de max(0,5, bbox × 0,2) m fora da bbox permite que o usuário arraste vértices para além da bbox atual (a view pai aumenta automaticamente room.width / length)
Zonas de audição e múltiplos ouvintes
AcousticConfiguration.listeners aceita 1..N posições de audição (com 0 ouvintes, uma posição primária padrão é sintetizada com um aviso; com 0 alto-falantes, os templates de caso de uso da §5.2 assumem o controle). A análise executa o método das fontes-imagem uma vez por ouvinte e mescla os pontos de reflexão em um único array (os pontos do ouvinte primário primeiro). O recomendador de painéis acústicos pondera cada parede pelo número de pontos de reflexão (cap = max(6, speakerCount × listenerCount)); vários assentos compartilhando a mesma parede naturalmente a empurram em direção ao teto do orçamento.
- No 3D, os nós de secondary listener usam opacity 0,45 + scale 0,75
- Na vista superior (RoomPlanView), os pontos de reflexão primários são r=5 em cor cheia, os secundários r=3 com opacidade 0,45
- A lista de reflexões (RecommendationView) mostra apenas os pontos primários; as posições de audição secundárias recebem uma nota de rodapé “+ N reflexões já contabilizadas no plano”
Reflexões de múltiplas ordens / RIR / mapa de calor ainda são calculados uma única vez para o ouvinte principal (por ouvinte, O(speakers × surfaces³) é pesado demais, e o RIR é uma métrica de ponto único).
Motor de recomendação
PanelPlacementRecommender gera [PanelRecommendation], cada item com: posição (parede esquerda / parede direita / teto / parede frontal / parede traseira / canto), quantidade, prioridade (essencial / forte / recomendado / opcional), effectivenessMultiplier, targetReflectionPointId.
5.1 Alocação proporcional (maiores restos)
O antigo add() guloso deixava paredes sem nada: a parede esquerda consumia o orçamento → a parede direita ficava com 0; com orçamentos apertados, o teto / a frente / o fundo também ficavam sem. A nova versão usa o método do maior resto (largest-remainder):
- Calcular o desejado bruto de cada parede (contagem de pontos de reflexão + profundidade da sala + caso de uso)
- floor(raw × budget / total) como alocação inicial
- paredes com raw > 0 recebem pelo menos 1 painel; somente quando o orçamento é menor que o número de paredes com demanda é que as superfícies são zeradas, começando pelas de menor prioridade
- se a soma > orçamento, corta a partir do menor resto fracionário
- passagem de simetria esquerda/direita: se raw[0] == raw[1], força alloc[0] == alloc[1]
- redistribuir qualquer sobra pela maior fração
5.2 Modelos por caso de uso
Sem alto-falantes posicionados, aplicam-se templates específicos por caso de uso:
- Sala de reunião: teto 35% + parede do fundo 20% + cada parede lateral 12%
- Podcast: diretamente à frente do microfone 25% + cada lado 18% + teto 20%
- Estúdio: amortecimento simétrico nas quatro paredes + até 4 armadilhas de graves de canto dentro do orçamento (o verdadeiro desvio de orçamento só acontece no caminho de modos de baixa frequência densos da §5.3)
- Audição geral: base simétrica + um aviso para adicionar alto-falantes
5.3 Os itens físicos indispensáveis contornam o orçamento
As armadilhas de graves de canto são forçadas a 4 quando os modos de 80–150 Hz são densos, ignorando o limite totalPanel. Motivo: os cantos têm a maior pressão sonora e as armadilhas de graves são ~1,5× mais eficientes ali do que nas paredes — uma necessidade física, não um item de orçamento.
5.4 Ponderação próxima ao ouvinte
Um pós-processamento marca as recomendações de parede a até 1,5 m de um ouvinte como .nearListener, define effectivenessMultiplier ×1,15 e indica no título / motivo que se trata de uma “zona de absorção sensível a curta distância”.
Medição
6.1 Varredura ESS
Seguindo o método de Farina:
- 180 Hz → 4 kHz, varredura logarítmica de 0,45 s
- Filtro inverso (
makeInverseSweep) = reversão temporal + um envelope de amplitude de +6 dB/oitava (crescente com a frequência, para compensar o espectro rosa do ESS) - Gravação via AVAudioRecorder em Linear PCM sem perdas (16 bits mono .caf, faixa dinâmica de ~96 dB — codecs perceptuais não têm lugar na medição de impulso; a taxa de amostragem segue a sessão de áudio — 44,1 kHz no microfone integrado, os 48 kHz nativos em microfones de medição USB)
- Leitura de volta → filtro casado → analyzeImpulse
makeInverseSweep(sampleRate:) é parametrizado) — a filtragem casada está sempre ativa, tanto a 44,1 kHz no microfone integrado quanto a 48 kHz em microfones de medição USB.6.2 Impulso de palma
Sem reprodução — o usuário bate uma palma forte dentro de uma janela de 1 s; são gravados 4,5 s no total (~3,5 s de cauda). O sinal vai direto para analyzeImpulse (não precisa de filtragem casada; uma palma já é um impulso estreito). Isso cobre a banda de oitava de 125 Hz, que a varredura (a partir de 180 Hz) não alcança.
6.3 Medido vs simulado
MeasuredVsSimulatedCard obtém o RT60 medido mais recente do projeto e mostra o desvio em relação ao RT60 simulado da geometria atual:
6.4 Ruído ambiente & calibração
AmbientNoiseEstimator grava 3 s de silêncio (aparando 100 ms em cada extremidade) para o ruído de fundo da sala: média de banda larga / pico em dBFS, mais uma classificação de silêncio em 5 níveis (o dBFS é apenas relativo até uma calibração de SPL, depois da qual passa a ser exibido dB SPL). A calibração de latência de campo próximo reutiliza a mesma varredura ESS: segure o telefone a até 30 cm do alto-falante, meça o atraso do início da gravação até o pico do impulso (incluindo um pre-roll proposital de 250 ms), idealmente em 2–3 execuções — uma dispersão das últimas 3 leituras abaixo de 5 ms indica confiança alta; abaixo de 20 ms, média. A latência fica vinculada à rota atual de entrada + saída e se autoinvalida quando qualquer uma das duas muda; as medições de campo distante mostram o atraso de propagação já com ela subtraída.
6.5 Microfones de medição USB e roteamento de saída
O seletor de entrada da página de medição lista o microfone integrado e entradas USB class-compliant (ex.: miniDSP UMIK-1 / UMIK-2); um listener de routeChangeNotification retorna ao microfone integrado quando o externo desaparece. As taxas de amostragem de gravação e da varredura seguem a sessão (UMIK-1 = 48 kHz). Arquivos de calibração REW .txt são importados diretamente (interpretando Sens Factor / SERNO, interpolação linear no domínio de frequência logarítmica, clamp nas extremidades; o arquivo de 90° é recomendado para medição de salas) — a correção apenas desloca a exibição do nível por banda, nunca a inclinação de decaimento do RT60.
Calibração única de SPL: encaixe um calibrador de nível sonoro (padrão 94 dB @ 1 kHz) sobre o microfone, o app amostra 3 s de nível para derivar o deslocamento dBFS → SPL, e as leituras passam a exibir dB SPL reais. A calibração (curva + deslocamento) é vinculada ao UID da porta de entrada e persistida em ~/Library/Application Support/mic_calibration.json; ao trocar de entrada, a interface avisa em laranja que a calibração não se aplica — importe e calibre novamente.
A reprodução passa pelo seletor de rota do sistema (um wrapper de AVRoutePickerView, colocado nos cards de varredura / calibração de campo próximo); a sessão libera AirPlay / Bluetooth A2DP / HFP para que os tons de teste toquem por alto-falantes externos, e volta para o alto-falante do aparelho quando nenhum está conectado. A entrada de gravação fica sempre explicitamente fixada na escolha do usuário (microfone USB ou integrado), para que o microfone de um dispositivo Bluetooth não roube a entrada no meio da reprodução e contamine a medição.
Visualização e saída
7.1 Visualização 3D
Room3DController renderiza com SceneKit: paredes / piso / teto / nós de alto-falantes (cabinet fino + accent tick + channel pill) / ouvinte (FE glyph + drop stem) / obstáculos (SCNBox translúcido) / painéis recomendados (mockups de painéis por parede) / segmentos de caminho de reflexão de múltiplas ordens / sobreposição de mapa de calor de energia de reflexão.
7.2 Mapa de calor de energia de reflexão
O ReflectionEnergyMapGenerator acumula a energia de cada caminho de múltiplas ordens na grade uCells × vCells da parede atingida (padrão 12×6). Um mapa de energia por parede, sobreposto em 3D como uma camada de cor de energia (azul → ciano → verde → amarelo → vermelho).
EnergyHeatmapRenderer transforma a grade em uma UIImage: células em estilo de blocos de 16 px cada (sem interpolação entre as células, mantendo as bordas da grade legíveis; a suavização na parede vem da filtragem bilinear de textura do SceneKit) + um degradê personalizado de 5 paradas (azul → ciano → verde → amarelo → vermelho) + alfa mapeado linearmente de 0,20 a 0,85 conforme a posição da energia em uma escala fixa de −30…0 dB (−∞ totalmente transparente).
7.3 Prévia da convolução RIR
RIRPreviewPlayer usa AVAudioEngine + AVAudioPlayerNode para convolver um sinal seco sintetizado proceduralmente (corda dedilhada / sequência de palmas / fala semelhante a vogais — sem amostras embutidas) com a RIR simulada e reproduzi-lo, para que os usuários ouçam a diferença entre “sem tratamento” e “com tratamento”.
A convolução no domínio do tempo RIRConvolver.convolve(dry:rir:) é O(N × M) direta (N = amostras do sinal dry, M = amostras do RIR), normalizada pelo pico em 0,9 para evitar clipping.
7.4 Manual de instalação em PDF
ReportExporter renderiza um PDF A4 (595 × 842 pt) via UIGraphicsPDFRenderer:
- Visão geral da sala + captura 3D
- Gráfico de barras de RT60 em 6 bandas, estimativa de Sabine vs. alvo (o RT60 medido não entra no PDF)
- Tabela de pontos de reflexão + tabela de modos + resumo de riscos
- Tabela de painéis recomendados (a BOM da lista de compras é exibida no app, não no PDF)
- Fonte Geist + fallback PingFangSC (cascadeList do CoreText, para o CJK não renderizar como □□□)
7.5 Persistência de projetos
ProjectStore (um actor) grava ~/Library/Application Support/Acouvero/projects.json: [RoomProject], cada um contendo o Room completo + speakers + listeners + recommendations + panelCountOverrides. No primeiro lançamento, os dados migram automaticamente do diretório legado FindEcho/ (FindEcho é o codinome interno anterior à mudança de nome, mantido deliberadamente nos símbolos do código).
MeasurementStore (um actor) grava ~/Library/Application Support/Acouvero/measurements.json: [MeasurementResult], vinculado a um projeto pelo roomId.
Ambos usam recuperação de decodificação em nível de elemento (um wrapper Throwable<T>) — um registro corrompido não derruba a lista inteira.
MicCalibrationStore grava ~/Library/Application Support/mic_calibration.json (raiz do diretório, sem subdiretório): a curva de calibração do REW + offset de SPL, vinculada ao UID da porta de entrada — trocar de entrada exige uma nova importação / calibração.
Notas de engenharia
8.1 SwiftUI + @Observable
A ViewModel (FindEchoViewModel) usa a macro @Observable do Swift 5.9 — os campos de estado são observados diretamente, sem Combine. Os resultados da AcousticAnalysis são armazenados em cache por meio de uma comparação Equatable de AcousticConfiguration:
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 Isolamento de actors
ProjectStore e MeasurementStore são actors, serializando a lógica de IO / assíncrona de forma natural; RoomPlanService é uma classe @MainActor. Os chamadores da UI usam async / await, então a thread principal nunca é bloqueada por IO de disco.
8.3 Ciclo de vida do AVAudio
AudioSessionManager centraliza a categoria da sessão (.playback para a prévia do RIR, .playAndRecord para a medição). A proteção contra interrupções cobre os dois caminhos: no lado da medição, um InterruptionMonitor dentro de AudioMeasurementService observa AVAudioSession.interruptionNotification — uma ligação / Siri durante uma varredura, palma ou medição de ruído ambiente descarta o resultado com um erro; no lado da prévia, RIRPreviewPlayer também observa, parando automaticamente o nó do player e limpando isPlaying. Uma mudança de rota de áudio no meio da medição também invalida a execução.
8.4 Cancelamento de Task
A reprodução da convolução é uma tarefa em segundo plano; toques rápidos no botão disparam pendingPlayTask?.cancel() mais uma dupla verificação de Task.isCancelled para evitar escritas fora de ordem na MainActor.
8.5 Cobertura de testes
AllShapesAndUseCasesTests usa o framework Swift Testing para rodar a matriz completa — cada formato × cada caso de uso (um único ouvinte; os alto-falantes são um par estéreo ou um único central por caso de uso) — verificando:
analysis.reflectionPaths.count >= 1analysis.warningsinclui a nota de aproximação para salas não retangulares- o total de painéis recomendados é não negativo (totalPanelCount ≥ 0; sem asserção de correspondência com o orçamento)
- polígonos personalizados que se autointersectam fazem fallback com segurança (sem crash, degradando para um retângulo)
- vértices < 3 e outros fallbacks de casos de borda
8.6 Plataforma e localização
Destino de implantação iOS 17.0, iPhone + iPad; zero dependências de terceiros (sem pacotes SPM / CocoaPods) — apenas frameworks do sistema (SwiftUI / SceneKit / ARKit / RoomPlan / AVFoundation / Accelerate). A localização usa um catálogo .xcstrings: chinês como idioma de origem, mais English / Français / Deutsch / Español / Português (Brasil) — 6 idiomas no total, alternáveis no app ou seguindo o sistema.
Limitações conhecidas
- Multi-ordem em salas não retangulares usa a caixa delimitadora — o risco de flutter / slapback de 2ª/3ª ordem perto de cantos côncavos pode sair impreciso. Os pontos de primeira ordem em si usam o polígono real.
- Os modos da sala em salas não retangulares são estimados a partir do bounding box — modos exatos exigem FEM.
- Medição com o microfone integrado do iPhone — não calibrado; resposta de frequência / sensibilidade não plana. A confiança é sempre marcada como
.low, usada como referência cruzada com a estimativa geométrica. Com um microfone de medição USB (miniDSP UMIK-1 / UMIK-2) e o arquivo de calibração do REW carregado, os níveis por banda são corrigidos pela curva de calibração; uma calibração de referência única de 94 dB SPL então exibe os níveis ambiente e de medição como dB SPL absolutos (somente dBFS até lá). - Hipótese de fonte única do ISM — ignora a difração da caixa, a diretividade da corneta e outras características de alto-falantes reais.
- Sem modelagem de armadilhas de graves — problemas abaixo de 80 Hz ficam fora do modelo de Sabine e precisam de tratamento dedicado para baixas frequências.
- Precisão da varredura AR — os endpoints do RoomPlan erram tipicamente 3-8 cm; um loop de 12 segmentos pode acumular mais de 5 cm. A espessura das paredes não é modelada.
Referências
- Sabine, W. C. (1922). Collected Papers on Acoustics. Harvard University Press.
- Allen, J. B., & Berkley, D. A. (1979). Image method for efficiently simulating small-room acoustics. JASA, 65(4), 943–950.
- Schroeder, M. R. (1965). New method of measuring reverberation time. JASA, 37(3), 409–412.
- Farina, A. (2000). Simultaneous measurement of impulse response and distortion with a swept-sine technique. Audio Engineering Society Convention 108.
- Scheibler, R., Bezzam, E., & Dokmanić, I. (2018). Pyroomacoustics: A Python package for audio room simulation and array processing algorithms. ICASSP 2018.
- EBU Tech 3276. Listening conditions for the assessment of sound programme material.
- ITU-R BS.1116. Methods for the subjective assessment of small impairments in audio systems.
- BB93 — Acoustic design of schools (UK).
- THX Pro Cinema Certification requirements.