简体   繁体   English

从头开始创建 DSP 系统

[英]Creating a DSP system from scratch

I love electronic music and I am interested in how it all ticks.我喜欢电子音乐,我对它的节奏很感兴趣。 I've found lots of helpful questions on Stack Overflow on libraries that can be used to play with audio, filters etc. But what I am really curious about is what is actually hapening: how is the data being passed between effects and oscillators?我在可用于播放音频、过滤器等的库上的 Stack Overflow 上发现了很多有用的问题。但我真正好奇的是实际情况如何:数据是如何在效果器和振荡器之间传递的? I have done research into the mathematical side of dsp and I've got that end of the problem sussed but I am unsure what buffering system to use etc. The final goal is to have a simple object heirarchy of effects and oscillators that pass the data between each other (maybe using multithreading if I don't end up pulling out all my hair trying to implement it).我已经对 dsp 的数学方面进行了研究,我已经解决了这个问题,但我不确定要使用什么缓冲系统等。最终目标是有一个简单的 object 效果和振荡器层次结构传递数据彼此之间(如果我最终没有拔出所有头发试图实现它,可能会使用多线程)。 It's not going to be the next Propellerhead Reason but I am interested in how it all works and this is more of an exercise than something that will yeild an end product.它不会成为下一个 Propellerhead 原因,但我对它的工作原理很感兴趣,这更像是一种练习,而不是最终产品。

At the moment I use .net and C# and I have recently learnt F# (which may or may not lead to some interesting ways of handling the data) but if these are not suitable for the job I can learn another system if necessary.目前我使用 .net 和 C# 并且我最近学习了 F# (这可能会或可能不会导致一些有趣的方法来处理数据)但如果有必要,我可以学习另一个不适合工作的系统。

The question is: what is the best way to get the large amounts of signal data through the program using buffers?问题是:使用缓冲区通过程序获取大量信号数据的最佳方法是什么? For instance would I be better off using a Queue, Array,Linked List etc?例如,使用队列、数组、链表等会更好吗? Should I make the samples immutable and create a new set of data each time I apply an effect to the system or just edit the values in the buffer?每次我对系统应用效果时,我应该使样本不可变并创建一组新数据,还是只编辑缓冲区中的值? Shoud I have a dispatcher/thread pool style object that organises passing data or should the effect functions pass data directly between each other?我应该有一个调度程序/线程池样式 object 来组织传递数据,还是效果函数应该直接在彼此之间传递数据?

Thanks.谢谢。

EDIT: another related question is how would I then use the windows API to play this array?编辑:另一个相关的问题是我将如何使用 windows API 来播放这个数组? I don't really want to use DirectShow because Microsoft has pretty much left it to die now我真的不想使用 DirectShow,因为微软现在几乎让它死了

EDIT2: thanks for all the answers. EDIT2:感谢所有答案。 After looking at all the technologies I will either use XNA 4(I spent a while trawling the internet and found this site which explains how to do it) or NAudio to output the music... not sure which one yet, depends on how advanced the system ends up being.在查看了所有技术之后,我将使用 XNA 4(我花了一段时间在互联网上搜索并找到了解释如何操作的网站)或 NAudio 到 output 音乐......不确定哪一个,取决于有多先进系统最终成为。 When C# 5.0 comes out I will use its async capabilities to create an effects architecture on top of that.当 C# 5.0 发布时,我将使用它的异步功能在此基础上创建效果架构。 I've pretty much used everybody's answer equally so now I have a conundrum of who to give the bounty to...我几乎平等地使用了每个人的答案,所以现在我有一个难题,谁应该给予赏金......

Have you looked at VST.NET (http://vstnet.codeplex.com/)?你看过 VST.NET (http://vstnet.codeplex.com/) 吗? It's a library to write VST using C# and it has some examples.这是一个使用 C# 编写 VST 的库,它有一些示例。 You can also consider writing a VST, so that your code can be used from any host application (but even if you don't want, looking at their code can be useful).您还可以考虑编写 VST,以便可以从任何主机应用程序使用您的代码(但即使您不想要,查看他们的代码也很有用)。

Signal data is usually big and requires a lot of processing.信号数据通常很大,需要大量处理。 Do not use a linked list, Most libraries I know simply use an array to put all the audio data (after all. that's what the sound card expect).不要使用链表,我知道的大多数库只是使用数组来放置所有音频数据(毕竟。这就是声卡所期望的)。

From a VST.NET sample:来自 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]);
        }
    }

