【问题标题】:Creating a DSP system from scratch从头开始创建 DSP 系统
【发布时间】:2011-09-14 02:45:38
【问题描述】:

我喜欢电子音乐,我对它的节奏很感兴趣。 我在可用于播放音频、过滤器等的库上的 Stack Overflow 上发现了很多有用的问题。但我真正好奇的是实际情况如何:数据是如何在效果器和振荡器之间传递的?我已经对 dsp 的数学方面进行了研究,我已经解决了这个问题,但我不确定要使用什么缓冲系统等。最终目标是有一个简单的对象层次结构的效果和振荡器来传递数据彼此之间(如果我最终没有拔出所有头发试图实现它,可能会使用多线程)。它不会成为下一个 Propellerhead 原因,但我对它的工作原理很感兴趣,这更像是一种练习,而不是最终产品。

目前我使用 .net 和 C#,并且我最近学习了 F#(这可能会或可能不会导致一些有趣的数据处理方式)但如果这些不适合这份工作,我可以在必要时学习另一个系统.

问题是:使用缓冲区通过程序获取大量信号数据的最佳方法是什么?例如,使用队列、数组、链表等会更好吗?每次我对系统应用效果时,我应该使样本不可变并创建一组新数据,还是只编辑缓冲区中的值?我应该有一个调度程序/线程池样式的对象来组织传递数据,还是应该让效果函数在彼此之间直接传递数据?

谢谢。

编辑:另一个相关问题是我将如何使用 Windows API 来播放这个数组?我真的不想使用 DirectShow,因为微软现在几乎让它死了

EDIT2:感谢所有答案。在查看了所有技术之后,我将使用 XNA 4(我花了一段时间在互联网上搜索并找到了 this site,它解释了如何做到这一点)或 NAudio 来输出音乐......还不确定哪一个,取决于如何先进的系统最终成为。当 C# 5.0 出现时,我将使用它的异步功能在此基础上创建效果架构。我几乎平等地使用了每个人的答案,所以现在我有一个难题,谁应该给予赏金......

【问题讨论】:

  • 你能问一个具体的问题吗?
  • 是的,抱歉,有点含糊。

标签: c# .net audio f# buffering


【解决方案1】:

你可以看看BYOND。它是在 C# 中用于程序化音频/midi 乐器和效果创建的环境。它既可以作为独立的,也可以作为 VST 乐器和效果器使用。

全面披露我是 BYOND 的开发者。

【讨论】:

  • 听起来很有趣,但我并没有真正回答这个问题。
【解决方案2】:

好吧,那我也来试试赏金吧:)

我实际上处于非常相似的情况。我制作电子音乐已有很长时间了,但直到最近几年我才开始探索实际的音频处理。

你提到你研究过数学。我认为这很关键。我目前正在努力通过 Ken Steiglitz 的数字信号处理入门 - 应用到数字音频和计算机音乐。如果您不知道复数和相量,那将非常困难。

我是一个 Linux 人,所以我开始用 C 语言编写 LADSPA 插件。我认为最好从基本级别开始,真正了解正在发生的事情。如果我在 Windows 上,我会从 Steinberg 下载 VST SDK 并编写一个快速的概念证明插件,它只会增加噪音或其他任何东西。

选择像 VST 或 LADSPA 这样的框架的另一个好处是,您可以立即在您的普通音频套件中使用您的插件。将您的第一个自制插件应用于音轨的满足感是无与伦比的。此外,您还可以与其他音乐家分享您的插件。

在 C#/F# 中可能有一些方法可以做到这一点,但如果你打算编写 VST 插件,我会推荐 C++,以避免任何不必要的开销。这似乎是行业标准。

