Sinais de voz geralmente ocupam uma faixa de frequência entre 300 Hz e 3400 Hz. Para o processamento digital, uma taxa de amostragem de 8 kHz é comumente adotada. Devido à natureza variante no tempo da fala, utilizamos técnicas de análise de curto prazo para extrair características significativas em pequenos intervalos de tempo.
Filtro de Pré-ênfase
A pré-ênfase é aplicada para compensar a queda de energia nas altas frequências causada pela radiação labial. O objetivo é equilibrar o espectro e aumentar a resolução em altas frequências. Isso é realizado através de um filtro digital FIR de alta passagem de primeira ordem, definido pela função de transferência:
$$H(z) = 1 - \beta z^{-1}$$
Onde $\beta$ é o coeficiente de pré-ênface, usualmente situado no intervalo $[0.9, 1.0]$. Se $s(n)$ é o sinal original, o sinal filtrado $s'(n)$ é obtido por: $s'(n) = s(n) - \beta s(n-1)$. No exemplo abaixo, utilizaremos $\beta = 0.97$.
% Resposta em frequência do filtro de pré-ênfase
fs = 8000;
beta = 0.97;
[freq_resp, eixos_f] = freqz([1, -beta], [1], 512, fs);
subplot(2,1,1);
plot(eixos_f, abs(freq_resp));
title('Resposta em Magnitude do Filtro de Alta Passagem');
xlabel('Frequência (Hz)'); ylabel('Magnitude');
subplot(2,1,2);
plot(eixos_f, angle(freq_resp));
title('Resposta de Fase');
xlabel('Frequência (Hz)'); ylabel('Fase (radianos)');
Ao aplicar este filtro em um segmento de voz, observamos uma redução nas amplitudes de baixa frequência e um realce nos componentes de alta frequência, o que é crucial para identificar formantes e outros detalhes espectrais.
% Aplicação da pré-ênfase em um sinal de áudio
% s_original representa o vetor de dados de áudio
inicio = 500; fim = 755;
trecho_voz = s_original(inicio:fim);
% Filtragem no domínio do tempo
voz_filtrada = filter([1, -0.97], [1], trecho_voz);
subplot(2,1,1);
plot(trecho_voz);
title('Sinal de Voz Original');
xlabel('Amostras'); ylabel('Amplitude');
subplot(2,1,2);
plot(voz_filtrada);
title('Sinal Após Pré-ênfase');
xlabel('Amostras'); ylabel('Amplitude');
Janelamento de Sinais
A voz é um sinal quase-estacionário quando observada em intervalos curtos (10 a 30 ms). O janelamento divide o sinal em quadros, aplicando uma função de peso para suavizar as bordas e evitar descontinuidades abruptas durante a análise espectral (FFT). As funções de janela mais comuns são a Retangular e a de Hamming.
A janela de Hamming é definida como:
$$w(n) = 0.54 - 0.46 \cos\left(\frac{2\pi n}{N-1}\right), \quad 0 \leq n \leq N-1$$
% Comparação entre Janela Hamming e Retangular
N = 64;
janela_ham = hamming(N);
janela_ret = rectwin(N);
% Espectro da janela Hamming
fft_ham = abs(fft(janela_ham, 1024));
fft_ham = 20*log10(fft_ham / max(fft_ham));
% Espectro da janela Retangular
fft_ret = abs(fft(janela_ret, 1024));
fft_ret = 20*log10(fft_ret / max(fft_ret));
f_norm = (0:1023)/1024;
figure;
subplot(1,2,1);
plot(f_norm, fft_ham); axis([0 0.5 -80 0]);
title('Magnitude Hamming (dB)');
xlabel('Frequência Normalizada');
subplot(1,2,2);
plot(f_norm, fft_ret); axis([0 0.5 -80 0]);
title('Magnitude Retangular (dB)');
xlabel('Frequência Normalizada');
A janela Retangular possui um lobo principal estreito (melhor resolução de frequência), mas lobos secundários altos, causando vazamento espectral. A janela de Hamming reduz significativamente os lobos secundários em troca de um lobo principal mais largo.
Divisão em Quadros (Framing)
O processo de "Framing" consiste em segmentar o sinal contínuo em quadros sobrepostos. A sobreposição (overlap) garante que as características do sinal não sejam perdidas nas bordas das janelas. Definimos o comprimento do quadro (comp_quadro) e o deslocamento entre quadros sucessivos (salto).
O número total de quadros $N_f$ para um sinal de comprimento $L$ é dado por:
$$N_f = \lfloor \frac{L - comp\_quadro + salto}{salto} \rfloor$$
Abaixo, uma implementação personalizada da função de enquadramento:
function quadros = dividir_em_quadros(sinal, func_janela, deslocamento)
% sinal: vetor de entrada
% func_janela: vetor da janela ou escalar (comprimento)
% deslocamento: amostras a saltar entre quadros
total_amostras = length(sinal);
tam_janela = length(func_janela);
if tam_janela == 1
L_quadro = func_janela;
janela_vetor = ones(L_quadro, 1);
else
L_quadro = tam_janela;
janela_vetor = func_janela(:);
end
num_quadros = floor((total_amostras - L_quadro + deslocamento) / deslocamento);
quadros = zeros(num_quadros, L_quadro);
for i = 1:num_quadros
ponto_inicio = (i-1) * deslocamento + 1;
segmento = sinal(ponto_inicio : ponto_inicio + L_quadro - 1);
quadros(i, :) = segmento(:)' .* janela_vetor';
end
end