65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 合成器工具包 - 第 I 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (33投票s)

2007年7月16日

MIT

13分钟阅读

viewsIcon

252760

downloadIcon

6461

使用 C# 和 Managed DirectX 创建软件合成器的工具包。

Lite Wave Demo

目录

引言

2006年秋,我用 C# 编程语言创建了一个简单的合成器。这是由弗吉尼亚军事学院的副教授 James Squire 委托的一个概念验证项目。我很高兴与 James 一起完成这个项目,并染上了“软合成器”的瘾。这促使我开始编写一个更复杂的“工具包”,用于使用 C# 创建基于软件的合成器。特别感谢 James 允许我在 Code Project 上重新使用我们项目中的代码。

这是计划三部分文章中的第一部分。在这里,我将概述该工具包。第二部分将深入探讨该工具包,向您展示如何创建简单的合成器。第三部分将演示如何创建更复杂的合成器。

如果您熟悉软合成器,您可能会问的第一个问题是该工具包是否兼容 VST。它不兼容。但是,我听说有人正在努力创建一个 .NET/VST 主机。如果这成为现实(或者已经成为现实),那么将该工具包的大部分内容改编成与它一起工作将不难。

这是该工具包的第二个主要版本。与第一个版本相比,主要的变化是移除了 MDX(Managed DirectX)用于波形输出。取而代之的是我编写的一个自定义库。延迟略长,但播放更稳定。此外,第二版支持创建自己的效果的功能。该工具包附带两种效果:合唱和回声;我希望将来能添加更多。另外,第二版支持将合成器的输出录制到波形文件中。

什么是合成器?

合成器是一种用于合成声音的软件或硬件(或两者的组合)设备。合成声音的方法有多种,但无论采用哪种方法,大多数合成器都使用相同的体系结构。通常,合成器有有限数量的“音色”来播放音符。一个音色负责合成分配给它的音符。当播放一个音符时,合成器会分配一个当前未使用的音色来播放该音符。如果所有音色都在播放,合成器会“窃取”一个音色,重新分配它来播放新音符。有很多音色窃取算法,但最常见的算法之一是窃取播放时间最长的音色。当音色播放其分配的音符时,合成器会将它们的输出混合并发送到其主输出。从那里,它会发送到您的扬声器或声卡。

一个音色由多个组件组成。有些组件会产生音色实际发出的声音,例如振荡器。振荡器合成一个在人耳可听范围内的频率的重复波形。有些组件不直接产生声音,而是用于调制其他组件。例如,一个常见的合成器组件是 LFO(低频振荡器)。它会产生一个低于人耳可听范围的重复波形。LFO 的一个典型用法是调制振荡器的频率以产生颤音效果。ADSR(攻击、衰减、延音和释放)包络是另一个常见的合成器组件。它用于调制声音的整体幅度等。例如,通过设置包络具有瞬时攻击、缓慢衰减和无延音,您可以模仿吉他或竖琴等拨弦乐器的幅度特性。这些组件共同合成了音色的输出。下面是一个四音色合成器的图示。

Typical Synthesizer Architecture

合成器的(非常)简史

早期的模拟合成器是用模块构建的,这是合成器组件的早期名称。每个模块都专注于做一件事。通过用跳线连接几个模块,您可以创建和配置自己的合成器架构。这产生了大量的声音可能性。

数字合成器在 80 年代初出现时,其中许多不如早期模拟合成器可配置。但是,找到模拟模块(如振荡器、包络、滤波器、LFO 等)的数字表示并不少见。

基于软件的合成器在 90 年代开始出现,并一直流行至今。它们是在个人计算机上运行的合成器。由于 PC 的多功能性,许多软件合成器已回归到早期模拟合成器的模块化方法。这使得音乐家能够在稳定的数字环境中利用模拟合成器的强大功能。

模拟合成器

合成器的输出是一个连续的波形。在软件中模拟此的一种方法是使用循环缓冲区。该缓冲区被划分为较小的缓冲区,这些缓冲区包含波形数据并按顺序播放。软件合成器首先合成两个波形数据缓冲区。然后将这些缓冲区写入循环缓冲区。然后开始播放循环。在播放过程中,它会通知合成器何时完成播放一个缓冲区。然后,合成器又会合成另一个波形数据缓冲区,并将其写入循环缓冲区。这样,合成器就比循环缓冲区的播放位置领先一个缓冲区。此过程一直持续到合成器停止。结果是合成器输出的无缝播放。

