#dsp #合成器 #合成 #实时 #即时编译器

nightly hexodsp

为在Rust中开发模块化合成器(如HexoSynth)而设计的全面DSP图形和合成库

3个版本

0.2.2 2024年1月4日
0.2.1 2022年11月6日
0.2.0 2022年8月28日

#599 in 音频

GPL-3.0-or-later

5MB
17K SLoC

hexodsp

HexoDSP - 用于在Rust中开发模块化合成器(如HexoSynth)的全面DSP图形和合成库。

该项目包含模块化合成器HexoSynth的完整DSP后端。

其目标是为想要在Rust中开发合成器的人提供工具集。您可以使用它快速定义一个可以在运行时更改的DSP图形。它附带了一个(不断增长的)已开发的DSP模块/节点的集合,如振荡器、滤波器、放大器、包络和序列发生器。

DSP图形API还提供了多种反馈方式,用于跟踪DSP线程中的信号。从监控单个节点的输入和输出以获取所有节点的当前输出值。

还有一个(可选的)即时编译器,用于定义在DSP图形模块/节点中以本地速度运行的定制DSP代码。

以下是功能列表的简要说明

  • 运行时可更改的DSP图形。
  • DSP图形和参数的序列化和加载。
  • 对运行中的DSP图形进行全面的监控和反馈内省。
  • 提供多种模块。
  • (可选)用于在运行时集成自定义DSP算法的即时编译(JIT)自定义DSP代码。一个可能的前端语言是HexoSynth中的视觉“BlockCode”编程语言。
  • 可扩展的框架,可快速将新节点添加到HexoDSP。
  • 涵盖HexoDSP中所有模块的全面自动化测试套件。

以下是DSP节点

有关完整列表,请参阅HexoDSP DSP节点参考

分类 名称 功能
IO Util 输出 音频输出(到DAW或Jack)
Osc Sampl 采样播放器
Osc Sin 正弦振荡器
Osc BOsc 基本带限波形振荡器(波形:Sin、Tri、Saw、Pulse/Square)
Osc VOsc 向量相位整形振荡器
Osc Noise 噪声振荡器
Osc FormFM 基于FM合成的共振峰振荡器
信号 Amp 放大器/衰减器
信号 SFilter 简单的滤波器集合,可用于合成
信号 FVaFilt 虚拟模拟滤波器集合(Moog、EDP Wasp、Korg MS20)
信号 Delay 单次信号延迟
信号 PVerb 混响节点,基于Dattorro板混响算法
信号 所有 基于内部延迟线反馈的全通滤波器
信号 梳状滤波器 梳状滤波器
信号 代码 JIT(即时)编译的自定义DSP代码片段。
N->M Mix3 3通道混音器
N->M Mux9 9通道到1个输出的多路复用器/开关
Ctrl SMap 简单的控制信号映射器
Ctrl 映射 控制信号映射器
Ctrl CQnt 控制信号音高量化器
Ctrl 量化 音高信号量化器
Mod TSeq 追踪器/图案序列器
Mod Ad 攻击-衰减(AD)包络
Mod Adsr 攻击-衰减-维持-释放(ADSR)包络
Mod TsLFO 三角/锯齿波形低频振荡器(LFO)
Mod RndWk 随机漫步者,一个采样与保持噪声发生器
IO Util FbWr / FbRd 用于补丁中反馈的实用模块
IO Util Scope 最多3通道的示波器
IO Util MidiP 从插件宿主、DAW或硬件输入的MIDI音高/音符
IO Util MidiCC 从插件宿主、DAW或硬件输入的MIDI CC
IO Util ExtA - ExtF 访问插件参数集A到F

API示例

文档

