2010-01-16 20 views
6

Estoy tratando de construir un sistema que pueda procesar un registro de alguien silbando y notas de salida.Analizar sonido "silbido" para tono/nota

¿Alguien puede recomendar una plataforma de código abierto que pueda usar como base para el reconocimiento de notas/tonos y el análisis de archivos de onda?

Gracias de antemano

Respuesta

10

como muchos otros Ya he dicho, FFT es el camino a seguir aquí. He escrito un pequeño ejemplo en Java usando el código FFT desde http://www.cs.princeton.edu/introcs/97data/. Para poder ejecutarlo, también necesitará la clase Compleja de esa página (consulte la fuente de la URL exacta).

El código se lee en un archivo, va en sentido de ventana sobre él y realiza una FFT en cada ventana. Para cada FFT, busca el coeficiente máximo y emite la frecuencia correspondiente. Esto funciona muy bien para señales limpias como una onda sinusoidal, pero para un silbido real probablemente tengas que agregar más. Probé con unos pocos archivos con silbidos que creé (usando el micrófono integrado de mi computadora portátil), el código capta la idea de lo que está sucediendo, pero para obtener notas reales hay que hacer más.

1) Es posible que necesite una técnica de ventana más inteligente. Lo que mi código usa ahora es una ventana rectangular simple. Como la FFT supone que la señal de entrada puede continuarse periódicamente, se detectan frecuencias adicionales cuando la primera y la última muestra de la ventana no coinciden. Esto se conoce como fuga espectral (http://en.wikipedia.org/wiki/Spectral_leakage), generalmente se usa una ventana que pesa muestras al principio y al final de la ventana (http://en.wikipedia.org/wiki/Window_function). Aunque la fuga no debe causar que se detecte la frecuencia incorrecta como el máximo, usar una ventana aumentará la calidad de la detección.

2) Para unir las frecuencias a las notas reales, puede usar una matriz que contenga las frecuencias (como 440 Hz para a ') y luego buscar la frecuencia más cercana a la que se ha identificado. Sin embargo, si el silbido está fuera de la afinación estándar, esto no funcionará más. Dado que el silbido sigue siendo correcto pero solo ajustado de manera diferente (como una guitarra u otro instrumento musical que se puede sintonizar de forma diferente y aún suena "bien", siempre y cuando la afinación se realice de forma consistente para todas las cuerdas), aún se pueden encontrar notas mirando en las relaciones de las frecuencias identificadas. Puede leer http://en.wikipedia.org/wiki/Pitch_%28music%29 como punto de partida para eso. Esto también es interesante: http://en.wikipedia.org/wiki/Piano_key_frequencies

3) Además, puede ser interesante detectar los puntos en el tiempo cuando cada tono individual se inicia y se detiene.Esto podría agregarse como un paso de procesamiento previo. Podrías hacer una FFT para cada nota individual entonces. Sin embargo, si el silbador no se detiene sino que simplemente se dobla entre notas, esto no sería tan fácil.

Definitivamente, eche un vistazo a las bibliotecas que los otros sugirieron. No conozco ninguno de ellos, pero tal vez ya contengan la funcionalidad para hacer lo que he descrito anteriormente.

Y ahora al código. Por favor, hágame saber qué funcionó para usted, encuentro este tema bastante interesante.

Edit: Actualicé el código para incluir la superposición y un asignador simple de las frecuencias a las notas. Sin embargo, solo funciona para whistlers "sintonizados", como se mencionó anteriormente.

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

He vuelto a probar, resulta que mi primer disco de silbido fue malo porque incluía mucho silbido (estaba demasiado cerca del micrófono). Ahora con una nueva grabación, funciona bastante bien en realidad. Tener la ventana solapada también ayuda a obtener mejores resultados. Agregaré eso más tarde al código. – ahans

2

Bueno, siempre se puede utilizar fftw para realizar la transformada rápida de Fourier. Es un marco muy respetado. Una vez que tienes una FFT de tu señal, puedes analizar la matriz resultante para los picos. Un análisis de estilo de histograma simple debería darle las frecuencias con mayor volumen. Entonces solo tienes que comparar esas frecuencias con las frecuencias que corresponden con diferentes tonos.

+3

Tenga en cuenta que encontrar la frecuencia correcta para un silbato es terriblemente fácil después de haber realizado la FFT porque el silbato generalmente tendrá exactamente un gran pico en la frecuencia con la que silbe. –

1

Es posible que desee considerar Python(x,y). Es un marco de programación científica para Python en el espíritu de Matlab, y tiene funciones sencillas para trabajar en el dominio de FFT.

0

Soy fan de la FFT, pero para los tonos monofónicos y bastante sinusoidales puras de silbidos, un detector de cruce cero haría una mucho mejor trabajo para determinar la frecuencia real a un costo de procesamiento mucho más bajo. La detección de cruce cero se utiliza en los contadores de frecuencia electrónicos que miden la frecuencia de reloj de lo que se está probando.

Si va a analizar algo más que tonos de onda sinusoidal pura, entonces FFT es definitivamente el camino a seguir.

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

+0

Hola, quería saber cómo el cruce por cero puede ayudarme a detectar silbidos y chasquidos de dedos. –

+0

El tono que produce el silbido se puede considerar bastante puro, por lo que cero cruzado funcionará bien para esto. Las instantáneas de los dedos se pueden ver como una señal muy corta y altamente transitoria. Puede usar un método de detección de umbral muy simple para disparar cuando se escuche. Tendrá que descartar los sonidos si la amplitud no vuelve a un nivel base después de un corto período de tiempo, de lo contrario, simplemente sería un detector de "ruido". El análisis de la duración de un chasquido de dedo promedio en un programa de audio le daría una respuesta en ms que puede utilizar como una guía para rechazar sonidos que son más largos que esto. – ronnied

+0

@ optimizaciones prematuras rondeadas? –

1

Si utiliza Java, echar un vistazo a TarsosDSP biblioteca. Tiene un buen detector de paso listo para usar.

Here es un ejemplo para Android, pero creo que no requiere demasiadas modificaciones para usarlo en otro lugar.

Cuestiones relacionadas