Acabei de revisar meu algoritmo de cálculo de pitch usando um algoritmo de espectro de produto harmônico. Eu só estava curioso para saber por que esta explicação do espectro do produto harmônico afirma que você precisa implementar uma janela Hanning para o conjunto de dados. Qual seria o efeito da implementação de outras funções do Window em um conjunto de dados (e então FFTing)? Qual função de janela é realmente a melhor para detecção de frequência? Aqui estão os métodos relevantes que usei em meu código:
/** * Calculates the Frequency based off of the byte array, * @param bytes The audioData you want to analyze * @return The calculated frequency in Hertz. */ private int getFrequency(byte[] bytes){ double[] audioData = this.bytesToDoubleArray(bytes); audioData = applyHanningWindow(audioData); Complex[] complex = new Complex[audioData.length]; for(int i = 0; i<complex.length; i++){ complex[i] = new Complex(audioData[i], 0); } Complex[] fftTransformed = FFT.fft(complex); //return calculateFrequency(fftTransformed); System.out.println("Max size:" + (fftTransformed.length*getFFTBinSize(fftTransformed.length)/4)); return calculateFundamentalFrequency(fftTransformed,4); } private double[] applyHanningWindow(double[] data){ return applyHanningWindow(data, 0, data.length); } private double[] applyHanningWindow(double[] signal_in, int pos, int size) { for (int i = pos; i < pos + size; i++) { int j = i - pos; // j = index into Hann window function signal_in[i] = (double)(signal_in[i] * 0.5 * (1.0 - Math.cos(2.0 * Math.PI * j / size))); } return signal_in; } /** * Harmonic Product Spectrum * @param fftData * @param n * @return */ private int calculateFundamentalFrequency(Complex[] fftData, int n){ Complex[][] data = new Complex[n][fftData.length/n]; for(int i = 0; i<n; i++){ for(int j = 0; j<data[0].length; j++){ data[i][j] = fftData[j*(i+1)]; } } Complex[] result = new Complex[fftData.length/n];//Combines the arrays for(int i = 0; i<result.length; i++){ Complex tmp = new Complex(1,0); for(int j = 0; j<n; j++){ tmp = tmp.times(data[j][i]); } result[i] = tmp; } //Calculates Maximum Magnitude of the array double max = Double.MIN_VALUE; int index = -1; for(int i = 0; i<result.length; i++){ Complex c = result[i]; double tmp = c.getMagnitude(); if(tmp>max){ max = tmp;; index = i; } } return index*getFFTBinSize(fftData.length); }
Resposta
O FFT só pode ser executado em um bloco limitado de dados. A matemática básica é baseada na suposição de que o sinal no domínio do tempo é periódico, ou seja, sua porção de dados se repete no tempo. Isso normalmente resulta em uma grande descontinuidade nas bordas do pedaço. Vejamos um exemplo rápido: tamanho FFT = 1000 pontos, taxa de amostragem = 1000 Hz, resolução de frequência = 1 Hz. Se você tem uma onda senoidal de 10 Hz, você não tem uma descontinuidade, pois exatamente 10 períodos se encaixam em sua janela FFT e os valores (e derivados) nas bordas são os mesmos. O FFT deste siganl será zero, exceto para um único valor no bin # 10. Isso também funciona para uma onda senoidal de 11 Hz. Porém, para a onda senoidal de 10,3 HZ você acaba com muita descontinuidade e o FFT terá energia em todos os bins com no máximo em torno de 10 ou 11 e então “saias” que rolam para os lados. Portanto, uma pequena mudança na frequência resulta em uma grande mudança na imagem FFT.
O uso de janelas é usado para evitar isso: o Windows certifica-se de que os dados nas bordas são zero, para que não haja descontinuidade. No entanto, a multiplicação no domínio do tempo é a convolução no domínio da frequência e isso resulta no alargamento das linhas espectrais e também nos lobos laterais. A escolha da janela controla as compensações entre a largura do lóbulo principal e o espaçamento e altura do lóbulo lateral. Os requisitos específicos de seu aplicativo determinam qual janela usar e há dezenas de opções. Hanning é apenas um deles. É basicamente “a janela de escolha se você não tiver ideias melhores”. Pessoalmente, eu prefiro as janelas Kaiser, pois elas têm um parâmetro contínuo que pode controlar o comportamento da janela em uma ampla faixa.
Em geral, FFT não é um ótimo método para detecção de pitch. Para a maioria dos sinais de áudio, o máximo no espectro NÃO é o fundamental (normalmente os harmônicos têm uma energia mais alta), para obter uma resolução decente, você precisa de longos pedaços de dados, mas isso torna o algoritmo muito lento e lento para responder às mudanças. Opções muito melhores são loops observados em fase, loops observados em atraso, autocorrelações, rastreador máximo / mínimo, rastreador de cruzamento zero, etc.