O objetivo inicial do projeto era utilizar a entrada de áudio da placa para fazer captura direta da guitarra, passando pelo ADC5.1 e aplicando uma FFT5.2 sobre essas amostras, para então identificar a frequência e duração das notas tocadas e fazer uma tradução para o formato MIDI. Porém, a captura da placa usada no projeto não pode ser utilizada, e substituimos a entrada do programa para um arquivo de áudio puro, gravado num computador e passado para a placa.
Após a leitura é realizada a transformada e seu resultado armazenado em um buffer de números complexos, também com tamanho FFT_SIZE. Este resultado, por sua vez, serve commo entrada para o bloco de detecção da frequência fundamental.
Após a detecção da frequência, cabe à função is_event() determinar se o aúdio sendo analisado constitui uma nova nota (ou silêncio), ou é apenas a continuação de uma nota anteriormente detectada.
Todos os blocos funcionais serão detalhados adiante.
typedef struc note { pitch_t pitch; int time; bool on; } note_t;
A struct possui três campos inteiros (com pitch_t e bool sendo dois typedef para int), onde o primeiro, pitch, determina o valor no formato MIDI para a frquência detectada, o segundo, time, o instante no tempo em que a nota foi detectada e o terceiro, on, determina se o evento MIDI gerado será um NOTEON ou NOTEOFF.
Essa saída do primeiro bloco do projeto, uma stream de elementos do tipo note_t, era a entrada esperada pelo segundo bloco.
A implementação de FFT utilizada no projeto foi a presente na biblioteca de processamento digital de sinais do Blackfin, a libbfdsp[6]. Mais especificamente, a função utilizada foi a rfftrad4_fr16, uma FFT com entrada real e saída complexa. Abaixo está a assinatura da função:
void rfftrad4_fr16(const fract16 input[], complex_fract16 temp[], complex_fract16 output[], const complex_fract16 twiddle_table[], int twiddle_stride, int fft_size, int block_exponent, int scale_method);
Para análise das frequências foram utilizados apenas os valores absolutos da saída da transformada.
O algoritmo de detecção da frequência fundamental recebe a saída da FFT, ou seja, um histograma contendo informações da intensidade relativa de cada um dos harmônicos presentes no sinal. Cabe a este bloco determinar qual é a frequência fundamental, que é a frequência percebida pelo ouvinte.
Inicialmente havíamos adotado um critério bastante simples para a determinação de tal frequência: selecionávamos a frequência com a maior intensidade. A partir de experimentos realizados com a captura da própria guitarra percebemos, porém, que nem sempre a frequência mais intensa era a fundamental. As figuras 5.3 e 5.4 demonstram, respectivamente, os casos onde a fundamental é mais intensa e onde isso não ocorre.
A partir da observação dos espectros das notas, percebemos que era suficiente obter os picos da função e, dentre estes picos, escolher o de menor frequência, que é a fundamental da nota.
get_fundamental_index(frequency_spectrum) { peaks[FFT_PARTITIONS]; for(i = 0; i < FFT_PARTITIONS; i++) { max_index = 0; max = 0; for(j = 0; j < FFT_PARTITION_SIZE; j++) { index = i*FFT_PARTITION_SIZE + j; cur = abs(frequency_spectrum[index]); if(cur > max) { max = cur; max_index = index; } } peaks[i].index = max_index; peaks[i].val = max; } sort(peaks, FFT_PARTITIONS, peaks.val); sort(peaks, FIRST_HARMONICS, peaks.index); }
A idéia original para se resolver este problema, seria aplicar algum filtro no sinal de entrada, antes de se aplicar a FFT em si, mas acabamos por adotar uma solução mais simples. Basicamente comparamos a frequência da nota atual com a da anterior e, caso as notas detectadas sejam diferentes, um evento está caracterizado, seja por uma nova nota sendo tocada, ou por silêncio sendo reconhecido.
Este modelo criou um outro problema, pois após uma nota ser tocada no instrumento, a frequência obtida pelo programa varia muito, e se estabiliza após algum tempo. Por este motivo, logo após a detecção de um evento fazemos um descarte de algumas FFTs, até que a entrada esteja estável. O número mais adequado de FFTs a se descartar foi encontrado através de testes, variando a quantidade de descartes até encontrar um valor que obtivesse o melhor resultado.
João Paulo Pizani Flor 2010-07-12