Pregunta

Estoy tratando de construir un sistema que sea capaz de procesar un registro de alguien silbar y salida de notas.

¿Alguien puede recomendar una plataforma de código abierto que puedo usar como la base para el reconocimiento de las notas / terreno de juego y análisis de los archivos de la onda?

Gracias de antemano

¿Fue útil?

Solución

Como muchos otros ya han dicho, FFT es el camino a seguir aquí. He escrito un pequeño ejemplo en Java utilizando el código de FFT http: //www.cs.princeton edu / introcs / 97data / . Con el fin de ejecutarlo, necesitará la clase Complejo de esa página también (ver la fuente de la URL exacta).

El código lee en un archivo, va ventana a gota durante ella y hace una FFT en cada ventana. Para cada FFT busca el coeficiente máximo y da salida a la frecuencia correspondiente. Esto funciona muy bien para las señales limpias como una onda sinusoidal, pero para un sonido real silbato es probable que tenga que añadir más. He probado con unos archivos con silbidos creé yo mismo (usando el micrófono integrado de la computadora portátil), el código se pone la idea de lo que está pasando, pero con el fin de obtener notas reales aún queda mucho por hacer.

1) Es posible que tenga alguna técnica de ventana más inteligente. Lo que mi código utiliza ahora es un simple ventana rectangular. Puesto que la FFT se supone que el Singal de entrada se puede continuar periódicamente, frecuencias adicionales se detectan cuando la primera y la última muestra en la ventana no coinciden. Esto se conoce como la fuga espectral ( http://en.wikipedia.org/wiki/Spectral_leakage ) , por lo general se utiliza una ventana que abajo-pesos muestras al principio y al final de la ventana ( http: // en.wikipedia.org/wiki/Window_function ). A pesar de que la fuga no debe causar la frecuencia equivocada para ser detectado como máximo, utilizando una ventana aumentará la calidad de detección.

2) Para hacer coincidir las frecuencias a las notas reales, se puede utilizar una matriz que contiene las frecuencias (como 440 Hz para una ') y luego buscar la frecuencia que es más cercana a la que ha sido identificado. Sin embargo, si el silbido está fuera de afinación estándar, esto no funciona más. Teniendo en cuenta que el silbido sigue siendo correcta, pero sólo sintonizado de manera diferente (como una guitarra u otro instrumento musical se puede ajustar de forma diferente y aún así sonar "bueno", siempre y cuando el ajuste se realiza constantemente para todas las cadenas), todavía se podía encontrar notas de mira en las relaciones de las frecuencias identificadas. Usted puede leer http://en.wikipedia.org/wiki/Pitch_%28music%29 como punto de partida en eso. Esto también es interesante: http://en.wikipedia.org/wiki/Piano_key_frequencies

3) Por otra parte podría ser interesante para detectar los puntos en el tiempo cuando cada tono individual se inicia y se detiene. Esto podría añadirse como una etapa de pre-procesamiento. Se podría hacer una FFT para cada nota individual a continuación. Sin embargo, si la marmota no se detiene sino que simplemente se dobla entre las notas, esto no sería tan fácil.

Sin duda, tenemos un vistazo a las bibliotecas de los otros sugirieron. No sé ninguna de ellas, pero tal vez ya contienen funcionalidad para hacer lo que he descrito anteriormente.

Y ahora con el código. Por favor, hágamelo saber lo que funcionó para usted, creo que este tema muy interesante.

Editar: He actualizado el código para incluir la superposición y un mapeador simple a partir de las frecuencias a las notas. Funciona sólo para silbadores "sintonizados" sin embargo, 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"));
    }
}

Otros consejos

Creo que esta plataforma de código abierto más le convenga http://code.google.com/p/musicg-sound-api/

Bueno, se podría utilizar siempre fftw para realizar la transformada rápida de Fourier. Es un marco muy respetado. Una vez que tenga una FFT de la señal se puede analizar la matriz resultante de picos. Un simple análisis de estilos histograma debe darle las frecuencias de mayor volumen. A continuación, sólo tiene que comparar esas frecuencias a las frecuencias que se corresponden con diferentes tonos.

Además de las otras grandes opciones:

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

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

Aquí es un ejemplo para android, pero creo que no requiere demasiado modificaciones para usarlo en otra parte.

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

Si va a analizar distintos de los tonos de onda sinusoidal pura nada, entonces FFT es definitivamente el camino a seguir.

Una aplicación muy sencilla de detección de cruce por cero en Java en GitHub

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top