Synthesizer Buffer

由于合成器必须比当前播放位置领先一个缓冲区,因此存在等于一个缓冲区长度的延迟。缓冲区越大,延迟越长。从 MIDI 键盘播放软件合成器时,这可能会很明显。当您弹奏键盘时,您会注意到合成器响应之前有一个延迟。因此,使用尽可能小的缓冲区是可取的。然而,您可能会遇到的一个问题是,如果缓冲区太小,合成器将无法跟上;它会落后。结果是出现故障效果。关键是选择一个缓冲区大小,该大小可以最小化延迟,同时使合成器有时间合成声音,同时保持在播放位置的前面。

类概述

设计此工具包的关键挑战在于决定创建哪些类,以及以一种使它们能够协同工作来模拟典型合成器功能的方式来设计它们。此外,我希望通过简单地插入您自己创建的组件,让您轻松创建自己的合成器。这需要大量的思考和一些反复试验,但我认为我已经找到了一个符合我目标的方案。下面我将描述该工具包的一些类。该工具包中有很多类,但下面的类是最重要的。

组件类

Component 类是一个抽象类,代表所有效果和合成器组件的通用功能。SynthComponent 类和EffectComponent 类都派生自Component 类。

Component 类具有以下属性

  • 属性
    • 名称
    • SamplesPerSecond

Name 属性只是 Component 的名称。创建 Component 时可以为其命名。例如,您可能希望将其中一个振荡器命名为“振荡器 1”。SamplesPerSecond 是一个受保护的属性,为派生类提供采样率值。

SynthComponent 类

SynthComponent 类是一个抽象类,代表所有合成器组件的通用功能。工具包合成器组件非常类似于模拟合成器中使用的模块。振荡器、ADSR 包络、LFO、滤波器等都是合成器组件的示例。

SynthComponent 类具有以下方法和属性

  • 方法
    • Synthesize
    • 触发器
    • Release
  • 属性
    • Ordinal
    • SynthesizeReplaceEnabled

Synthesize 方法使合成器组件合成其输出。输出被放入缓冲区,稍后可以检索。Trigger 方法根据 MIDI 音符触发组件;它告诉组件哪个音符触发了它来合成其输出。Release 方法告知组件先前触发它的音符何时被释放。所有这些方法都是抽象的;您必须在派生类中重写它们。

Ordinal 属性代表组件的序数值。这并不能告诉你太多。稍后我将对 Ordinal 属性进行更多说明。SynthesizeReplaceEnabled 属性获取一个值,指示合成器组件在合成其输出时是否覆盖其缓冲区。在某些情况下,您会希望您的组件覆盖其缓冲区。但是,在其他情况下,当组件与其他组件共享其缓冲区时,让组件简单地将其输出添加到缓冲区而不是覆盖它可能会很有用。

该工具包附带了一系列 SynthComponent 派生类,足以创建一个基本的减法合成器。这些组件应被视为您编写的组件的起点。

MonoSynthComponent 和 StereoSynthComponent 类

有两个类派生自 SynthComponent 类:MonoSynthComponentStereoSynthComponent。这些是您将派生合成器组件的类。它们分别代表单声道输出和立体声输出的合成器组件。这两个类都有一个 GetBuffer 方法。对于 MonoSynthComponent 类,GetBuffer 方法返回一个 float 类型的单维数组。StereoSynthComponentGetBuffer 方法返回一个 float 类型的二维数组。在这两种情况下,该数组都是合成器组件写入其输出的底层缓冲区。

EffectComponent 类

EffectComponent 类是一个抽象类,代表所有效果组件的通用功能。效果位于全局级别;它们处理当前正在播放的所有 Voice 的输出。目前,该工具包附带两个 EffectComponent 派生类:ChorusEcho

EffectComponent 类具有以下方法和属性

  • 方法
    • 进程
    • Reset
  • 属性
    • 缓冲区

Process 方法使效果处理其输入。换句话说,Process 使其将效果算法应用于其输入。Reset 方法使效果重置自身。例如,Reset 会导致 Echo 效果清除其延迟线。Buffer 属性代表效果用于输入和输出的缓冲区。效果应从其缓冲区读取,应用其算法,并将结果写回缓冲区。