在缓冲方面,我一直在使用循环缓冲区(这里有一篇好文章:http://www.dspguide.com/ch28/2.htm)。一个很好的练习是实现一个有限响应滤波器(Steiglitz 将其称为前馈滤波器)——它们依赖于缓冲,玩起来很有趣。

我在 Github 上有一个包含一些非常基本的 LADSPA 插件的仓库。除了架构差异之外,它们也可能对编写 VST 插件的人有用。 https://github.com/andreasjansson/my_ladspa_plugins

另一个很好的示例代码来源是 CSound 项目。里面有大量的 DSP 代码,而且该软件主要针对音乐家。

【讨论】:

    【解决方案3】:

    我已经做了很多实时 DSP,虽然没有音频。虽然您的任何一个想法(不可变缓冲区)与(已修改的可变缓冲区)都可以工作,但我更喜欢为信号路径中的每个链接创建一个永久缓冲区。大多数效果不适合就地修改,因为每个输入样本都会影响多个输出样本。当您有重采样阶段时,每个链接的缓冲区技术效果特别好。

    这里,当样本到达时,第一个缓冲区被覆盖。然后第一个过滤器从其输入缓冲区(第一个缓冲区)读取新数据并写入其输出(第二个缓冲区)。然后它调用第二个阶段从第二个缓冲区读取并写入第三个。

    这种模式完全消除了动态分配,允许每个阶段保留可变数量的历史(因为效果需要一些内存),并且在重新排列路径中的过滤器方面非常灵活。

    【讨论】:

      【解决方案4】:

      我不知道这是否真的是您想要的,但这是我在大学期间的个人项目之一。在我自己实现之前,我并没有真正理解声音和 DSP 是如何工作的。我试图尽可能靠近扬声器,所以我只使用 libsndfile 来处理复杂的文件格式。

      基本上,我的第一个项目是创建一个大型双打数组,用正弦波填充它,然后使用 sf_writef_double() 将该数组写入文件以创建我可以播放的内容,并在波形编辑器。

      接下来,我在 sine 调用和 write 调用之间添加了另一个函数来添加效果。

      通过这种方式,您可以开始使用非常低电平的振荡器和效果器,并且可以立即看到结果。另外,只需很少的代码就可以让这样的东西工作。

      就个人而言,我会从最简单的解决方案开始,然后慢慢添加。尝试只写出一个文件并使用您的音频播放器播放它,这样您就不必处理音频 api。只需使用单个数组即可启动并就地修改。绝对从单线程开始。随着项目的发展,您可以开始转向其他解决方案,例如管道而不是数组、多线程处理或使用音频 API。

      如果您想创建一个可以发布的项目,具体取决于它的具体内容,您可能需要迁移到更复杂的库,例如一些实时音频处理。但是,当您达到这一点时,通过上述简单方法学习的基础知识肯定会有所帮助。

      祝你好运!

      【讨论】:

        【解决方案5】:

        F# 在这里可能是一个不错的选择,因为它非常适合操作函数。函数可能是信号创建和处理的良好构建块。

        由于 Array 模块中的高阶函数,F# 还擅长操作集合,尤其是数组。

        这些品质让 F# 在金融领域很受欢迎,而且我猜对信号处理也很有用。

        Visual F# 2010 for Technical Computing 有一个专门介绍傅立叶变换的部分,它可能与您想做的事情相关。不过,我想网上有很多关于转换的免费信息。

        最后,要播放样本,您可以使用XNA。我认为最新版本的 API (4.0) 也允许录制,但我从未使用过。有一个著名的 Xbox 音乐编辑应用程序 ezmuse+ Hamst3r Edition 使用 XNA,所以这绝对是可能的。

        【讨论】:

        • 是的,我开始倾向于 F# 而不是 C#,因为它适合这类问题。虽然微软最好的音频产品是在一个最终为游戏设计的系统中,但这似乎有点奇怪。
        • @Ciemnl:你为什么觉得这很奇怪?游戏配乐是一门大生意。
        【解决方案6】:

        关于缓冲和异步/线程/同步问题,我建议您查看新的 TPL 数据流库。凭借其块原语、并发数据结构、数据流网络、异步消息处理和 TPL 的基于任务的抽象(可与 async/await C# 5 功能一起使用),它非常适合此类应用程序。

        【讨论】:

        • 在阅读了关于 TPL 的概要之后,如果不使用 TPL 似乎我会疯了!!它看起来像是适合这项工作的完美系统......如果它可以在 F# 中使用那就更好了
        • 是的,TPL 特别适合这项任务,但也请注意在其上分层的新库,即 Async CTP (go.microsoft.com/fwlink/?LinkId=205053) 附带的 TDF(TPL 数据流)。
        【解决方案7】:

        你看过 VST.NET (http://vstnet.codeplex.com/) 吗?这是一个使用 C# 编写 VST 的库,它有一些示例。您还可以考虑编写 VST,以便您的代码可以在任何主机应用程序中使用(但即使您不想要,查看他们的代码也会很有用)。

        信号数据通常很大,需要大量处理。不要使用链表!我知道的大多数库都只是使用一个数组来放置所有音频数据(毕竟,这就是声卡所期望的)。

        来自 VST.NET 示例:

            public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels)
            {
                VstAudioBuffer audioChannel = outChannels[0];
        
                for (int n = 0; n < audioChannel.SampleCount; n++)
                {
                    audioChannel[n] = Delay.ProcessSample(inChannels[0][n]);
                }
            }
        

        audioChannel 是一个非托管 float* 缓冲区的包装器。

        您可能将样本存储在不可变数组中。然后,当您想播放它们时,您复制输出缓冲区中的数据(如果需要,更改频率)并在此缓冲区中执行效果。请注意,您可以使用多个输出缓冲区(或通道)并在最后对它们求和。

        编辑

        我知道播放阵列的两种低级方法:Windows API 中的 DirectSound 和 WaveOut。 C# Example using DirectSoundC# example with WaveOut。但是,您可能更喜欢使用外部更高级别的库,例如 NAudio。 NAudio 便于 .NET 音频操作 - 请参阅此 blog post 以向声卡发送正弦波。你可以看到他们也在使用一个浮点数组,这是我推荐的(如果你使用字节进行计算,你最终会在声音中产生很多混叠)。

        【讨论】:

        • 我现在受到了写 VST 的启发!让我措手不及的一件事是花车用于样品的事实。我原以为会使用 short 或 int。
        • 浮点数具有更好的精度。当您要对样本进行操作时,您将获得更少的工件。 (我在做图像处理时也注意到了)
        • 浮点数的精度并不比整数高,但它们的范围确实大得多。由于人耳对音频有对数响应,因此浮点编码的效果非常好。另请阅读 A-lawmu-law PCM 编码。
        • @Ben Voigt:确实。我的观点是:即使您需要在最后对值进行四舍五入(例如 16 位),在计算过程中也需要更高的精度。此外,浮点数可能很方便,因为经常使用诸如 sin 之类的函数。
        【解决方案8】:

        从阅读thisthis 开始。

        这会让你知道你必须做什么。

        然后,学习 DirectShow 架构 - 并学习如何不这样做,而是尝试创建它的简化版本。

        【讨论】:

        • 谢谢我以前没有遇到过Csound,我会调查一下。另外,我在哪里可以找到关于 directshow 架构的描述?我只是停留在一个特定的部分:我不确定如何进行缓冲,所以我真的在寻找如何做的描述
        猜你喜欢
        • 1970-01-01
        • 2011-04-08
        • 2020-04-21
        • 2021-04-12
        • 2021-11-18
        • 1970-01-01
        • 2011-05-03
        • 1970-01-01
        相关资源
        最近更新 更多