LoginSignup
3
3

More than 5 years have passed since last update.

javaを用いた短時間フーリエ変換

Last updated at Posted at 2017-11-01
package application;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
 * 音声(wav)データの波形を見るプログラム
 * ただし、Wav(PCM・リトルエディアン)形式で保存された
 * ファイルのチャンネル1のみ出力
 *
 * @author karura
 *
 */
public class Main extends Application
{
    // 定数
    private final String    fileName    = "/onnsei/0119.wav";        // チャートに表示する音声ファイルへのパス
    private final double    sec         = 1;                     // チャートに表示する期間(s)
    private final double    cut         = 0.05;
    // 取得する音声情報用の変数
    private AudioFormat     format                  = null;
    private double[]       valuesActual            = null;  // 音声データ
    private double[]        spectrumActual          = null;
    private double[]        spectrumImaginal        = null;
    private double[]        spectrumpower           = null;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception
    {
          // 音声ストリームを取得
        File                file    = new File( fileName );
        AudioInputStream    is      = AudioSystem.getAudioInputStream( file );

        // メタ情報の取得
        format = is.getFormat();




        // 取得する標本数を計算
        // 1秒間で取得した標本数がサンプルレートであることから計算
        int mount   = (int) ( format.getSampleRate() *sec );
        int cutmount   = (int) ( format.getSampleRate() *cut );
        System.out.println(mount);
        System.out.println(cutmount);
        int samp   = (int) ( format.getSampleRate() );
        int hsamp = samp/2;
        int t = samp / cutmount;
        System.out.println(t);
        System.out.println(samp);

        // フォント色がおかしくなることへの対処
        System.setProperty( "prism.lcdtext" , "false" );

        // シーングラフの作成
        HBox        root    = new HBox();
        VBox        box1    = new VBox();
        VBox        box2    = new VBox();
        VBox        box3    = new VBox();
        root.getChildren().addAll( box1 , box2 ,box3);
        System.out.println( "loading wav data..." );
       initialize();


       int count = 0;
       double goukei = 0;
       double[] P       = new double[ valuesActual.length ];//短時間フーリエ変換後の一時値

       spectrumActual       = new double[ cutmount ];
       spectrumImaginal     = new double[ cutmount ];
       spectrumpower     = new double[ cutmount ];
      int n = (mount+cutmount-1)/cutmount;
      int m = 0;
      int o = cutmount;
      System.out.println("ループ回数"+n);


      for(int k =0; k<n; k++){
     if(o > valuesActual.length){

         double[] punc =Arrays.copyOfRange(valuesActual, m,valuesActual.length );
         DFT(punc, spectrumActual , spectrumImaginal , spectrumpower,false );

            for(int i = 0; i < cutmount; i++){
           P[i] = spectrumpower[i] *(t/hsamp);
     }}


     else{
      double[] punc =Arrays.copyOfRange(valuesActual, m,o );
      DFT(punc, spectrumActual , spectrumImaginal , spectrumpower,false );
      for(int s = 0; s < cutmount; s++){
        P[k] = P[k] + spectrumpower[s] * ((double)t / hsamp) ;

      }






     }
       m = m + cutmount;
       o = o + cutmount;
      count ++;

        // 離散変換後の波形をチャート表



      }
      int plength = 0;
      for(int x =0; x < P.length;x++ ){
            if(P[x] != 0){
            plength ++; 
            }


  }

      double[] POWER  = new double[ plength ];//
      for(int x =0; x < plength;x++ ){
        if(P[x] != 0){
            POWER[x]= P[x];
        }}
    //powerの最大値,最小値,平均値の算出








     //powerの最大値,最小値,平均値の算出
         double[] heikin = new double[20000];
         double[] max = new double[20000];
         double[] min = new double[20000];
         double minh = 10000;
         double maxh = 0;
         int counts =0;
         int countt=0;
         double goukeis=0;


         for(int i = 0; i< POWER.length;){

             if(POWER[i] <= 1.75){//閾値1.75として単語ごとに分割
                 if(i != 0){
                 heikin[countt] = goukeis /counts;
                 max[countt] = maxh;
                 min[countt] = minh;
                 countt++;
                 }
             while(POWER[i] <= 1.75){
                 i++;
                 if(i == POWER.length-1){
                     break;
                 }
                 }
             goukeis =0;
             counts =0;
             maxh = 0;
             }
             if(maxh < POWER[i]){
                 maxh = POWER[i];
             }
             if(minh > POWER[i]){
                 minh = POWER[i];
             }
             goukeis += POWER[i];
             counts++;


             if(i == POWER.length-1){
                 max[countt] = maxh;
                 min[countt] = minh;
                 heikin[countt] = goukeis /counts; 
                 System.out.println(heikin[countt]);
                 break;
             }
             i++;



         }

         for(int u = 0;u<=countt; u++){
             System.out.println("平均値"+heikin[u]);
             System.out.println("最大値"+max[u]);
             System.out.println("最小値"+min[u]);
         }







 for(int y =0; y < POWER.length;y++ ){

         System.out.println(POWER[y]);


 }




        box1.getChildren().add( createLineChart( "音声波形" , valuesActual ) );
        // 離散フーリエ変換後のスペクトルをチャート表示
        box2.getChildren().add( createLineChart( "スペクトル(実数部)" , spectrumActual ) );            // 折れ線グラフの追加
        box2.getChildren().add( createLineChart( "スペクトル(虚数部)" , spectrumImaginal ) );          // 折れ線グラフの追加
        box1.getChildren().add( createLineChartnew( "パワースペクトル" ,  P,n) );

        // シーンの作成
        Scene       scene   = new Scene( root , 800 , 400 );


        // ウィンドウ表示
        primaryStage.setScene( scene );
        primaryStage.show();

    }

