Androidでギターチューナーを作成するためのフーリエ変換の適用。 パート1







音声データのスペクトル分析は、フーリエ変換と呼ばれるアルゴリズムに基づいています。 元のオーディオ信号を周波数成分に展開するとき、個々の周波数は高調波と呼ばれます。 メインハーモニクスがピッチを決定し、セカンダリハーモニクスが音色を決定します。 周波数の全スペクトル(高調波)を表示するためにフーリエ変換を使用するモバイルアプリケーションが多数あります。 また、ギターをカスタマイズするのに役立つモバイルアプリケーションがあります。 それらは原理に従って動作します。基本波はスペクトルの最大振幅値で見つかります。 基本的な高調波は、この高調波のすべての倍数の最小値、または高調波間のステップによって決定されるため、このステートメントは完全に真実ではありません。 音響信号のスペクトルの基本波の値を表示する方法を見つける必要があります。



この記事の最初の部分では、離散フーリエ変換の原理と、AudioRecordクラスを使用してAndroidデバイスからオーディオデータを記録する機能について検討します。



DFT、FFT。 JTransformsライブラリ。



パルスコード変調(PCM)として記録されたサウンドデータは、特定の時点での音波の振幅を示します。 19世紀に、ジャンバプティストフーリエは、任意の周期信号が無限の一連の単純な正弦波信号の合計として表されることを証明しました。 このことから、元の音声信号は、独自の振幅を持つ単純な正弦波信号の周波数順に並べられた合計として表すことができます。 したがって、1つの依存関係(振幅時間)から別の依存関係(振幅周波数)に進みます。 このような変換は、フーリエ変換と呼ばれます。



コンピューターでフーリエ変換を計算するために、離散フーリエ変換(DFT)の概念が導入されました。これには、入力として離散関数が必要です。



Javaプログラミング環境での離散フーリエ変換の原理を考慮してください。 まず、周波数がそれぞれ100 Hzと880 Hzの2つの高調波(コサイン)の合計を表す関数を説明します。



private double someFun(int index, int sampleRate) { final int amplitudeOfFirstHarmonic = 15; final int amplitudeOfSecondHarmonic = 1; final int frequencyOfFirstHarmonic = 100; final int frequencyOfSecondHarmonic = 880; return amplitudeOfFirstHarmonic * Math.cos((frequencyOfFirstHarmonic * 2 * Math.PI * index ) / sampleRate) + amplitudeOfSecondHarmonic *Math.cos((frequencyOfSecondHarmonic * 2 * Math.PI * index) / sampleRate); }
      
      





サンプリング周波数として、8000 Hzの値を取得し、ループ内でsomeFunc()関数を呼び出して、8000要素の配列にデータを入れます



 final int sampleRate = 8000; final int someFuncSize = 8000; double[] someFunc = new double[someFuncSize]; for (int i = 0; i < someFunc.length; i++) { someFunc[i] = someFun(i, sampleRate); }
      
      





その結果、関数の離散表現を定義する値の配列を取得します。 受信したデータを視覚的に表現するために、取得した値を配列から.csvファイルに取得し、Excelでグラフを作成します









図からわかるように、周波数が100 Hzの高調波の振幅は15であり、その値は、amplitudeOfFirstHarmonic定数で示されています。 周波数が100 Hz、振幅が15の第1高調波の上に、周波数が880 Hz、振幅が1の第2高調波が描画されます。

同じ原理を使用して、コサインとサインの2つの基底関数を作成します。 ここでのみ、頻度値を基本関数のメソッドパラメーターに転送します。



 private double cos(int index, int frequency, int sampleRate) { return Math.cos((2 * Math.PI * frequency * index) / sampleRate); } private double sin(int index, int frequency, int sampleRate) { return Math.sin((2 * Math.PI * frequency * index) / sampleRate); }
      
      





