1

私はそれを楽しむために、サウンドスニペットを連続して高速でループさせて遊んでいて、この質問に出くわしました、それはうまく解決すると思います。高速になるとバグが発生します。その間にあるものはすべて削除され、1 バイトしか使用されないことがよくあるからです。そのため、配列内のすべてのバイトの平均を一気に取得するように変更したかったのです。問題は、バイトを int で割ることができないことです。バイトから int への変更に関しては、私は少しばかげています。私の解決策はこれを行うことでした(前述の質問から再び補足します。

 import javax.swing.JOptionPane;
 import javax.swing.JFileChooser;
 import javax.sound.sampled.*;
 import java.net.URL;
 import java.io.ByteArrayOutputStream;
 import java.io.ByteArrayInputStream;
 import java.util.Date;
 import java.io.File;

 class AcceleratePlayback {

     public static void main(String[] args) throws Exception {
         int playBackSpeed = 3;
     File soundFile;
         if (args.length>0) {
             try {
                 playBackSpeed = Integer.parseInt(args[0]);
             } catch (Exception e) {
                 e.printStackTrace();
                 System.exit(1);
             }
         }
         System.out.println("Playback Rate: " + playBackSpeed);

         JFileChooser chooser = new JFileChooser();
         chooser.showOpenDialog(null);
         soundFile = chooser.getSelectedFile();

         System.out.println("FILE: " + soundFile);
         AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile);
         AudioFormat af = ais.getFormat();

         int frameSize = af.getFrameSize();

         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         byte[] b = new byte[2^16];
         int read = 1;
         while( read>-1 ) {
             read = ais.read(b);
             if (read>0) {
                 baos.write(b, 0, read);
             }
         }
         System.out.println("End entire: \t" + new Date());

        //This is the important bit

         byte[] b1 = baos.toByteArray();
         byte[] b2 = new byte[b1.length/playBackSpeed];
         for (int ii=0; ii<b2.length/frameSize; ii++) {
             for (int jj=0; jj<frameSize; jj++) {
                     int b3=0;  
             for (int kk = 0; kk < playBackSpeed; kk++){
              b3 = b3+(int)b1[(ii*frameSize*playBackSpeed)+jj+kk];  
             }
             b3 = b3/playBackSpeed;
             b2[(ii*frameSize)+jj] = (byte)b3;
             }
         }
        //ends here

         System.out.println("End sub-sample: \t" + new Date());

         ByteArrayInputStream bais = new ByteArrayInputStream(b2);
         AudioInputStream aisAccelerated = new AudioInputStream(bais, af, b2.length);
         Clip clip = AudioSystem.getClip();
         clip.open(aisAccelerated);
         clip.loop(2*playBackSpeed);
         clip.start();

         JOptionPane.showMessageDialog(null, "Exit?");
     }
}

これはおそらく間違った方法だと思いますが、他に何ができるかわかりません。

最高です、アレックス。

4

1 に答える 1

1

私の以前の「解決策」が引用されたので、可変速再生に使用するものをより詳細にレイアウトします。正直に言うと、この質問で使用されているアプローチを完全には理解していないため、コードを改善しようとするつもりはありません。これを行う際に「質問に答えない」リスクがありますが、線形補間の使用に関する詳細が増えると、これが目的の高速ループを作成するのに十分な方法であることがわかるでしょう。

私が思いついたアプローチが最善であると主張しているわけではありません。私はサウンドエンジニアではありません。しかし、うまくいくようです。(提案された改善には常に感謝しています。)

これは、自分のゲーム用に作成したサウンド ライブラリ用です。Java Clip のアイデアに基づいていますが、いくつかの追加機能があります。私のライブラリには、データを保存する場所と、再生用の別の構造 (1 つは同時シングル再生用、もう 1 つはループ用) があります。どちらも、サウンドを逆方向に再生する程度まで、可変速が可能です。

「クリップ」データをロードして保持するには、「clipData」と呼ばれる単一の int[] を使用しますが、L と R の両方に使用するため、奇数と偶数の int はどちらの耳にも使用されます。

最初に「clipData」をロードしています:

    while((bytesRead = ais.read(buffer, 0, 1024)) != -1)
    {
        bufferIdx = 0;
        for (int i = 0, n = bytesRead / 2; i < n; i ++)
        {
            clipData[(int)clipIdx++] = 
                    ( buffer[(int)bufferIdx++] & 0xff )
                    | ( buffer[(int)bufferIdx++] << 8 ) ;
        }
    }

再生のために、このデータ配列を保持するオブジェクトには 2 つの get() メソッドがあります。1つ目は通常速度です。int は、clipData 配列にインデックスを付けるために使用されます (より大きなオーディオ ファイルの場合は、おそらく 'long' にする必要があります!):

public double[] get(int idx) throws ArrayIndexOutOfBoundsException
{
    idx *= 2; // assumed: stereo data

    double[] audioVals = new double[2];
    audioVals[0] = clipData[idx++];
    audioVals[1] = clipData[idx];

    return audioVals;
}

double[] の代わりに、float 配列を返すことは可能でしょうか?

これは、可変速度用の強化された get() メソッドです。線形補間を使用して、clipData へのインデックスとして使用される double の小数部分を考慮します。

public double[] get(double idx) throws ArrayIndexOutOfBoundsException
{
    int intPart = (int)idx * 2;
    double fractionalPart = idx * 2 - intPart;

    int valR1 = clipData[intPart++];
    int valL1 = clipData[intPart++]; 
    int valR2 = clipData[intPart++];
    int valL2 = clipData[intPart];

    double[] audioVals = new double[2];

    audioVals[0] = (valR1 * (1 - fractionalPart) 
            + valR2 * fractionalPart);

    audioVals[1] = (valL1 * (1 - fractionalPart) 
            + valL2 * fractionalPart);      

    return audioVals;
}

while(playing) ループ (データを再生 SourceDataLine にロードするため) には、サウンド データ配列を反復処理する「カーソル」と呼ばれる clipData に関連付けられた変数があります。通常の再生では、「cursor」が 1 ずつインクリメントされ、clipData の最後に到達したときにゼロに戻ることを確認するためにテストされます。

次のようなものを書くことができます:audioData = clipData.get(cursor++)データの連続するフレームを読み取る。

バリスピードの場合、上記は次のようになります。

audioData = clipData.get(cursor += speedIncrement);

「speedIncrement」は double です。2.0 に設定すると、再生速度が 2 倍になります。0.5 に設定すると、半分の速度になります。正しいチェックを入れれば、逆再生のために speedIncrement を負の値に等しくすることさえできます。

これは、速度がナイキスト値を超えない限り機能します (少なくとも理論的には)。繰り返しますが、'cursor' が clipData の端から外れていないことを確認するためにテストする必要がありますが、サウンド データ配列の反対側の適切な場所で再起動します。

お役に立てれば!

別の注意: 上記の get() メソッドを書き直して、単一のフレームではなく、バッファに相当する読み取りを送信することをお勧めします。私は現在、フレームごとに物事を行うことを実験しています。コードが少し理解しやすくなり、フレームごとの処理と応答性が向上すると思いますが、確実に速度が低下します。

于 2013-05-05T20:36:25.273 に答える