    /**
     * 音声ファイルを読み込み、メタ情報とサンプリング・データを取得
     * @throws Exception
     */
    protected void initialize() throws Exception
    {
        // 音声ストリームを取得
        File                file    = new File( fileName );
        AudioInputStream    is      = AudioSystem.getAudioInputStream( file );

        // メタ情報の取得
        format = is.getFormat();



        // 取得する標本数を計算
        // 1秒間で取得した標本数がサンプルレートであることから計算
        int mount   = (int) ( format.getSampleRate() *sec );

        int samp   = (int) ( format.getSampleRate() );
        int t = samp / mount;
        System.out.println(t);

        // 音声データの取得
        valuesActual    = new double[ mount ];

        for( int i=0 ; i<mount ; i++ )
        {
            // 1標本分の値を取得
            int     size        = format.getFrameSize();
            byte[]  data        = new byte[ size ];
            int     readedSize  = is.read(data);

            // データ終了でループを抜ける
            if( readedSize == -1 ){ break; }

            // 1標本分の値を取得
            switch( format.getSampleSizeInBits() )
            {
                case 8:
                    valuesActual[i]   = (int) data[0];
                    break;
                case 16:
                    valuesActual[i]   = (int) ByteBuffer.wrap( data ).order( ByteOrder.LITTLE_ENDIAN ).getShort();
                    break;
                default:
            }
        }

        // 音声ストリームを閉じる
        is.close();
    }