The audioChannel is a wrapper around an unmanaged float* buffer. audioChannel 是一个非托管 float* 缓冲区的包装器。

You probably store your samples in an immutable array.您可能将样本存储在不可变数组中。 Then, when you want to play them, you copy the data in the output buffer (change the frequency if you want) and perform effects in this buffer.然后,当您想播放它们时,您复制 output 缓冲区中的数据(如果需要,更改频率)并在此缓冲区中执行效果。 Note you can use several output buffers (or channels) and sum them at the end.请注意,您可以使用多个 output 缓冲区(或通道)并在最后对它们求和。

Edit编辑

I know two low-level ways to play your array: DirectSound and WaveOut from Windows API.我知道播放阵列的两种低级方法:来自 Windows API 的 DirectSound 和 WaveOut。 C# Example using DirectSound . C# 使用 DirectSound 的示例 C# example with WaveOut . C# 示例与 WaveOut However, you might prefer use an external higher-level library, like NAudio .但是,您可能更喜欢使用外部更高级别的库,例如NAudio NAudio is convenient for .NET audio manipulation - see this blog post for sending a sine wave to the audio card. NAudio 便于 .NET 音频操作 - 请参阅此博客文章以向声卡发送正弦波。 You can see they are also using an array of float, which is what I recommend (if you do your computations using bytes, you'll end up with a lot of aliasing in the sound).你可以看到他们也在使用一个浮点数组,这是我推荐的(如果你使用字节进行计算,你最终会在声音中产生很多混叠)。

F# is probably a good choice here, as it's well fitted to manipulate functions. F# 在这里可能是一个不错的选择,因为它非常适合操作函数。 Functions are probably good building blocks for signal creation and processing.函数可能是信号创建和处理的良好构建块。

F# is also good at manipulating collections in general, and arrays in particular, thanks to the higher-order functions in the Array module. F# 通常也擅长操纵 collections,尤其是 arrays,这要归功于 Array 模块中的高阶函数。

These qualities make F# popular in the finance sector and are also useful for signal processing, I would guess.这些品质使 F# 在金融领域很受欢迎,我猜对信号处理也很有用。

Visual F# 2010 for Technical Computing has a section dedicated to Fourier Transform, which could be relevant to what you want to do. Visual F# 2010 for Technical Computing有一个专门介绍傅里叶变换的部分,这可能与您想做的事情相关。 I guess there is plenty of free information about the transform on the net, though.不过,我想网上有很多关于转换的免费信息。

Finally, to play samples, you can useXNA .最后,要播放样本,您可以使用XNA I think the latest version of the API (4.0) also allows recording, but I have never used that.我认为API(4.0)的最新版本也允许录制,但我从未使用过。 There is a famous music editing app for the Xbox called ezmuse+ Hamst3r Edition that uses XNA, so it's definitely possible. Xbox 上有一个著名的音乐编辑应用程序,名为ezmuse+ Hamst3r Edition ,它使用 XNA,所以这绝对是可能的。

