如何在 Java 中播放 (MIDI) 序列中的音频剪辑?

How can I play an audio clip in a (MIDI) sequence in Java?

我正尝试在 Java 中编写一个非常简单的 DAW,但我无法按顺序播放音频剪辑。我已经研究了 Java 声音中的采样和 MIDI 类,但我真正需要的是两者的混合体。

您似乎无法使用 MIDI 类 等音序器来播放您自己的音频剪辑。 我曾尝试使用调度来编写自己的音序器以按顺序播放 javax.sound.sampled.Clip,但时间差异太大。这不是一个真正可行的选择,因为它不计时。

有人对我如何解决这个问题有什么建议吗?

不,您不能使用音序器直接播放您自己的剪辑。 在 MIDI 世界中,您必须处理样本、乐器和音库。

很快,一个样本就是音频数据+循环点、样本覆盖的音符范围、基础音量和包络等信息。 乐器是一组样本,音库包含一组乐器。 如果你想用你自己的声音来播放一些音乐,你必须用它们制作一个音库。

您还需要使用不同于 Java 提供的默认实现的其他实现,因为该默认只读取专有格式的音库,这种格式至少在 15 年甚至 20 年前就消失了。 早在 2008-2009 年,就存在 Gervill 等人。它能够读取 SF2 和 DLS 音库。 SF2 和 DLS 是两种流行的音库格式,市场上有几种免费或付费的程序可以编辑它们。

如果你想反过来,从采样开始,这也和你注意到的一样,你不能依赖计时器、任务计划、Thread.sleep 之类的东西来获得足够的精确。 使用这些可以达到的最佳精度约为 10 毫秒,这对于音乐来说当然太少了。

通常的方法是通过将音频剪辑自己混合到最终剪辑中来生成音乐的音频。所以你可以达到帧精度。 事实上,这就是 MIDI 合成器的大致作用。

我可以证明,结合 MIDI 和样本方面的音频混合系统可以用 Java 编写,就像我自己写的一样,它目前可以使用样本和几个实时合成器,我也写了。

关键是使样本的音频数据在每帧的基础上可用,并且帧计数 command-processor/audio-mixer 管理 "commands," 的执行并收集和混合音频帧数据。在 44100 fps 的情况下,精度接近 0.02 毫秒。如果需要,我可以更详细地描述。

另一种可能更明智的方法是使用 Java bridge to a system such as Jack.


编辑:回答评论中的问题 (12/8/19)。

Java 中的音频样本数据通常保存在内存中(Java 使用 Clip)或从 .wav 文件中读取。因为 Clip 没有公开各个帧,所以我写了一个备用方法,并用它来将数据保存为范围为 -1 到 1 的有符号浮点数。有符号浮点数是保存音频数据的常用方法,我们将执行多项操作。

对于 .wav 音频的播放,Java 将读取数据与 AudioInputStream 相结合,并与 SourceDataLine 输出相结合。您的系统将不得不坐在中间,拦截 AudioInputStream,转换为 PCM 浮点帧,并在进行时计算帧数。

可以同时处理多个源或轨道,并合并(简单地添加归一化浮点数)到单个信号。该信号可以转换回字节并通过单个 SourceDataLine 发送出去播放。

从单个 SourceDataLine 中的任意第 0 帧计算输出帧将有助于保持构成的传入轨道协调,并将提供用于安排您希望在之前执行的任何其他命令的帧编号参考该帧正在输出(例如,更改源的 volume/pan 或合成器上的设置)。

我个人的 Clip 替代品与 AudioCue 非常相似,欢迎您检查和使用。主要区别在于,无论好坏,我在我的系统中一次处理一帧所有内容,并且 AudioCue 及其 "Mixer" 处理缓冲区加载。我有几个非常可信的人批评我个人的每帧系统效率低下,所以当我为 AudioCue 制作 public API 时,我屈服于这种先入之见。 [有一些方法可以向每帧系统添加缓冲以重新获得效率,并且每帧使调度更简单。所以我坚持我的每帧逻辑方案。]