    /**
     * 離散フーリエ変換
     * @param in フーリエ変換を行う実数配列
     * @param outActual 計算結果の実数部配列
     * @param outImaginal 計算結果の虚数部配列
     * @param winFlg 窓関数の使用フラグ
     */
    protected void DFT( double[] in , double[] outActual , double[] outImaginal ,double[] power, boolean winFlg )
    {
        // 配列初期化
        int  length             = in.length;

        // 離散フーリエ変換
        for( int k=0 ; k<length ; k++ )
        {
            // 初期化

            outActual[k]    = 0.0d;
            outImaginal[k]  = 0.0d;

            // 計算
            for( int n=0 ; n<length ; n++ )
            {
                // 入力値に窓関数を適用
                double normal   = ( !winFlg )? in[n]  : hanWindow( in[n] , n , 0 , length );

                // k次高周波成分を計算
                outActual[k]    +=        normal * Math.cos( 2.0 * Math.PI * (double)n * (double)k / (double)length );
                outImaginal[k]  += -1.0 * normal * Math.sin( 2.0 * Math.PI * (double)n * (double)k / (double)length );
            }



            // 残りの計算
            outActual[k]    /= length;
            outImaginal[k]  /= length;
            power[k] = Math.sqrt(Math.pow(outActual[k],2)+Math.pow(outImaginal[k],2));
        }
    }



    /**
     * 窓関数(ハン窓)
     * @param in 変換する値
     * @param i 配列中のインデックス
     * @param minIndex 配列の最小インデックス
     * @param maxIndex 配列の最大インデックス
     * @return
     */
    protected double hanWindow( double in , double i , double minIndex , double maxIndex )
    {
        // 入力値の正規化
        double normal   = i / ( maxIndex - minIndex );


        // ハン窓関数の値を取得
        double  han     =  0.5 - 0.5 * Math.cos( 2.0 * Math.PI * normal );

        return in * han;
    }



    /**
     * 折れ線グラフで波形表示
     * @param title グラフのタイトル文字
     * @param values グラフに出力するデータ配列
     * @return 作成したグラフノード
     */
    @SuppressWarnings("unchecked")
    protected Node createLineChart( String title , double[] values )
    {
        // 折れ線グラフ
        NumberAxis                  xAxis   = new NumberAxis();
        NumberAxis                  yAxis   = new NumberAxis();
        LineChart<Number, Number>   chart   = new LineChart<Number, Number>( xAxis , yAxis );
        chart.setMinWidth( 600 );
        chart.setMinHeight( 400 );

        // データを作成
        Series< Number , Number > series1    = new Series<Number, Number>();
        series1.setName( title  );
        for( int i=1 ; i<values.length ; i++ )
        {

            double m = ((i /sec));
            series1.getData().add( new XYChart.Data<Number, Number>( m , values[i] ) );
        }

        // データを登録
        chart.getData().addAll( series1 );

        // 見た目を調整
        chart.setCreateSymbols(false);                                                          // シンボルを消去
        series1.getNode().lookup(".chart-series-line").setStyle("-fx-stroke-width: 0.75px;");   // 線を細く

        return chart;
    }
    protected Node createLineChartnew( String title , double[] values ,int t)
    {

        // 折れ線グラフ
        NumberAxis                  xAxis   = new NumberAxis();
        NumberAxis                  yAxis   = new NumberAxis();
        LineChart<Number, Number>   chart   = new LineChart<Number, Number>( xAxis , yAxis );
        chart.setMinWidth( 600 );
        chart.setMinHeight( 400 );

        // データを作成
        Series< Number , Number > series1    = new Series<Number, Number>();
        series1.setName( title  );
        double time = 0;

        for(  int i =0; i< t ; i++ )
        {


            series1.getData().add( new XYChart.Data<Number, Number>(time , values[i] ) );
           time = time + cut;

        }

        // データを登録
        chart.getData().addAll( series1 );

        // 見た目を調整
        chart.setCreateSymbols(false);                                                          // シンボルを消去
        series1.getNode().lookup(".chart-series-line").setStyle("-fx-stroke-width: 0.75px;");   // 線を細く

        return chart;
    }}

このサイトを元にJavaで離散フーリエ変換を少し書き加え短時間フーリエ変換を行うようにした。
パワースペクトルを算出し,時間ごとの音声のパワーの変化をグラフ表示できるようになっている。
少し実行時間がかかってしまうのが難点である。
OSはiOSを用いている。

参照サイト: Javaで周波数分析をしてみる|軽Lab

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3