Haskell中的反应式音频合成

Haskell中的反应式音频合成,haskell,audio,midi,reactive-banana,Haskell,Audio,Midi,Reactive Banana,我正试图开始与反应香蕉,并希望创建一个简单的合成器。有很多GUI示例,但我很难将它们应用到音频中。由于音频API有一个回调,上面写着“给我n个音频样本”,我想我应该在每次回调(使用newAddHandler返回的snd部分)中触发一个事件,该事件包含要生成的样本数、它们应该写入的指针以及协调MIDI事件的时间信息。然后,传递给reactimate的IO操作会将样本写入指针。MIDI事件将类似地从另一个回调触发,并且还包含计时信息 然而,这就是我被卡住的地方。我猜音频信号应该是一种行为,但如何在适

我正试图开始与反应香蕉,并希望创建一个简单的合成器。有很多GUI示例,但我很难将它们应用到音频中。由于音频API有一个回调,上面写着“给我n个音频样本”,我想我应该在每次回调(使用newAddHandler返回的snd部分)中触发一个事件,该事件包含要生成的样本数、它们应该写入的指针以及协调MIDI事件的时间信息。然后,传递给reactimate的IO操作会将样本写入指针。MIDI事件将类似地从另一个回调触发,并且还包含计时信息


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

假设其目的是要进行现场直播,我认为为每次回调触发一个事件将是非常有限的。大多数音频API都期望这些回调会很快返回(例如,通常您永远不会调用malloc或在其中执行阻塞IO)。触发FRP事件可能对非常简单的处理有效,但我认为如果您尝试执行更复杂的操作,您将在音频流中退出


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

要解决类似的问题,我发现从语义的角度来看很有用:什么是音频信号?我可以用什么类型来表示它

本质上,音频信号是时变振幅

Audio = Time -> Double
这意味着表现为一种行为

type Audio = Behavior Double
type Audio = Behavior (Vector Double)
然后,我们可以使用
组合器来查询特定时刻的振幅,即事件发生时的振幅

然而,出于效率考虑,音频数据通常存储在64字节(或128256字节)的块中。毕竟,处理需要快速,使用紧密的内部循环很重要。这建议将音频数据建模为一种行为

type Audio = Behavior Double
type Audio = Behavior (Vector Double)
其值为64字节的音频数据块,并且在与64字节对应的时间段结束时会发生变化

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



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

这不是反应性的,但可能仍然很有趣:关于计时的注意事项在感谢中,回调中触发的事件可能意味着“填充下一个缓冲区”,而不是当前缓冲区,然后,可以将前一个回调所填充的缓冲区交给API,以避免暂停(响应系统时钟触发事件可能会导致漂移并最终退出)。无论在何处触发事件,最大的问题是我问题的第二段中描述的反应性香蕉部分。@如果不存在,我怀疑您希望使用类似于
行为生成器
(例如使用最新的
bytestring
的生成器)的类型。您可以累积事件来创建该行为,并将音频输入添加到该行为中,然后输出回调刷新该行为。不过,使用生成器是否允许我使用反应式香蕉来计算示例?我想知道信封、振荡器、滤波器等的行为(它们基本上都是时间->双精度的),但我仍然不明白如何从中提取样本。你需要一个时钟来驱动网络(例如,某种类型的
事件时间
)。您可以设置采样率(例如44100),并由此计算采样之间的时间,这是您的时钟事件需要触发的频率(使用产量循环或类似计划)。然后使用
oscil clock
获取输出。一个复杂的问题是,对于实时音频,如果时钟在每个采样点运行,您将无法进行很多处理。通常,每个时钟事件都会触发一个音频数据块,64或128个样本,因此
行为(时间->向量双精度)
。忽略性能并假设行为双精度,您和John L所说的是时钟机制为每个样本触发一个事件(例如每秒44100次)?拥有每秒X次触发的时钟事件是构建每秒X次更改的
行为的好方法。太棒了!我开始了解这些片段是如何组合在一起的,但似乎我仍然缺少一些重要的东西。如何制作时钟事件?查看可用的函数,我得到的最接近的函数是使用replicate来获取[()],我可以在“生成n个样本”事件上溢出、应用、收集并最终生成fmap,但我猜一个缓冲区中的所有事件都将共享相同的时间戳,即“生成n个样本”事件发生时的测量时间,而不是t/sampleRate,其中t=[0..]。您需要导入外部时钟机制。大多数UI编程框架,如SDL、wxWidgets等,都提供计时器对象。(您也可以通过使用
forkIO
threadDelay
来滚动您的文件)使用
fromAddHandler
将其转换为
事件。关键是,
reactive banana
不提供默认的计时器实现,因为不同的用例需要不同的精度等。您必须自己创建一个计时器,这意味着创建一个
偶数计时器