Voice 类

Voice 类是一个 抽象 类,代表合成器中的一个音色。您将从该类派生自己的音色类。Voice 类派生自 StereoSynthComponent 类,并重写和实现 SynthComponent抽象 成员。

Synthesizer 类

Synthesizer 类是该工具包的核心。它通过定期将来自其音色的输出写入主缓冲区来驱动合成器输出。它还提供合成器参数的文件管理。

SynthHostForm 类

SynthHostForm 类提供了一个运行合成器的环境。通常,您会创建一个基于 System.Windows.Forms 的应用程序。在添加必要的对所需程序集的引用后,您可以从 SynthHostForm 类派生您的主 Form。它具有您必须重写的以下成员

  • 方法
    • CreateSynthesizer
    • CreateEditor
  • 属性
    • HasEditor

CreateSynthesizer 方法返回一个使用委托初始化的合成器,该委托会创建您的自定义 Voice。这在第二部分会更清楚。CreateEditor 返回一个能够编辑合成器参数的 FormHasEditor 属性获取一个值,该值指示您是否可以提供编辑器 Form。提供编辑器 Form 是可选的。如果您不想创建编辑器,可以依赖 SynthHostForm 提供默认编辑器。如果没有可用的编辑器,CreateEditor 应该抛出 NotSupportedException

合成器图

Voice 类将合成器组件视为有向无环图中的节点。每个组件都有一个 Ordinal 属性(如上所述)。该属性的值等于连接到它的所有组件的 Ordinal 值之和加一。例如,没有输入的 LFO 组件的 Ordinal 值为 1。一个有两个频率调制输入的振荡器组件的 Ordinal 值将是 1 加上两个 FM 输入的 Ordinal 值之和。下面是一个典型合成器架构的图。每个组件都标有其 Ordinal 值。

A typical synthesizer

当您创建自己的音色类时,将其派生自抽象的 Voice 基类。您可以通过调用其 AddComponent 方法来向 Voice 添加组件。Voice 将组件添加到集合中,并按每个组件的 Ordinal 值对其进行排序。当 Synthesizer 运行时,它会定期调用其所有音色的 Synthesize。如果一个音色当前正在播放,它会遍历其组件集合,并对每个组件调用 Synthesize

由于组件按其 Ordinal 值排序,因此组件合成其输出的顺序与其他组件同步。在上面的示例中,振荡器 1 的频率受到 LFO 1 和包络 1 的调制。LFO 1 和包络 1 的输出都需要在振荡器 1 之前合成;振荡器 1 使用 LFO 1 和包络 1 的输出来调制其频率。按 Ordinal 值排序可确保组件以正确的顺序协同工作。

这种组织和实现合成器信号流的方法很大程度上受到了 J. Paul Morrison 的基于流程的编程网站的启发。其思想是,您拥有一系列以某种方式连接的组件,数据流经它们。可以轻松地更改和重新排列组件以创建新的配置。我坚信这种方法。

下载解决方案

本文第一部分的可下载 Visual Studio 解决方案与第二部分和第三部分相同。它包括该工具包以及两个演示项目。一个用于简单的合成器。另一个是用于“Lite Wave”合成器,它要复杂得多。

依赖项

该工具包依赖于我的MIDI 工具包来实现 MIDI 功能。MIDI 工具包又依赖于我的其他几个项目。我在下载中包含了正确的程序集并进行了链接,因此解决方案应该可以直接编译。

结论

这是对合成器工具包的简要概述。希望随着问题的出现,我能随着时间的推移改进这篇文章。写这篇文章很有挑战性,因为有很多信息需要涵盖。一方面,我希望提供一个有用的工具包概述。另一方面,我不想陷入细节。时间会证明我是否找到了正确的平衡。

如果您对软合成器感兴趣,我希望我的介绍足以让您继续阅读第二部分。它通过向您展示如何使用该工具包创建简单的合成器来提供更深入的了解。

历史

  • 2007 年 7 月 16 日 - 发布第一个版本
  • 2007 年 7 月 20 日 - 在下载部分添加了一个 MP3 文件,演示了 Lite Wave 合成器产生的声音之一
  • 2007 年 8 月 16 日 - 发布第二个版本。移除了 DirectX 并添加了波形录制功能
© . All rights reserved.