A transformada
O FFTProcessor usa a FFT real-empacotada da Accelerate
(vDSP_fft_zrip), radix-2 (Cooley-Tukey), o que exige que o tamanho da
janela de análise seja sempre potência de 2 — de 1024 a 65536 amostras no Spectra.
A entrada real é empacotada em formato complexo (vDSP_ctoz) antes da
transformada direta. No layout packed-real do vDSP, o bin 0 do resultado mistura o
componente DC (parte real) e o componente de Nyquist (parte imaginária) — o Spectra
não desembaraça os dois porque ambos ficam fora da faixa de interesse (20 Hz–20 kHz).
Além da magnitude normalizada (usada pelo RTA e pelo SPL), o processador expõe o
espectro complexo bruto, sem normalização (complexSpectrum) — é o que
alimenta a função de transferência e o
delay finder, onde razões entre
espectros cancelam qualquer fator de escala comum — e uma FFT inversa
(inverseReal) usada para trazer a correlação cruzada de volta ao domínio
do tempo no delay finder.
Janelas
Quatro janelas de análise estão disponíveis, geradas pelas funções nativas da Accelerate: retangular (nenhuma, na prática), Hann, Hamming e Blackman. Cada uma troca resolução em frequência por vazamento espectral de forma diferente — três propriedades resumem essa troca:
- Ganho coerente ($\overline{w} = \sum w_n / N$) — a média dos coeficientes da janela; quanto a amplitude de um tom isolado é atenuada por ela.
- ENBW (Equivalent Noise Bandwidth) — a largura de banda de ruído branco que produziria a mesma potência de saída que a janela produz; sempre ≥ 1 bin.
- Largura do lóbulo principal — em quantos bins um tom puro se espalha antes de cair para o primeiro nulo.
| Janela | Ganho coerente | ENBW (bins) | Lóbulo principal (bins) |
|---|---|---|---|
| Retangular | 1,0 | 1,0 | 2 |
| Hann | 0,5 | 1,5 | 4 |
| Hamming | 0,54 | 1,3628 | 4 |
| Blackman | 0,42 | 1,7269 | 6 |
Valores nominais de referência1. O
FFTProcessor não confia nesses números diretamente: ele remede o ganho
coerente e a ENBW a partir dos coeficientes reais gerados pela Accelerate para cada
janela, então qualquer diferença de implementação entre o vDSP e a literatura clássica
já entra corrigida.
Normalização independente de janela
Este é o ponto mais sutil do motor espectral — e o que motivou a revisão de precisão da v0.9.0. A convenção de escala do Spectra é: um tom senoidal de amplitude de pico 1,0, alinhado a um bin, soma 1,0 linear (0 dB) ao redor do seu lóbulo principal — sempre, não importa o tamanho da FFT nem a janela escolhida.
onde N é o tamanho da FFT (o vDSP_fft_zrip devolve o
resultado escalado por N em amplitude, logo N² em potência),
w̄ é o ganho coerente e ENBW a largura de banda de ruído
equivalente — ambos medidos a partir da janela em uso.
ENBW, a largura de banda de ruído da janela "vaza" para
dentro de toda soma de bins. Antes da correção, o SPL lia 1,76 dB alto com janela
Hann, e trocar Hamming por Blackman movia a leitura 2,4 dB — sem nada mudar no
microfone. A calibração de SPL precisou ser refeita uma vez depois dessa correção.
Consequência direta: o pico de um único bin isolado (sem somar a energia do
lóbulo inteiro) fica −10·log₁₀(ENBW) abaixo de 0 dB — −1,76 dB em Hann,
por exemplo. Isso é revertido explicitamente onde faz sentido (ver interpolação de
pico, abaixo).
Interpolação parabólica de pico
A grade de bins de uma FFT tem resolução finita (Δf = taxa de amostragem / N):
um tom de 100 Hz raramente cai exatamente sobre um bin. Ler "a frequência é a do bin
mais alto" arredonda para a grade — o PROGRESS do projeto registra um tom de 100 Hz
sendo rotulado 105,9 Hz antes desta correção.
O SpectralPeak ajusta uma parábola sobre o logaritmo (dB) das magnitudes
de três bins vizinhos ao pico — o lóbulo principal de uma janela Hann/Blackman perto
do topo tem formato aproximadamente parabólico em escala log:
onde α, β, γ são as magnitudes em dB dos bins k−1, k e k+1.
Esta é a forma clássica de interpolação parabólica de pico espectral2.
O deslocamento δ é limitado a ±0,5 bin: um valor maior indicaria que os
três pontos não formam um único lóbulo isolado (ruído, ou dois tons se batendo), e
extrapolar além do bin vizinho inventaria uma frequência que não está no sinal. A
potência interpolada é multiplicada de volta pela ENBW da janela, revertendo a perda
de escalonamento (scalloping loss) descrita acima — sem isso, o nível do pico
isolado sairia sistematicamente baixo.
Resultado medido: tons localizados com erro abaixo de 0,03 Hz e nível dentro de 0,35 dB do valor teórico, em toda a faixa de frequência testada.
Resolução automática de FFT
Quanto mais fina a fração de oitava (1/48, por exemplo), mais estreita é cada banda —
e uma banda pode ficar mais estreita do que o lóbulo principal da janela consegue
resolver. O AnalysisResolution calcula, para cada combinação de
frequência/fração/janela, se a FFT em uso resolve a banda inteira ou não, e escolhe
automaticamente um tamanho de FFT suficiente.
Um tom qualquer se espalha por lóbulo principal (bins) × Δf Hz de
espectro. Uma banda de fração 1/n em frequência f tem
largura f · largura relativa(n). A banda só contém o tom inteiro quando é
mais larga que esse espalhamento — dois limiares, calibrados por medição direta sobre
todas as combinações de fração, tamanho de FFT e frequência que o app oferece:
- Totalmente resolvido (fator 1,25×) — erro medido no pior caso: −0,03 dB.
- Ainda utilizável (fator 0,75×) — erro medido no pior caso: −0,59 dB; abaixo disso a leitura fica materialmente baixa, e o app declara isso na tela em vez de mostrar um número enganoso.
| Fração de oitava | FFT automática |
|---|---|
| até 1/3 | 4096 |
| até 1/8 | 8192 |
| até 1/16 | 16384 |
| 1/24 e acima (até 1/48) | 32768 |
Tamanhos manuais vão até 65536 (Ajustes). Custo no pior caso (1/48, FFT de 32768): 0,15 ms de FFT + 0,22 ms de integração de bandas por quadro — contra um orçamento de 33 ms a 30 quadros por segundo.
Essa limitação não é um bug corrigível por fator de calibração: é física — nenhuma FFT de comprimento fixo resolve uma fração estreita o bastante em frequências baixas (uma banda de 1/48 em 125 Hz tem 1,8 Hz de largura; a janela Hann já espalha um tom por 4 bins). As únicas saídas reais são uma janela mais longa (mais latência) ou um banco de filtros dedicado — por isso o Spectra declara o limite em vez de escondê-lo.