2010-01-16 18 views
6

Sto provando a creare un sistema che sia in grado di elaborare una registrazione di qualcuno che fischia e genera note.Analizza il suono "whistle" per pitch/note

Qualcuno può consigliare una piattaforma open source che posso utilizzare come base per il riconoscimento di note/toni e l'analisi dei file wave?

Grazie in anticipo

risposta

10

come molti altri ho già detto, FFT è la strada da percorrere qui. Ho scritto un piccolo esempio in Java usando il codice FFT da http://www.cs.princeton.edu/introcs/97data/. Per eseguirlo, avrai bisogno anche della classe Complesso da quella pagina (vedi la fonte per l'esatto URL).

Il codice legge in un file, passa a livello di finestra e fa un FFT su ogni finestra. Per ogni FFT cerca il coefficiente massimo e emette la frequenza corrispondente. Questo funziona molto bene per segnali puliti come un'onda sinusoidale, ma per un suono fischietto effettivo probabilmente dovrai aggiungere altro. Ho provato con alcuni file con il fischio che ho creato io stesso (usando il microfono integrato del mio computer portatile), il codice ha l'idea di cosa sta succedendo, ma per fare in modo che le note effettive siano più necessarie.

1) Potrebbe essere necessaria una tecnica di finestra più intelligente. Ciò che il mio codice utilizza ora è una semplice finestra rettangolare. Poiché la FFT presuppone che il singal in ingresso possa essere continuato periodicamente, vengono rilevate frequenze aggiuntive quando il primo e l'ultimo campione nella finestra non corrispondono. Questo fenomeno è noto come perdita spettrale (http://en.wikipedia.org/wiki/Spectral_leakage), in genere si utilizza una finestra che riduce i campioni all'inizio e alla fine della finestra (http://en.wikipedia.org/wiki/Window_function). Sebbene la perdita non debba causare il rilevamento della frequenza errata come massima, l'uso di una finestra aumenta la qualità del rilevamento.

2) Per abbinare le frequenze alle note effettive, è possibile utilizzare una matrice contenente le frequenze (ad esempio 440 Hz per un ') e quindi cercare la frequenza più vicina a quella che è stata identificata. Tuttavia, se il fischio è fuori sintonizzazione standard, questo non funzionerà più. Dato che il fischio è ancora corretto ma solo accordato in modo diverso (come una chitarra o un altro strumento musicale può essere sintonizzato in modo diverso e suonare ancora "buono", a patto che l'accordatura sia eseguita in modo coerente per tutte le corde), potresti comunque trovare le note guardando ai rapporti delle frequenze identificate. Puoi leggere http://en.wikipedia.org/wiki/Pitch_%28music%29 come punto di partenza. Questo è anche interessante: http://en.wikipedia.org/wiki/Piano_key_frequencies

3) Inoltre potrebbe essere interessante rilevare i punti nel tempo in cui ogni singolo tono inizia e si arresta.Questo potrebbe essere aggiunto come una fase di pre-elaborazione. Allora potresti fare una FFT per ogni singola nota. Tuttavia, se il fischietto non si ferma ma si piega solo tra le note, non sarebbe così facile.

hanno sicuramente un'occhiata alle librerie gli altri suggerivano. Non ne conosco nessuno, ma forse contengono già funzionalità per fare ciò che ho descritto sopra.

E ora al codice. Per favore fatemi sapere cosa ha funzionato per voi, trovo questo argomento piuttosto interessante.

Modifica: Ho aggiornato il codice per includere la sovrapposizione e un semplice mapper dalle frequenze alle note. Tuttavia, funziona solo per i whistler "sintonizzati", come menzionato sopra.

package de.ahans.playground; 

import java.io.File; 
import java.io.IOException; 
import java.util.Arrays; 

import javax.sound.sampled.AudioFormat; 
import javax.sound.sampled.AudioInputStream; 
import javax.sound.sampled.AudioSystem; 
import javax.sound.sampled.UnsupportedAudioFileException; 

public class FftMaxFrequency { 

    // taken from http://www.cs.princeton.edu/introcs/97data/FFT.java.html 
    // (first hit in Google for "java fft" 
    // needs Complex class from http://www.cs.princeton.edu/introcs/97data/Complex.java 
    public static Complex[] fft(Complex[] x) { 
     int N = x.length; 

     // base case 
     if (N == 1) return new Complex[] { x[0] }; 

     // radix 2 Cooley-Tukey FFT 
     if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } 

