2010-01-16 23 views
6

Tôi đang cố gắng xây dựng một hệ thống có thể xử lý bản ghi của một ai đó huýt sáo và ghi chú đầu ra.Phân tích âm thanh "còi" cho quảng cáo chiêu hàng/ghi chú

Có ai có thể đề xuất nền tảng nguồn mở mà tôi có thể sử dụng làm cơ sở để nhận dạng nốt/nốt và phân tích tệp sóng không?

Cảm ơn trước

Trả lời

10

Như nhiều người khác đã nói, FFT là con đường để đi đến đây. Tôi đã viết một ví dụ nhỏ trong Java sử dụng mã FFT từ http://www.cs.princeton.edu/introcs/97data/. Để chạy nó, bạn sẽ cần lớp phức tạp từ trang đó (xem nguồn cho URL chính xác).

Mã đọc trong một tệp, đi qua cửa sổ thông minh và thực hiện FFT trên mỗi cửa sổ. Đối với mỗi FFT nó tìm kiếm hệ số tối đa và đầu ra tần số tương ứng. Điều này làm việc rất tốt cho các tín hiệu sạch như sóng sin, nhưng đối với một âm thanh còi thực tế bạn có thể phải thêm nhiều hơn nữa. Tôi đã thử nghiệm với một vài tập tin với huýt sáo tôi tạo ra bản thân mình (sử dụng mic tích hợp của máy tính xách tay của tôi), mã không có ý tưởng về những gì đang xảy ra, nhưng để có được ghi chú thực tế cần phải được thực hiện.

1) Bạn có thể cần một số kỹ thuật cửa sổ thông minh hơn. Mã của tôi sử dụng bây giờ là một cửa sổ hình chữ nhật đơn giản. Kể từ khi FFT giả định rằng đầu vào singal có thể được định kỳ tiếp tục, tần số bổ sung được phát hiện khi đầu tiên và mẫu cuối cùng trong cửa sổ không phù hợp. Điều này được gọi là rò rỉ phổ (http://en.wikipedia.org/wiki/Spectral_leakage), thường là một cửa sổ sử dụng các mẫu có trọng lượng xuống ở đầu và cuối cửa sổ (http://en.wikipedia.org/wiki/Window_function). Mặc dù rò rỉ không nên gây ra tần số sai được phát hiện là tối đa, bằng cách sử dụng một cửa sổ sẽ làm tăng chất lượng phát hiện.

2) Để khớp tần số với ghi chú thực tế, bạn có thể sử dụng một mảng chứa tần số (như 440 Hz cho ') và sau đó tìm tần số gần nhất với tần số đã được xác định. Tuy nhiên, nếu tiếng huýt sáo tắt điều chỉnh tiêu chuẩn, điều này sẽ không hoạt động nữa. Cho rằng tiếng huýt sáo vẫn đúng nhưng chỉ điều chỉnh khác nhau (như guitar hoặc nhạc cụ khác có thể được điều chỉnh khác nhau và vẫn âm thanh "tốt", miễn là điều chỉnh được thực hiện nhất quán cho tất cả các chuỗi), bạn vẫn có thể tìm thấy ghi chú bằng cách tìm kiếm theo tỷ lệ của các tần số được xác định. Bạn có thể đọc http://en.wikipedia.org/wiki/Pitch_%28music%29 làm điểm bắt đầu trên đó. Điều này cũng thú vị: http://en.wikipedia.org/wiki/Piano_key_frequencies

3) Hơn nữa, có thể thú vị khi phát hiện các điểm đúng lúc khi mỗi giai điệu riêng lẻ bắt đầu và dừng.Điều này có thể được thêm dưới dạng bước xử lý trước. Bạn có thể thực hiện FFT cho từng ghi chú riêng lẻ sau đó. Tuy nhiên, nếu huýt sáo không dừng lại mà chỉ uốn cong giữa các ghi chú, điều này sẽ không dễ dàng như vậy.

Chắc chắn hãy xem các thư viện mà những người khác đã đề xuất. Tôi không biết bất kỳ người trong số họ, nhưng có lẽ họ đã có chức năng để làm những gì tôi đã mô tả ở trên.

