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在上面的示例中,由另一个线程更改并实时更新声音。我已经尝试创建字节数组,然后根据所需值在单独的线程中填充它,但似乎没有得到任何东西或失真。我也尝试过写入SourceDataLinein 块,但这提供了离散频率的“块”,而不是我所追求的平滑过渡。除了我已经尝试过的以外,搜索似乎并没有提供太多帮助。

它用于模拟 theramin,因此理想情况下需要尽可能平滑低延迟。

我可以提前做没有问题 - 但事实证明现场很棘手。有没有人可以分享任何想法或例子?

4

2 回答 2

1

看起来你只是从数据数组中读取一次,所以无论数据是否被修改,都只会产生一个音高。我认为您需要在循环中播放较短的波形,每次迭代都会重新读取数据数组。我不知道 SourceDataLine 类是如何工作的,所以我不知道这是否会产生未分段的声音。

于 2012-09-10T17:54:16.807 回答
1

我写了一个 Java theremin,它可以在这个 url 上播放:

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

在该站点上,有两个指向 Java Gaming 论坛的链接,其中讨论了所涉及的各种问题。

我使用波表而不是 sin 函数来生成 PCM 数据,但是可以以类似的方式设置更改输入 sin 函数的变量的方法。

最简单的做法是在创建声音字节的最里面的 while 循环中查询基类中的 volatile float 或 double。您的 GUI 可以更新此变量,而 while 循环可以基于此计算音高。

每次缓冲区加载时查询一次音高变量不会令人满意,因此下一个合乎逻辑的步骤是让您的 while 循环在您处理的每一帧中检查此变量!是的,这意味着每秒引用音高变量 44100 次,如果那是您的帧速率。

但即便如此,问题仍然是响应受限于 JVM 时间切片线程的方式。当声音线程没有主动循环时,它也不会读取已放入“pitch”变量的新值!回想一下,虽然声音线程非常能够保持帧速率恒定,但它不是“实时”这样做的,而是在活动的爆发中。因此,在声音处理线程休眠期间,GUI 可能会多次覆盖音高值,从而导致音高不连续。

为了解决这个问题,我创建了一个 FIFO,在其中存储所有 GUI 生成的音高变化事件并为其添加时间戳。在最里面的声音处理循环中,这个 FIFO 被参考(而不是前面提到的 volatile double)来确定要使用的音高值,基于每个样本。由于来自 GUI 的音高值将是离散值并且出现在不同的时间,因此您需要一种内插音高值的方法来填补空白。我使用时间戳和值来计算每帧插值,从而在每个样本的最内层循环中更新音高变量。

我认为我写的解决方案仍然存在很多问题,我期待着重新审视这个!

于 2012-09-11T00:27:38.450 回答