所有私有字段和函数的开发文档可单独托管:[HexoDSP API开发者文档](http://m8geil.de/hexodsp_doc/hexodsp/)

高级合成构造器API

HexoDSP提供了一套高级API,用于构建DSP图,被称为crate::SynthConstructor。结合crate::build模块,您可以使用Rust定义连接DSP节点的DSP图。

HexoDSP为您提供了一套现成的DSP节点。您可以在以下位置查看节点的参考文档:[HexoDSP DSP节点参考](http://m8geil.de/hexodsp_doc/hexodsp/build/index.html#hexodsp-dsp-node-reference)

此外,您还可以查看DynamicNode1x1 DSP节点,它允许您定义自己的DSP节点并将其插入到HexoDSP图中。

以下是一个简短的示例,展示了crate::SynthConstructor API的工作方式

use hexodsp::*;
use hexodsp::build::*;

let mut sc = SynthConstructor::new();

spawn_audio_thread(sc.executor().unwrap());

// Define a sine oscillator at 110Hz. The `0` is used to identify
// the oscillator instance. `sin(1)` would be a second and independent sine oscillator.
// `sin(2)` a third sine oscillator and so on... You may use up to 256 sine oscillators
// if your CPU is fast enough.
let sin = sin(0).set().freq(110.0);

// (Bandlimited) Sawtooth oscillator at 220Hz
let saw = bosc(0).set().wtype(2).set().freq(220.0);

// Setup a mixer node to sum the two oscillators:
let mix = mix3(0).input().ch1(&sin.output().sig());
let mix = mix3(0).input().ch2(&saw.output().sig());

// Turn down the initial output volume of the mixer a bit:
let mix = mix3(0).set().ovol(0.7);

// Plug the mixer into the audio output node:
let out = out(0).input().ch1(&mix.output().sig());

// Upload the DSP graph:
sc.upload(&out).unwrap();

// start some frontend loop here, or some GUI or whatever you like....

// Later at runtime you might want to change the oscillator
// frequency from the frontend:
sc.update_params(&bosc(0).set().freq(440.0));

fn spawn_audio_thread(exec: NodeExecutor) {
    // Some loop here that interfaces with [NodeExecutor::process] and regularily
    // calls [NodeExecutor::process_graph_updates].
    //
    // Please refer to the examples that come with HexoDSP!
}

六边形矩阵API

这是六边形矩阵API的简要概述,这是HexoSynth内部使用的主要API。

这仅展示了直接生成音频样本,而不需要任何音频设备播放。对于实时应用的库,请参阅库附带示例。

use hexodsp::*;

let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);

let sin = NodeId::Sin(0);
let amp = NodeId::Amp(0);
let out = NodeId::Out(0);
matrix.place(0, 0, Cell::empty(sin)
                   .out(None, None, sin.out("sig")));
matrix.place(0, 1, Cell::empty(amp)
                   .input(amp.inp("inp"), None, None)
                   .out(None, None, amp.out("sig")));
matrix.place(0, 2, Cell::empty(out)
                   .input(out.inp("inp"), None, None));
matrix.sync().unwrap();

let gain_p = amp.inp_param("gain").unwrap();
matrix.set_param(gain_p, SAtom::param(0.25));

let (out_l, out_r) = node_exec.test_run(0.11, true, &[]);
// out_l and out_r contain two channels of audio
// samples now.

简化六边形矩阵API

还有一个简化版本,便于使用crate::MatrixCellChain抽象设置DSP链。

use hexodsp::*;

let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B);

chain.node_out("sin", "sig")
    .node_io("amp", "inp", "sig")
    .set_atom("gain", SAtom::param(0.25))
    .node_inp("out", "ch1")
    .place(&mut matrix, 0, 0);
matrix.sync().unwrap();

let (out_l, out_r) = node_exec.test_run(0.11, true, &[]);
// out_l and out_r contain two channels of audio
// samples now.

开发状态

截至2022-07-30:架构及其功能现在基本上已经功能完整。唯一缺少的部分是模块/节点集合,这是当前开发领域。正在添加大量节点。

请确保关注Weird Constructors Mastodon账户或此项目的发布,以便了解更新。

运行Jack示例

要运行示例

    cargo run --release --example jack_demo_node_api