     // fft of even terms 
     Complex[] even = new Complex[N/2]; 
     for (int k = 0; k < N/2; k++) { 
      even[k] = x[2*k]; 
     } 
     Complex[] q = fft(even); 

     // fft of odd terms 
     Complex[] odd = even; // reuse the array 
     for (int k = 0; k < N/2; k++) { 
      odd[k] = x[2*k + 1]; 
     } 
     Complex[] r = fft(odd); 

     // combine 
     Complex[] y = new Complex[N]; 
     for (int k = 0; k < N/2; k++) { 
      double kth = -2 * k * Math.PI/N; 
      Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); 
      y[k]  = q[k].plus(wk.times(r[k])); 
      y[k + N/2] = q[k].minus(wk.times(r[k])); 
     } 
     return y; 
    } 

    static class AudioReader { 
     private AudioFormat audioFormat; 

     public AudioReader() {} 

     public double[] readAudioData(File file) throws UnsupportedAudioFileException, IOException { 
      AudioInputStream in = AudioSystem.getAudioInputStream(file); 
      audioFormat = in.getFormat(); 
      int depth = audioFormat.getSampleSizeInBits(); 
      long length = in.getFrameLength(); 
      if (audioFormat.isBigEndian()) { 
       throw new UnsupportedAudioFileException("big endian not supported"); 
      } 
      if (audioFormat.getChannels() != 1) { 
       throw new UnsupportedAudioFileException("only 1 channel supported"); 
      } 

      byte[] tmp = new byte[(int) length]; 
      byte[] samples = null;  
      int bytesPerSample = depth/8; 
      int bytesRead; 
      while (-1 != (bytesRead = in.read(tmp))) { 
       if (samples == null) { 
        samples = Arrays.copyOf(tmp, bytesRead); 
       } else { 
        int oldLen = samples.length; 
        samples = Arrays.copyOf(samples, oldLen + bytesRead); 
        for (int i = 0; i < bytesRead; i++) samples[oldLen+i] = tmp[i]; 
       } 
      } 

      double[] data = new double[samples.length/bytesPerSample]; 

      for (int i = 0; i < samples.length-bytesPerSample; i += bytesPerSample) { 
       int sample = 0; 
       for (int j = 0; j < bytesPerSample; j++) sample += samples[i+j] << j*8; 
       data[i/bytesPerSample] = (double) sample/Math.pow(2, depth); 
      } 

      return data; 
     } 

     public AudioFormat getAudioFormat() { 
      return audioFormat; 
     } 
    } 

    public class FrequencyNoteMapper { 
     private final String[] NOTE_NAMES = new String[] { 
       "A", "Bb", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" 
      }; 
     private final double[] FREQUENCIES; 
     private final double a = 440; 
     private final int TOTAL_OCTAVES = 6; 
     private final int START_OCTAVE = -1; // relative to A 

     public FrequencyNoteMapper() { 
      FREQUENCIES = new double[TOTAL_OCTAVES*12]; 
      int j = 0; 
      for (int octave = START_OCTAVE; octave < START_OCTAVE+TOTAL_OCTAVES; octave++) { 
       for (int note = 0; note < 12; note++) { 
        int i = octave*12+note; 
        FREQUENCIES[j++] = a * Math.pow(2, (double)i/12.0); 
       } 
      } 
     } 

     public String findMatch(double frequency) { 
      if (frequency == 0) 
       return "none"; 

      double minDistance = Double.MAX_VALUE; 
      int bestIdx = -1; 

      for (int i = 0; i < FREQUENCIES.length; i++) { 
       if (Math.abs(FREQUENCIES[i] - frequency) < minDistance) { 
        minDistance = Math.abs(FREQUENCIES[i] - frequency); 
        bestIdx = i; 
       } 
      } 

      int octave = bestIdx/12; 
      int note = bestIdx % 12; 

      return NOTE_NAMES[note] + octave; 
     } 
    } 

    public void run (File file) throws UnsupportedAudioFileException, IOException { 
     FrequencyNoteMapper mapper = new FrequencyNoteMapper(); 

     // size of window for FFT 
     int N = 4096; 
     int overlap = 1024; 
     AudioReader reader = new AudioReader(); 
     double[] data = reader.readAudioData(file); 

     // sample rate is needed to calculate actual frequencies 
     float rate = reader.getAudioFormat().getSampleRate(); 

     // go over the samples window-wise 
     for (int offset = 0; offset < data.length-N; offset += (N-overlap)) { 
      // for each window calculate the FFT 
      Complex[] x = new Complex[N]; 
      for (int i = 0; i < N; i++) x[i] = new Complex(data[offset+i], 0); 
      Complex[] result = fft(x); 

      // find index of maximum coefficient 
      double max = -1; 
      int maxIdx = 0; 
      for (int i = result.length/2; i >= 0; i--) { 
       if (result[i].abs() > max) { 
        max = result[i].abs(); 
        maxIdx = i; 
       } 
      } 
      // calculate the frequency of that coefficient 
      double peakFrequency = (double)maxIdx*rate/(double)N; 
      // and get the time of the start and end position of the current window 
      double windowBegin = offset/rate; 
      double windowEnd = (offset+(N-overlap))/rate; 
      System.out.printf("%f s to %f s:\t%f Hz -- %s\n", windowBegin, windowEnd, peakFrequency, mapper.findMatch(peakFrequency)); 
     }  
    } 

    public static void main(String[] args) throws UnsupportedAudioFileException, IOException { 
     new FftMaxFrequency().run(new File("/home/axr/tmp/entchen.wav")); 
    } 
} 
+0