次に、離散フーリエ変換を実行するメソッドを定義します。 メソッドのパラメーターとして、単純なコサインの合計とこの関数のサンプリング周波数で構成される元の離散関数の値の配列を渡します。



 private double[] dft(double[] frame, int sampleRate) { final int resultSize = sampleRate / 2; double[] result = new double[resultSize * 2]; for (int i = 0; i < result.length / 2; i++) { int frequency = i; for (int j = 0; j < frame.length; j++) { result[2*i] +=frame[j] * cos(j, frequency, sampleRate); result[2*i + 1] +=frame[j] * sin(j, frequency, sampleRate); } result[2*i] =result[2*i] / resultSize; result[2*i + 1] = result[2*i + 1] / resultSize; } return result; }
      
      





フーリエ変換を実行した後、得られた値は、余弦および正弦の軸上で、周波数順に並べられたすべてのベクトルの射影を決定します。 このようなベクトルの長さを見つけるには、ピタゴラスの定理を適用する必要があります $インライン$ A = \ sqrt {a ^ 2 + b ^ 2} $インライン$ 。



 double[] result; long start = System.currentTimeMillis(); result = dft(someFunc, sampleRate); long finish = System.currentTimeMillis(); long timeConsumedMillis = finish - start; System.out.println("Time's dft: " + timeConsumedMillis); double[] amplitude = new double[sampleRate/2]; for (int i = 0; i < result.length / 2; i++) { amplitude[i] = Math.sqrt(result[2*i]*result[2*i] + result[2*i+1]*result[2*i+1]); System.out.println(i + ": " + "Projection on cos: " + result[2*i] + " Projection on sin: " + result[2*i + 1] + " amplitude: "+ amplitude[i] + "\n"); }
      
      





前のリストのコードを実行した結果、離散関数someFunc()の表現が、0からサンプリング周波数の半分の周波数で並べられた振幅値のセットとして得られました。



したがって、周波数が100 Hzおよび880 Hzの高調波の場合、振幅の値は、someFunc()メソッドの振幅定数FirstFirstHarmonicおよびitudeOfSecondHarmonicで指定された値に対応します。 また、残りの高調波は3998のままで、残りの高調波は離散フーリエ変換に送信された元の離散関数someFunc()で定義されていないため、振幅はゼロに近くなります。

















someFunc()関数を.csvファイルにフーリエ変換した後に得られた振幅の値を導出し、Excelでグラフをプロットします。 フーリエ変換の結果、元の信号のスペクトルが得られました











コンピューター上の離散フーリエ変換アルゴリズムは時間内に実行されます $インライン$ O(n ^ 2)$インライン$ プロセスが遅すぎる。 離散フーリエ変換の高速計算のために、彼らは高速フーリエ変換(FFT)を発明しました。 FFTアルゴリズムは、再帰的アプローチを使用して計算を実行します。これにより、アルゴリズムの実行時間は $インライン$ O(n * \ lg {n})$インライン$ 。 Javaプログラミング環境にはFFTアルゴリズムの既製の実装があり、これはJTransformsライブラリの形式で提示されます。



マイクからのデータの記録。 録音



AudioRecordクラスは、AndroidモバイルデバイスからPCMオーディオデータを受信します。

AudioRecordクラスのインスタンスを作成するには、AudioRecordクラスのコンストラクターで、次のようなパラメーターを指定する必要があります。



AudioRecordクラスのインスタンスを作成した後、startReading()メソッドを呼び出す必要があります。これにより、モバイルデバイスから録音が開始されます。 記録されたオーディオデータは、クラスインスタンスの作成時にminBufferSizeパラメーターで指定された部分サイズでAudioRecordクラスの内部バッファーに保存されます。 AudioRecordクラスの内部バッファーから、AudioRecordクラスのread()メソッドを呼び出して、記録されたデータを定期的に取得する必要があります。

たとえば、モバイルデバイスからオーディオデータを記録し、このデータを.csvテキストファイルに出力し、Excelを使用して受信したオーディオデータをプロットします。









AudioRecordクラスのインスタンスを作成するとき、8000 Hzのサンプリング周波数と1024のバッファーサイズを使用します。



記事のこの部分が興味深い場合は、記事の2番目の部分で、Android用のギターチューナーの作成に焦点を当てます。



All Articles