您可能需要以下依赖项(Ubuntu Linux)

    sudo apt install libjack0 libjack-jackd2-dev qjackctl

这些可能在Debian上也能工作

    sudo apt install libjack0 libjack-dev

运行自动化测试套件

存在用于DSP和后端代码的自动化测试套件

    cargo test

已知错误

  • 你在GitHub上遇到和创建的问题。

致谢

  • Dimas Leenman(又名Skythedragon)贡献了FormFM节点。
  • Frederik Halkjær(又名Fredemus,又名RocketPhysician)为FVaFilt虚拟模拟滤波器节点贡献了DSP算法。

贡献

我目前对想要实现的目标有相当清晰的认识,我的目标是最终用这个项目来制作音乐。

项目还处于初级阶段,我现在没有太多时间去负责项目协调。所以如果你的问题在GitHub问题跟踪器中腐烂,或者你的拉取请求长时间悬而未决,请不要感到生气。

如果你想为HexoDSP/HexoSynth贡献新的DSP节点/模块,请查看crate::dsp模块开头处的指南。

如果我有时间并且认为贡献与我的愿景相符,我可能会合并拉取请求。

请记住,我只能在项目的许可协议下接受贡献(GPLv3或更高版本)。

联系作者

您可以通过Discord或Mastodon联系我。我加入了大多数公共Rust Discord服务器,特别是“Rust Audio”Discord服务器。我有时也在freenode.net,例如在#lad频道(昵称weirdctr)。

许可协议

本项目采用GNU通用公共许可证版本3或更高版本。

为什么选择GPL?

显而易见的原因是,本项目从许多其他免费软件/开源合成项目中复制和翻译了代码。源代码将显示各个部分的来源和许可证。

我的原因

选择代码的许可证让我困扰了很长时间。我阅读了许多关于这个主题的讨论。阅读了许可证说明,并与其他开发者讨论了这个问题。

首先,关于我为什么要免费编写代码,原因是

  • 编写计算机程序是我的热情所在。在我的空闲时间,我可以按照自己的意愿编写我想要的代码。我可以自由地分配我的时间,自由地选择我想要工作的项目。
  • 帮助朋友或家人。
  • 解决我遇到的问题。
  • 学习新东西。

这些是我免费编写代码的原因。现在,关于为什么我要发布代码,尽管我可以选择将其保留给自己

  • 以便它可能为用户和自由软件社区带来价值。
  • 展示我的艺术作品。
  • 与其他开发者取得联系。
  • 交流知识和帮助其他开发者。
  • 而且,这也是在私人项目上多加润色的一种很好的方式。

大多数这些原因还不能证明GPL是合理的。GPL的主要观点,至少在我看来:GPL确保软件永远保持免费软件。软件的最终用户始终处于控制之中。用户有适应新平台或用例的软件的手段。即使原始作者不再维护软件。它最终防止了"供应商锁定"。我真的很讨厌供应商锁定,尤其是作为开发者。尤其是作为开发者,我想并且需要保持对我所使用的计算机和软件的控制。

另一个观点是,我的工作(以及任何其他开发者的工作)都有价值。如果我无偿地赠送我的工作,我实际上是在免费工作。这降低了我可以(以及可能的其他开发者)要求的技术、劳动力和时间的价格。

这使我选择了GPL的两个原因

  1. 我不想免费支持供应商锁定的情况。当我有选择时,当我在私人时间里投资于为最终用户提供价值时,我想防止这种情况发生。
  2. 我不想通过无偿奉献我在宝贵的私人时间里所付出的工作来降低自己(和其他开发者)的工资和价格。我不想让公司能够在封闭源代码项目中使用它,以驱动供应商锁定场景。

如果您有兴趣在封闭源代码项目中使用我的代码或项目,我们可以讨论代码或项目的再许可。请注意,我只能重新许可我编写的项目部分。如果项目包含来自其他项目和作者的GPL代码,我不能重新许可它。

许可证:GPL-3.0-or-later

依赖项

~11–22MB
~292K SLoC