L'ho provato di nuovo, il mio primo disco di fischietto è stato negativo perché includeva molto rumore sibilante (ero troppo vicino al microfono). Ora con una nuova registrazione funziona abbastanza bene in realtà. Anche avere la finestra si sovrappone in qualche modo aiuta a ottenere risultati migliori. Lo aggiungerò più tardi al codice. – ahans

2

Beh, si può sempre usare fftw per eseguire la Fast Fourier Transform. È una struttura molto ben rispettata. Una volta ottenuto un FFT del segnale, è possibile analizzare la matrice risultante per i picchi. Una semplice analisi dello stile dell'istogramma dovrebbe darti le frequenze con il volume maggiore. Quindi devi solo confrontare quelle frequenze con le frequenze che corrispondono alle diverse altezze.

+3

Avviso che trovare la frequenza giusta per un fischietto è terribilmente facile dopo aver fatto la FFT perché il fischio avrà generalmente esattamente un grande picco alla frequenza fischietto. –

1

Si potrebbe prendere in considerazione Python(x,y). È un framework di programmazione scientifica per python nello spirito di Matlab e ha funzioni semplici per lavorare nel dominio FFT.

0

Sono un fan della FFT, ma per i toni sinusoidali monofoniche e abbastanza puri di fischi, un rivelatore di zero-cross farebbe una lavoro molto migliore nel determinare la frequenza effettiva a un costo di elaborazione molto più basso. Il rilevamento zero-cross viene utilizzato nei contatori elettronici di frequenza che misurano il clock rate di qualsiasi cosa venga testata.

Se si va ad analizzare qualcosa di diverso da toni onda sinusoidale pura, quindi FFT è sicuramente la strada da percorrere.

A very simple implementation of zero cross detection in Java on GitHub

+0

Ciao, volevo sapere come lo zero crossing può aiutarmi a rilevare fischietti e scatti delle dita. –

+0

Il tono che produce il fischio può essere considerato abbastanza puro, quindi zero cross funzionerà bene per questo. Gli snap alle dita possono essere visti come un segnale molto corto e altamente transitorio. Puoi utilizzare un metodo di rilevamento della soglia molto semplice da attivare quando viene ascoltato. Dovrai scartare i suoni se l'ampiezza non ritorna a un livello base dopo un breve periodo di tempo altrimenti sarebbe semplicemente un rilevatore di "rumore". L'analisi della lunghezza di uno snap medio in un programma audio ti darà una risposta in ms che puoi usare come guida per rifiutare suoni più lunghi di questo. – ronnied

+0

@ronnied ottimizzazioni pre-mature? –

1

Se si utilizza Java, uno sguardo ai TarsosDSP biblioteca. Ha un buon rilevatore di pitch pronto all'uso.

Here è un esempio per Android, ma penso che non richieda troppe modifiche per usarlo altrove.