質問
私は誰かが口笛を吹いた記録を処理してメモを出力できるシステムを構築しようとしています。
Wave ファイルの音符/ピッチ認識および分析のベースとして使用できるオープンソース プラットフォームを推奨できる人はいますか?
前もって感謝します
解決
他の多くの人がすでに述べているように、ここでは FFT が最適です。以下の FFT コードを使用して Java で小さな例を作成しました。 http://www.cs.princeton.edu/introcs/97data/. 。これを実行するには、そのページの Complex クラスも必要です (正確な URL についてはソースを参照してください)。
コードはファイルを読み取り、ウィンドウごとに処理し、各ウィンドウで FFT を実行します。FFT ごとに最大係数を探し、対応する周波数を出力します。これは、正弦波のようなクリーンな信号には非常にうまく機能しますが、実際のホイッスルの音の場合は、おそらくさらに追加する必要があります。自分で作成した口笛を含むいくつかのファイルでテストしました (ラップトップ コンピューターの内蔵マイクを使用)。コードは何が起こっているかを理解していますが、実際の音を取得するにはさらに多くのことを行う必要があります。
1) もう少しインテリジェントなウィンドウテクニックが必要になるかもしれません。私のコードが現在使用しているのは、単純な長方形のウィンドウです。FFT は入力信号が周期的に継続できることを前提としているため、ウィンドウ内の最初と最後のサンプルが一致しない場合、追加の周波数が検出されます。これはスペクトル漏洩として知られています ( http://en.wikipedia.org/wiki/Spectral_leakage )、通常はウィンドウの最初と最後でサンプルを重み付けするウィンドウを使用します( http://en.wikipedia.org/wiki/Window_function )。漏れによって誤った周波数が最大値として検出されることはありませんが、ウィンドウを使用すると検出品質が向上します。
2) 周波数を実際の音と一致させるには、周波数を含む配列 (a' の場合は 440 Hz など) を使用し、識別された周波数に最も近い周波数を探します。ただし、笛吹きが標準のチューニングから外れている場合、これは機能しません。口笛はまだ正しいが、チューニングが異なるだけであることを考えると(ギターや他の楽器が、すべての弦で一貫してチューニングが行われている限り、異なるチューニングをしても「良い」音に聞こえるのと同じです)、検索することで音を見つけることができます。特定された周波数の比率で。あなたは読むことができます http://en.wikipedia.org/wiki/Pitch_%28music%29 その出発点として。これも興味深いです: http://en.wikipedia.org/wiki/Piano_key_frequency
3) さらに、個々のトーンが開始および停止する時点を検出することは興味深いかもしれません。これは前処理ステップとして追加できます。個別の音符ごとに FFT を実行できます。ただし、口笛吹き手が止まらず、音符の間を曲がるだけの場合、これはそれほど簡単ではありません。
他の人が提案したライブラリをぜひ見てください。私はそれらのどれも知りませんが、おそらく上で説明したことを行うための機能がすでに含まれているでしょう。
そしてコードへ。何が効果的だったか教えてください。このトピックは非常に興味深いと思います。
編集: コードを更新して、重複と、周波数からノートへの単純なマッパーを含めました。ただし、上で述べたように、これは「調整された」ホイッスルにのみ機能します。
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"));
}
}
他のヒント
私は、このオープンソースのプラットフォームはあなたにぴったりだと思います http://code.google.com/p/musicg-sound-api/
さて、あなたは常に高速フーリエ変換を実行するためにFFTW使用することができます。これは非常に尊敬のフレームワークです。あなたは信号のFFTを持ったら、ピークの結果の配列を分析することができます。シンプルなヒストグラムスタイルの分析は、あなたの最大のボリュームと頻度を与える必要があります。次に、あなただけの異なるピッチに対応した周波数にこれらの周波数を比較することがあります。
他の優れたオプションに加えて:
- csound ピッチ検出: http://www.csounds.com/manual/html/pvspitch.html
- fmod: http://www.fmod.org/ (無料版があります)
- アビオ: http://aubio.org/doc/pitchdetection_8h.html
あなたはのPython(x、y)はに検討する必要があります。これは、MATLABの精神でのpythonのための科学的なプログラミングフレームワークだし、それはFFTドメインでの作業のための簡単な機能を持っています。