播放不同音高的正弦波

时间:2012-09-10 17:46:24

标签: java javasound sine

我有一个(非常简单的)代码,我把它放在一起,播放特定频率的正弦波并播放它 - 它没有问题:

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,但这提供了离散频率的“块”,而不是我所追求的平滑过渡。除了我已经尝试过的之外,搜索似乎并没有提供太多其他内容。

这是对theramin的仿真,因此理想情况下需要尽可能平滑的低延迟。

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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

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

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

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

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

每个缓冲区加载一次咨询音高变量将不会令人满意,因此下一个逻辑步骤是让你的while循环在你处理的每个帧中检查这个变量!是的,这意味着每秒传输音调变量44100次,如果这是你的帧率。

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

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

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