3

特定の周波数の正弦波を再生して再生する(非常に単純な)コードを一緒にスローしました-問題なく動作します:

public class Sine {

    private static final int SAMPLE_RATE = 16 * 1024;
    private static final int FREQ = 500;

    public static void main(String[] args) throws LineUnavailableException {
        final AudioFormat af = new AudioFormat(SAMPLE_RATE, 8, 1, true, true);
        try(SourceDataLine line = AudioSystem.getSourceDataLine(af)) {
            line.open(af, SAMPLE_RATE);
            line.start();
            play(line);
            line.drain();
        }
    }

    private static void play(SourceDataLine line) {
        byte[] arr = getData();
        line.write(arr, 0, arr.length);
    }

    private static byte[] getData() {
        final int LENGTH = SAMPLE_RATE * 100;
        final byte[] arr = new byte[LENGTH];
        for(int i = 0; i < arr.length; i++) {
            double angle = (2.0 * Math.PI * i) / (SAMPLE_RATE/FREQ);
            arr[i] = (byte) (Math.sin(angle) * 127);
        }
        return arr;
    }
}

また、getData()メソッドを変更して、再生時にピッチが徐々に変化するバイト配列を返すようにすることもできますが、問題はありません。

ただし、「ライブ」の周波数と振幅をスムーズに更新できる正弦波を連続的に再生する方法に苦労しています。つまりFREQ、上記の例で別のスレッドによって変更され、サウンドがリアルタイムで更新されます。バイト配列を作成してから、後で必要な値に基づいて別のスレッドに入力しようとしましたが、何も得られないか、歪みがあるようです。また、チャンクに書き込もうとしましたSourceDataLineが、これは、私が求めているスムーズな移行ではなく、離散周波数の「ブロック」を提供します。周りを検索しても、私がすでに試したこと以外はあまり提供されていないようです。

これはセラミンのエミュレーション用であるため、理想的には可能な限りスムーズで低遅延である必要があります。

事前に問題なく行うことができますが、ライブは難しいことがわかっています。共有できるアイデアや例はありますか?

4

2 に答える 2

1

データ配列から読み取っているのは1回だけのように見えるため、データが変更されているかどうかに関係なく、1つのピッチのみが生成されます。反復ごとにデータ配列を再読み取りするループ内で、より短いウェーブを再生する必要があると思います。SourceDataLineクラスがどのように機能するかはわかりませんが、これによってセグメント化されていないサウンドが生成されるかどうかはわかりません。

于 2012-09-10T17:54:16.807 に答える
1

Java テルミンを作成しました。この URL で再生できます。

http://www.hexara.com/VSL/JTheremin.htm

そのサイトには、関連するさまざまな問題について議論が行われた Java Gaming フォーラムへの 2 つのリンクがあります。

PCMデータの生成にはsin関数ではなくウェーブテーブルを使用していますが、sin関数に入力する変数の変更方法も同様に設定できます。

最も簡単な方法は、サウンド バイトが作成される最も内側の while ループで参照される volatile float または double を基底クラスに持つことです。GUI はこの変数を更新でき、while ループはこれに基づいてピッチ計算を行うことができます。

バッファのロードごとに 1 回ピッチ変数を参照するだけでは十分ではないため、次の論理的なステップは、処理するフレームごとに while ループでこの変数をチェックすることです。はい、それは、フレームレートが 1 秒あたり 44100 回ピッチ変数を参照することを意味します。

それでも、JVM がスレッドをタイム スライスする方法によって応答が制限されるという問題が残ります。サウンド スレッドがアクティブにループしていない場合、「ピッチ」変数に配置された新しい値も読み取られません。サウンド スレッドはフレーム レートを一定に保つことができますが、それは「リアルタイム」ではなく、アクティビティのバーストで行われていることを思い出してください。したがって、GUI は、サウンド処理スレッドがスリープしている間にピッチ値を数回上書きする可能性があり、その結果、ピッチの不連続が発生します。

これを回避するために、GUI で生成されたすべてのピッチ変更イベントを保存してタイムスタンプを付ける FIFO を作成しました。最も内側のサウンド処理ループでは、サンプルごとに使用するピッチ値を決定するために、(前述の volatile double の代わりに) この FIFO が参照されます。GUI からのピッチ値は離散値であり、さまざまなタイミングで取得されるため、ギャップを埋めるためにピッチ値を補間する方法が必要です。タイムスタンプと値を使用してフレームごとの補間を計算し、サンプルごとに最も内側のループでピッチ変数を更新します。

私が書いた解決策にはまだ多くの問題があると思います。これを再検討することを楽しみにしています!

于 2012-09-11T00:27:38.450 に答える