Và bây giờ đến mã. Xin vui lòng cho tôi biết những gì đã làm việc cho bạn, tôi thấy chủ đề này khá thú vị.

Chỉnh sửa: Tôi đã cập nhật mã để bao gồm chồng chéo và trình ánh xạ đơn giản từ tần số đến ghi chú. Nó chỉ hoạt động đối với các "huýt sáo" được điều chỉnh, như đã đề cập ở trên.

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

Tôi đã thử nghiệm một lần nữa, hóa ra hồ sơ đầu tiên của tôi huýt sáo là xấu vì nó bao gồm rất nhiều tiếng ồn rít (tôi đã quá gần mic). Bây giờ với một ghi âm mới nó hoạt động khá tốt thực sự. Ngoài ra có cửa sổ chồng chéo phần nào giúp để có được kết quả tốt hơn. Tôi sẽ thêm mã đó vào sau. – ahans

2

Vâng, bạn luôn có thể sử dụng fftw để thực hiện Chuyển đổi Fourier nhanh. Đó là một khuôn khổ rất được kính trọng. Một khi bạn đã có một FFT của tín hiệu của bạn, bạn có thể phân tích các mảng kết quả cho các đỉnh. Một phân tích kiểu biểu đồ đơn giản sẽ cung cấp cho bạn các tần số có khối lượng lớn nhất. Sau đó, bạn chỉ cần so sánh các tần số đó với tần số tương ứng với các nốt khác nhau.

+3

Lưu ý rằng việc tìm tần số phù hợp cho tiếng còi là rất dễ dàng sau khi bạn đã thực hiện FFT vì tiếng còi nói chung sẽ có chính xác một mức tăng đột biến lớn ở tần suất bạn huýt sáo. –

1

Bạn có thể muốn xem xét Python(x,y). Đó là một khuôn khổ lập trình khoa học cho python theo tinh thần của Matlab, và nó có các chức năng dễ dàng để làm việc trong miền FFT.

0

Tôi là một fan hâm mộ của FFT nhưng đối với những tông màu sin đơn âm và khá tinh khiết của huýt sáo, một máy dò zero-cross sẽ làm một công việc tốt hơn nhiều khi xác định tần số thực tế với chi phí xử lý thấp hơn nhiều. Phát hiện zero-cross được sử dụng trong các bộ đếm tần số điện tử để đo tốc độ xung nhịp của bất kỳ thứ gì đang được kiểm tra.

Nếu bạn phân tích bất kỳ điều gì khác ngoài tông màu sóng thuần túy, thì FFT chắc chắn là cách để đi.

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

+0

Xin chào, tôi muốn biết cách giao cắt số không có thể giúp tôi phát hiện còi và ngón tay. –

+0

Các giai điệu mà huýt sáo sản xuất có thể được coi là khá tinh khiết vì vậy không chéo sẽ làm việc tốt cho việc này. Finger snaps có thể được xem như một tín hiệu rất ngắn và rất thoáng qua. Bạn có thể sử dụng một phương pháp phát hiện ngưỡng rất đơn giản để kích hoạt khi nó được nghe. Bạn sẽ phải loại bỏ âm thanh nếu biên độ không trở về một mức cơ sở sau một khoảng thời gian ngắn nếu không nó sẽ chỉ đơn giản là một máy dò "nhiễu". Phân tích độ dài của một ngón tay chụp trung bình trong một chương trình âm thanh sẽ cung cấp cho bạn một câu trả lời trong ms mà bạn có thể sử dụng như một hướng dẫn để từ chối âm thanh dài hơn này. – ronnied

+0

@ronnied tối ưu hóa trước khi trưởng thành? –

1

Nếu bạn sử dụng Java, có một cái nhìn tại TarsosDSP thư viện. Nó có một máy phát hiện độ sẵn sàng để đi khá tốt.

Here là ví dụ cho android, nhưng tôi nghĩ rằng nó không đòi hỏi quá nhiều sửa đổi để sử dụng ở nơi khác.

Các vấn đề liên quan