2

我正在尝试使用 reactive-banana 并想创建一个简单的合成器。有很多 GUI 示例,但我无法将它们应用于音频。由于音频 API 有回调说“给我 n 个音频样本”,我想我应该触发一个事件,每个回调(使用 newAddHandler 返回的 snd 部分)包含要生成的样本数量,应该写入的指针, 和时间信息来协调 MIDI 事件。传递给 reactimate 的 IO 操作会将样本写入指针。MIDI 事件将类似地从另一个回调中触发,并且还包含时间信息。

然而,这就是我卡住的地方。我猜音频信号应该是一种行为,但是我如何在适当的时间内“运行”一种行为来获取样本?正确的数量当然取决于两个音频回调之间可能发生的 MIDI 事件。

4

2 回答 2

2

假设意图是实时做某事,我认为为每个回调触发一个事件将是极其有限的。大多数音频 API 期望这些回调将很快返回(例如,通常您永远不会调用 malloc 或在一个中执行阻塞 IO)。触发 FRP 事件可能适用于非常简单的处理,但我认为如果您尝试做任何更复杂的事情,您会在音频流中丢失。

我希望一种更可行的方法是自己触发事件(通过时钟,或响应 GUI 事件等)并生成音频缓冲区,并从该缓冲区读取回调 API。我知道一些音频 API(例如 portaudio)有一个缓冲模式,可以自动处理其中的一些。尽管如果您只有一个回调 API,那么在此之上添加一个缓冲区并不难。

于 2014-02-15T18:30:21.663 回答
1

为了解决这样的问题,我发现采取语义观点很有用:什么是音频信号?我可以用什么类型来表示它?

本质上,音频信号是随时间变化的幅度

Audio = Time -> Double

这表明表示是一种行为

type Audio = Behavior Double

然后,我们可以使用<@>组合器查询特定时刻的幅度,即每当事件发生时。

但是,出于效率的原因,音频数据通常存储在64 字节(或 128、256)的块中。毕竟,处理需要快速,并且使用紧密的内部循环很重要。这建议将音频数据建模为一种行为

type Audio = Behavior (Vector Double)

它的值是 64 字节的音频数据块,每当对应于 64 字节的时间段结束时,它就会发生变化。

只有在明确语义模型后才能连接到其他 API。在这种情况下,将行为中的音频数据写入缓冲区似乎是个好主意,每当外部 API 调用您的回调时,就会显示缓冲区的内容。


顺便说一句,我不知道reactive-banana-0.8 是否足够快而对样本级音频处理有用。它应该不会太糟糕,但您可能必须选择一个相当大的块大小。

于 2014-02-19T13:57:11.760 回答