Анализировать "свист" звук для высоты тона / ноты - PullRequest
6 голосов
/ 16 января 2010

Я пытаюсь создать систему, которая сможет обрабатывать записи кого-то со свистом и выводить заметки.

Может ли кто-нибудь порекомендовать платформу с открытым исходным кодом, которую я могу использовать в качестве базы для распознавания нот / основного тона и анализа волновых файлов?

Заранее спасибо

Ответы [ 7 ]

10 голосов
/ 16 января 2010

Как уже говорили многие другие, FFT - это путь сюда. Я написал небольшой пример на Java с использованием FFT-кода http://www.cs.princeton.edu/introcs/97data/.. Чтобы запустить его, вам также понадобится класс Complex с этой страницы (точный URL см. В источнике).

Код читает файл, проходит по нему по окнам и делает FFT для каждого окна. Для каждого БПФ он ищет максимальный коэффициент и выдает соответствующую частоту. Это очень хорошо работает для чистых сигналов, таких как синусоида, но для реального свистящего звука вам, вероятно, придется добавить больше. Я протестировал несколько файлов со свистом, которые я создал сам (используя встроенный микрофон моего портативного компьютера), код дает представление о том, что происходит, но для получения реальных заметок необходимо сделать больше.

1) Возможно, вам понадобится более умная оконная техника. Теперь мой код использует простое прямоугольное окно. Поскольку БПФ предполагает, что входной сигнал может периодически продолжаться, дополнительные частоты обнаруживаются, когда первый и последний сэмплы в окне не совпадают. Это известно как спектральная утечка (http://en.wikipedia.org/wiki/Spectral_leakage), обычно используется окно, которое уменьшает выборку в начале и в конце окна (http://en.wikipedia.org/wiki/Window_function). Хотя утечка не должна приводить к тому, что в качестве максимума будет обнаружена неправильная частота, использование окна повысит качество обнаружения.

2) Чтобы сопоставить частоты с реальными нотами, вы можете использовать массив, содержащий частоты (например, 440 Гц для a), а затем искать частоту, наиболее близкую к той, которая была идентифицирована. Однако, если свист не соответствует стандартной настройке, это больше не будет работать. Учитывая, что свист все еще правильный, но настроен только по-другому (например, гитара или другой музыкальный инструмент могут настраиваться по-разному и звучать «хорошо», если настройка выполняется последовательно для всех струн), вы все равно можете найти ноты, посмотрев в соотношениях идентифицированных частот. Вы можете прочитать http://en.wikipedia.org/wiki/Pitch_%28music%29 в качестве отправной точки для этого. Это тоже интересно: http://en.wikipedia.org/wiki/Piano_key_frequencies

3) Кроме того, может быть интересно обнаружить моменты времени, когда каждый отдельный тон начинается и останавливается. Это может быть добавлено в качестве шага предварительной обработки. Вы можете сделать БПФ для каждой отдельной заметки. Однако, если вистлер не останавливается, а просто наклоняется между нотами, это будет не так просто.

Определенно взгляните на библиотеки, которые предложили другие. Я не знаю ни одного из них, но, возможно, они уже содержат функциональность для выполнения того, что я описал выше.

А теперь к коду. Пожалуйста, дайте мне знать, что сработало для вас, я нахожу эту тему довольно интересной.

Редактировать: Я обновил код, включив в него перекрытие и простое сопоставление частот и нот. Это работает только для "настроенных" вистлеров, хотя, как упомянуто выше.

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"));
    }
}
4 голосов
/ 27 декабря 2011

я думаю, что эта платформа с открытым исходным кодом подходит вам http://code.google.com/p/musicg-sound-api/

2 голосов
/ 16 января 2010

Ну, вы всегда можете использовать fftw для выполнения быстрого преобразования Фурье. Это очень уважаемая структура. Получив БПФ своего сигнала, вы можете проанализировать результирующий массив на наличие пиков. Простой анализ стиля гистограммы должен дать вам частоты с наибольшей громкостью. Тогда вам просто нужно сравнить эти частоты с частотами, которые соответствуют различным шагам.

1 голос
/ 16 сентября 2014

Если вы используете Java, взгляните на библиотеку TarsosDSP . Он имеет довольно хороший готовый детектор высоты тона.

Здесь - пример для Android, но я думаю, что для его использования в других местах не требуется слишком много модификаций.

1 голос
/ 16 января 2010

в дополнение к другим замечательным опциям:

1 голос
/ 16 января 2010

Возможно, вы захотите рассмотреть Python (x, y) .Это научная среда программирования для Python в духе Matlab, и она имеет простые функции для работы в области FFT.

0 голосов
/ 26 марта 2013

Я фанат БПФ, но для монофонических и довольно чистых синусоидальных тонов свиста, детектор с нулевым пересечением мог бы гораздо лучше определять фактическую частоту при гораздо более низких затратах на обработку. Обнаружение пересечения нуля используется в электронных счетчиках частоты, которые измеряют тактовую частоту всего, что тестируется.

Если вы собираетесь анализировать что-либо, кроме чистых синусоидальных тонов, то БПФ, безусловно, является подходящим вариантом.

Очень простая реализация обнаружения пересечения нуля в Java на GitHub

...