MCU & FPGA DSP Filtro Digital com Séries de Taylor e Cepstrum para Detecção de Assobio em RP2040

Filtro Digital com Séries de Taylor e Cepstrum para Detecção de Assobio em RP2040


4 — Cepstrum para detectar o assobio (conceito + coleta de frames + log via Taylor)

4.1 O que é “cepstrum” e por que ele funciona tão bem para assobios

O assobio é quase periódico. Se o período do sinal for (T_0), então a frequência fundamental é:

\[
f_0 = \frac{1}{T_0}
\]

O cepstrum é uma técnica que “transforma periodicidade em pico”. A intuição é:

  • Um sinal periódico tem harmônicas no espectro (picos em \(f_0, 2f_0, 3f_0, \dots)\).
  • Quando você aplica log no espectro, você “comprime” diferenças de amplitude e evidencia a estrutura harmônica.
  • Ao fazer a transformada inversa (no caso do real cepstrum, é IFFT do log-magnitude), aparece um pico em quefrência perto de \(T_0\).

A “quefrência” (que vem de brincar com “frequency”) é um eixo em tempo medido em amostras ou segundos. Se (f_s) é a taxa de amostragem, então o pico de quefrência em amostras (q_0) se relaciona com a frequência fundamental por:

\[
f_0 \approx \frac{f_s}{q_0}
\]

Para assobio, esse pico costuma ser muito mais estável do que em fala.


4.2 Qual cepstrum vamos implementar no RP2040

Vamos implementar o real cepstrum clássico:

  1. Pegamos um frame \(x[n]\) do sinal filtrado
  2. Calculamos FFT \(\rightarrow X[k]\)
  3. Magnitude: \(|X[k]|\)
  4. Log-magnitude: \(\log(|X[k]| + \epsilon)\)
  5. IFFT \(\rightarrow c[n]\) (o cepstrum)
  6. Procuramos o pico de (c[n]) numa faixa de quefrência compatível com assobio

Isso é robusto e prático — e no RP2040 dá para fazer com FFT pequena (ex.: 256 pontos) em tempo real.

Importante: nós já usamos Taylor como “operador diferencial”. Agora vamos usar Taylor também para aproximar o log, evitando logf() pesada.


4.3 Coleta de frame (buffer) do sinal já filtrado

Vamos trabalhar com:

  • \(f_s \approx 8000\ \text{Hz}\)
  • FFT de 256 pontos (frame curto, bom para tempo real)

Faixa típica de assobio (800–3000 Hz) → períodos entre:

\[
T_{min} = \frac{1}{3000} \approx 0{,}333\text{ ms}
\quad,\quad
T_{max} = \frac{1}{800} = 1{,}25\text{ ms}
\]

Em amostras (com (f_s=8000)):

\[
q_{min} \approx \frac{8000}{3000} \approx 2{,}67 \Rightarrow 3
\quad,\quad
q_{max} = \frac{8000}{800} = 10
\]

Então o pico de quefrência esperado fica mais ou menos entre 3 e 10 amostras (bem pequeno!). Na prática, por janela/ruído, é comum procurar numa faixa um pouco maior, tipo 3 a 20.

Vamos adicionar um buffer circular simples e um coletor de frame.

4.3.1 Código incremental: buffer e coleta

#define FS_HZ          8000
#define FRAME_N        256

typedef struct {
    float data[FRAME_N];
    uint32_t idx;
    bool full;
} frame_buf_t;

static inline void frame_buf_init(frame_buf_t *b) {
    b->idx = 0;
    b->full = false;
}

static inline void frame_buf_push(frame_buf_t *b, float v) {
    b->data[b->idx++] = v;
    if (b->idx >= FRAME_N) {
        b->idx = 0;
        b->full = true;
    }
}
  • full=true significa: “já tenho um frame completo pronto para análise”.

4.4 Logaritmo aproximado com Série de Taylor (sem logf())

O log natural pode ser aproximado por:

\[
\ln(1+u) = u – \frac{u^2}{2} + \frac{u^3}{3} – \frac{u^4}{4} + \dots
\quad \text{para } |u|<1
\]

Para usar isso em qualquer valor positivo (x), fazemos redução de faixa:

  • Reescreva \(x = m \cdot 2^e\), onde \(m \in [1,2)\)
  • Então:
    \[
    \ln(x) = \ln(m) + e\ln(2)
    \]
  • E como (m \in [1,2)), definimos (u = m-1 \in [0,1)) e aplicamos Taylor em (\ln(1+u))

No C, dá para fazer frexpf() (bem leve) para obter mantissa/expoente.

4.4.1 Implementação (Taylor truncada)

#include <math.h>

static inline float ln_taylor_1pu(float u)
{
    // ln(1+u), u em [0, 1)
    // Série truncada (5 termos): u - u^2/2 + u^3/3 - u^4/4 + u^5/5
    float u2 = u * u;
    float u3 = u2 * u;
    float u4 = u2 * u2;
    float u5 = u4 * u;
    return (u)
         - (0.5f  * u2)
         + (0.3333333f * u3)
         - (0.25f * u4)
         + (0.2f  * u5);
}

static inline float ln_approx(float x)
{
    // x > 0
    int e = 0;
    float m = frexpf(x, &e);   // x = m * 2^e, m in [0.5, 1)
    // vamos trazer m para [1,2): m2 = m*2, e2 = e-1
    float m2 = m * 2.0f;
    int e2 = e - 1;

    const float LN2 = 0.69314718056f;
    float u = m2 - 1.0f;       // u in [0,1)
    return ln_taylor_1pu(u) + ((float)e2) * LN2;
}

Essa aproximação é ótima para “log-magnitude” do cepstrum porque:

  • A gente só precisa de monotonicidade e compressão
  • Erro pequeno não quebra a detecção por pico
  • Evita custo de logf() em loop

4.5 Loop já pronto: filtro + coleta de frame

Agora juntamos o que já temos (seção anterior) com o buffer de frame:

static taylor_filter_t g_filter = {0};
static frame_buf_t     g_frame;

void dsp_init(void) {
    frame_buf_init(&g_frame);
}

void dsp_step(void)
{
    float x = read_adc_normalized();
    float y = taylor_bandpass_step(&g_filter, x);

    frame_buf_push(&g_frame, y);
}

E no main() (ainda incremental):

dsp_init();

while (true) {
    dsp_step();
    sleep_us(1000000 / FS_HZ);

    if (g_frame.full) {
        // Próxima seção: FFT -> log -> IFFT -> cepstrum -> pico -> LED
        g_frame.full = false;
    }
}

Até aqui:

  • Já filtramos o áudio
  • Já coletamos frames
  • Já temos ln_approx() pronto para usar no cepstrum

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

Quefrequência e Análise Cepstral: Uma Introdução Prática para Sistemas Embarcados (ESP32-P4)Quefrequência e Análise Cepstral: Uma Introdução Prática para Sistemas Embarcados (ESP32-P4)

A análise cepstral e o conceito de quefrequência são técnicas essencialmente poderosas no processamento de sinais de áudio, permitindo separar efeitos de excitação, resposta acústica e periodicidades espectrais que não

0
Adoraria saber sua opinião, comente.x