With respect to buffering and asynchrony/threading/synchronization issues I suggest you to take a look at the new TPL Data Flow library.关于缓冲和异步/线程/同步问题,我建议您查看新的 TPL 数据流库。 With its block primitives, concurrent data structures, data flow networks, async message prcessing, and TPL's Task based abstraction (that can be used with the async/await C# 5 features), it's a very good fit for this type of applications.凭借其块原语、并发数据结构、数据流网络、异步消息处理和 TPL 基于任务的抽象(可与 async/await C# 5 功能一起使用),它非常适合此类应用程序。

I don't know if this is really what you're looking for, but this was one of my personal projects while in college.我不知道这是否真的是你想要的,但这是我在大学时的个人项目之一。 I didn't truly understand how sound and DSP worked until I implemented it myself.在我自己实现之前,我并没有真正理解声音和 DSP 是如何工作的。 I was trying to get as close to the speaker as possible, so I did it using only libsndfile, to handle the file format intricacies for me.我试图尽可能靠近扬声器,所以我只使用 libsndfile 来为我处理复杂的文件格式。

Basically, my first project was to create a large array of doubles, fill it with a sine wave, then use sf_writef_double() to write that array to a file to create something that I could play, and see the result in a waveform editor.基本上,我的第一个项目是创建一个大型双精度数组,用正弦波填充它,然后使用 sf_writef_double() 将该数组写入文件以创建可以播放的内容,并在波形编辑器中查看结果。

Next, I added another function in between the sine call, and the write call, to add an effect.接下来,我在 sine 调用和 write 调用之间添加了另一个 function 以添加效果。

This way you start playing with very low-level oscillators and effects, and you can see the results immediately.这样您就可以开始使用非常低电平的振荡器和效果器,并且可以立即看到结果。 Plus, it's very little code to get something like this working.另外,只需很少的代码就可以让这样的东西工作。

Personally, I would start with the simplest possible solution you can, then slowly add on.就个人而言,我会从最简单的解决方案开始,然后慢慢添加。 Try just writing out to a file and using your audio player to play it, so you don't have to deal with the audio apis.尝试只写出一个文件并使用您的音频播放器播放它,这样您就不必处理音频 api。 Just use a single array to start, and modify-in-place.只需使用单个数组即可启动并就地修改。 Definitely start off single-threaded.绝对从单线程开始。 As your project grows, you can start moving to other solutions, like pipes instead of the array, multi-threading it, or working with the audio API.随着项目的发展,您可以开始转向其他解决方案,例如管道而不是阵列、多线程处理或使用音频 API。

If you're wanting to create a project you can ship, depending on exactly what it is, you'll probably have to move to more complex libraries, like some real-time audio processing.如果您想创建一个可以发布的项目,具体取决于它的具体内容,您可能必须转向更复杂的库,例如一些实时音频处理。 But the basics you learn by doing the simple way above will definitely help when you get to this point.但是,当您达到这一点时,通过上述简单方法学习的基础知识肯定会有所帮助。

Good luck!祝你好运!

I've done quite a bit of real-time DSP, although not with audio.我已经做了很多实时 DSP,虽然没有音频。 While either of your ideas (immutable buffer) vs (mutable buffer modified in place) could work, what I prefer to do is create a single permanent buffer for each link in the signal path.虽然您的任何一个想法(不可变缓冲区)与(已修改的可变缓冲区)都可以工作,但我更喜欢为信号路径中的每个链接创建一个永久缓冲区。 Most effects don't lend themselves well to modification in place, since each input sample affects multiple output samples.大多数效果不适合就地修改,因为每个输入样本都会影响多个 output 样本。 The buffer-for-each-link technique works especially well when you have resampling stages.当您有重采样阶段时,每个链接的缓冲区技术特别有效。

Here, when samples arrive, the first buffer is overwritten.在这里,当样本到达时,第一个缓冲区被覆盖。 Then the first filter reads the new data from its input buffer (the first buffer) and writes to its output (the second buffer).然后第一个过滤器从它的输入缓冲区(第一个缓冲区)读取新数据并写入它的 output(第二个缓冲区)。 Then it invokes the second stage to read from the second buffer and write into the third.然后它调用第二个阶段从第二个缓冲区读取并写入第三个缓冲区。

This pattern completely eliminates dynamic allocation, allows each stage to keep a variable amount of history (since effects need some memory), and is very flexible as far as enabling rearranging the filters in the path.这种模式完全消除了动态分配,允许每个阶段保留可变数量的历史(因为效果需要一些内存),并且在重新排列路径中的过滤器方面非常灵活。

Alright, I'll have a stab at the bounty as well then:)好吧,那我也试试赏金吧:)

I'm actually in a very similar situation.我实际上处于非常相似的情况。 I've been making electronic music for ages, but only over the past couple of years I've started exploring actual audio processing.我制作电子音乐已经很久了,但只是在过去的几年里,我才开始探索实际的音频处理。

You mention that you have researched the maths.你提到你研究过数学。 I think that's crucial.我认为这很关键。 I'm currently fighting my way through Ken Steiglitz' A Digital Signal Processing Primer - With Applications to Digital Audio and Computer Music.我目前正在努力通过 Ken Steiglitz 的数字信号处理入门 - 应用到数字音频和计算机音乐。 If you don't know your complex numbers and phasors it's going to be very difficult.如果您不知道复数和相量,那将非常困难。

I'm a Linux guy so I've started writing LADSPA plugins in C.我是 Linux 人,所以我开始在 C 中编写 LADSPA 插件。 I think it's good to start at that basic level, to really understand what's going on.我认为从基本层面开始,真正了解正在发生的事情是很好的。 If I was on Windows I'd download the VST SDK from Steinberg and write a quick proof of concept plugin that just adds noise or whatever.如果我在 Windows 上,我会从 Steinberg 下载 VST SDK 并编写一个概念插件的快速证明,它只会增加噪音或其他任何东西。

Another benefit of choosing a framework like VST or LADSPA is that you can immediately use your plugins in your normal audio suite.选择 VST 或 LADSPA 等框架的另一个好处是,您可以立即在普通音频套件中使用插件。 The satisfaction of applying your first home-built plugin to an audio track is unbeatable.将您的第一个自制插件应用于音轨的满足感是无与伦比的。 Plus, you will be able to share your plugins with other musicians.另外,您将能够与其他音乐家分享您的插件。

There are probably ways to do this in C#/F#, but I would recommend C++ if you plan to write VST plugins, just to avoid any unnecessary overhead.在 C#/F# 中可能有一些方法可以做到这一点,但如果你打算编写 VST 插件,我会推荐 C++,以避免任何不必要的开销。 That seems to be the industry standard.这似乎是行业标准。

In terms of buffering, I've been using circular buffers (a good article here: http://www.dspguide.com/ch28/2.htm ).在缓冲方面,我一直在使用循环缓冲区(这里有一篇好文章: http://www.dspguide.com/ch28/2.htm )。 A good exercise is to implement a finite response filter (what Steiglitz refers to as a feedforward filter) - these rely on buffering and are quite fun to play around with.一个很好的练习是实现一个有限响应滤波器(Steiglitz 将其称为前馈滤波器)——它们依赖于缓冲并且玩起来很有趣。

I've got a repo on Github with a few very basic LADSPA plugins.我有一个关于 Github 的仓库,里面有一些非常基本的 LADSPA 插件。 The architectural difference aside, they could potentially be useful for someone writing VST plugins as well.除了架构差异之外,它们也可能对编写 VST 插件的人有用。 https://github.com/andreasjansson/my_ladspa_plugins https://github.com/andreasjansson/my_ladspa_plugins

Another good source of example code is the CSound project.另一个很好的示例代码来源是 CSound 项目。 There's tonnes of DSP code in there, and the software is aimed primarily at musicians.那里有大量的 DSP 代码,该软件主要针对音乐家。

You could have a look at BYOND .你可以看看BYOND It is an environment for programmatic audio / midi instrument and effect creation in C#.它是 C# 中用于程序化音频/midi 乐器和效果创建的环境。 It is available as standalone and as VST instru and effect.它可以独立使用,也可以作为 VST 乐器和效果器使用。

FULL DISCLOSURE I am the developer of BYOND.完全披露我是 BYOND 的开发者。

Start with reading this and this .从阅读thisthis开始。

This will give you idea on WHAT you have to do.这会让你知道你必须做什么。

Then, learn DirectShow architecture - and learn HOW not to do it, but try to create your simplified version of it.然后,学习 DirectShow 架构 - 并学习如何不这样做,而是尝试创建它的简化版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM