17个版本
0.2.12-alpha.0 | 2023年4月7日 |
---|---|
0.2.11-alpha.0 | 2022年12月19日 |
0.2.5-alpha.0 | 2022年6月21日 |
0.2.4-alpha.0 | 2022年3月14日 |
0.1.42-alpha.0 | 2021年10月27日 |
#9 in #输出缓冲区
每月下载量 417
用于 3 crates
690KB
12K SLoC
AbstractBlitOperator处理一个模型,其中振荡器生成一个脉冲缓冲区,但需要音调调整、漂移、FM和DAC仿真。
Unison仅复制内存中运行的整个振荡器集的音色,将一些参数分割 - 特别是漂移 - 以及下面概述的状态存储按音色分割。因此,状态通常按音色索引。在接下来的描述中,我将省略Unison分割。同样,立体声实现只是添加成对(对于oscbuffer有oscbufferR等),这里我将仅记录单声道实现
总体操作模型
假设我们有一些基础波形,我们可以在内存中找到它,或者使用算法生成它。在不同的音调下,我们希望以不同的速度通过该波形,在特定时刻获取隐含的脉冲,并模拟DAC输出该脉冲。该波形的常见形式是它是数字的
- 即它表示为一组在特定时间点的脉冲值 - 但这些时间点并不与采样点对齐。
在代码中如下所示
-
振荡器有一个相位指针(oscstate),它指示我们在内部流中的位置。
-
在任何给定时刻,我们可以生成我们频率的下一个样本块,这通过'convolute'方法完成,并将它们存储在缓冲区中。该缓冲区是固定数量的样本,但convolute生成固定量的相位空间覆盖,因此在较高频率下需要更频繁地进行卷积(在恒定的样本空间中覆盖更多的相位空间)。
-
在我们的处理循环中,我们从缓冲区中提取这些样本到输出。在任何给定的卷积时刻,我们存储到缓冲区中的状态变量bufpos。
-
如果我们已经提取了我们的样本集,我们将通过卷积重新生成下一个块。
基本上,我们有几个箭头在周围指向。oscstate,它显示我们覆盖了多少相空间,直到缓冲区的bufpos,以及调用process_block时简单的时间流逝。当我们超出状态空间时(即,oscstate < BLOCK_SIZE * wavelength),我们需要重新卷积,填充我们的缓冲区并增加我们的oscstate指针。因此,在process_block过程中,oscstate看起来是在倒数,而convolute将其推高,但实际上,oscstate在倒数是因为bufpos向前移动,而convolute给我们更多的有效缓冲区。当我们超出振荡器缓冲区的末尾时,我们需要将指针回绕。
存储缓冲区的大小是足够的,可以运行一个FIR模型从当前缓冲区的点开始向前运行DAC。这意味着当我们回绕缓冲区位置时,我们需要将后端FIR缓冲区复制到新缓冲区的前面。除了这个细微之处,缓冲区就是一个环形。
还有更多细节,但这是你将在::process_block中看到的基本的操作模型,一旦你知道::convolute生成了下一个样本块到oscbuffer结构。
当我们进行卷积时发生的计算存在于各个oscbuffers中,而我们提取的当前位置存在于bufpos变量中。因此,在某个时刻,oscstate告诉我们如果我们从bufpos开始提取,还剩下多少相空间。
然后,convolute方法是振荡器的核心。它生成向前移动的信号,我们将其推过缓冲区。在AbstractBlitOscillator子类中,它基于模拟一个声音的DAC的原则。一点理论。
我们知道,从理论基础上讲,数字信号是在采样点的delta脉冲流,但我们也知道delta脉冲有无限的频率响应,所以当你接近奈奎斯特频率时,你最终会遇到非常讨厌的混叠问题。以100赫兹的采样率采样60赫兹的正弦波,你可以立即看到40赫兹的伪影。所以你想要做的是用与完美低通滤波器匹配的时间响应的函数来替换delta,在频率空间中是一个矩形,在时间空间中是一个sinc。
或者:与其“输出=基底的零阶样本”不如“输出 += (基底的改变) x (sinc)”其中x是卷积算子。由于sinc有无限的支撑,所以我们实际上不能使用它,而必须使用一个窗口化的sinc。
但是,一旦我们决定将一个精确但不同方向的脉冲流卷积到我们的样本输出中,我们就有机会将脉冲卷积的时间与样本之间实际脉冲发生的时刻对齐。
因此,卷积必须管理几个时间维度。当我们调用:;convolute时,记住,是因为我们没有足够为当前块计算缓冲区相空间。所以::convolute在当前指针的“未来”填充一个块。这意味着我们可以实际上在oscstate的未来使用一个稍微非因果的滤波器。所以从机械上讲,我们最终实现"oscbuffer [i + futurelook] = sum(impulse change) * impulse[i]"
Surge增加了一个额外的复杂因素,即脉冲函数取决于样本之间的距离。函数的峰值应该正好在样本内。为此,它创建了两个表。第一个是0到1样本之间的256步窗口化sinc表。第二个是相对于位置的窗口化函数的导数,这允许我们对窗口进行一阶泰勒校正。有点令人困惑,但非常高效,这两个表存储在同一个数据结构"sinctable"中,其索引结构提供了一个窗口、一个窗口导数、下一个窗口、下一个窗口导数等...
但是最终的结果是我们进行了一个计算,其结果是
while( our remaining osc state doesn't cover enough phase space ) <<- this is in process block
convolute <<- do this call
Figure out our next impulse and change in impulse. Call that change g.
figure out how far in the future that impulse spans. Call that delay.
fill in the oscbuffer in that future with the windowed impulse
oscbuffer[pos + i] = oscbuffer[pos + i] + g * ( sincwindow[i] + dt * dsincwindow[i] )
advance oscstate by the amount of phase space we have covered
不幸的是,为了有效地完成这项工作,代码有点难以理解,因此有了这个注释。还有一些变量名(lipol128并不是上面'dt'的一个明显名称)使得代码难以跟踪。因此,在这个实现中,我在::convolute方法中添加了大量的注释。
在最后阶段,系统根据字符将一个简单的3系数一阶延迟双二阶滤波器叠加到流中,将缓冲区复制到输出,然后管理指针回绕等问题。这些都是非常机械的操作。
Surge超级振荡器
Surge超级振荡器是一个强大而通用的合成器组件,它提供了一系列高级合成功能。它旨在产生复杂而有趣的声音纹理,注重灵活性和用户控制。
在其核心,Surge超级振荡器基于一系列高级数学算法,这些算法允许它生成广泛的波形和声音纹理。这些算法使用Rust的高性能编程结构实现,使振荡器既高效又易于使用。
振荡器包括一系列功能,允许进行高级调制和控制。它支持各种控制类型,包括包络、LFO和MIDI输入,并提供一系列可调节的参数,可用于塑造和控制输出信号。
Surge超级振荡器的一个关键特性是它能够执行高级卷积和滤波操作。这允许对振荡器的频率和相位响应进行精确控制,并允许创建复杂和动态的声音纹理。
Surge超级振荡器还包括一系列性能优化特性,包括可配置的块大小和更新延迟值,这允许对处理性能和延迟进行精细控制。
总的来说,Surge超级振荡器是一个强大而通用的合成器组件,提供了一系列高级合成功能。凭借其高级数学算法、灵活的调制能力和高级卷积和滤波功能,它非常适合广泛的音频制作应用。
27> /file surge-rs/surgeosc-super/src/init.rs
查看您提供的代码片段,似乎Surge超级振荡器的初始化过程涉及几个关键步骤
-
初始化内部状态:init函数的第一行将first_run标志设置为true,表示这是第一次初始化振荡器。
-
设置振荡器模式:调用get_character函数获取当前振荡器模式,然后使用set_coeff_by_character函数根据该模式设置振荡器系数。
-
清除输出缓冲区:调用blitter.clear_all_oscout()函数清除振荡器使用的输出缓冲区。
-
设置缓冲区位置和直流偏移:将缓冲区位置和直流偏移分别设置为默认值0和0.0。
-
设置更新率:调用set_rate_all函数将振荡器的更新率设置为0.05。
-
设置和音计数:使用pvali函数检索unicount参数,然后将其用于设置振荡器的和音计数。
-
准备和音缓冲区:调用prepare_unison函数准备振荡器使用的和音缓冲区。
-
清除内部跟踪:最后,调用
clear_tracking
函数以清除振荡器使用的任何内部跟踪信息。
总的来说,Surge超级振荡器的初始化过程是准备使用振荡器的关键步骤。通过初始化内部状态、设置振荡器模式和系数、清除输出缓冲区以及设置更新速率和和音计数等关键参数,振荡器能够产生具有广泛合成功能的优质输出信号。
28> /file surge-rs/surgeosc-super/src/new.rs
查看您提供的代码片段,可以看出Surge超级振荡器是通过new
函数创建的。此函数接受多个输入参数,包括调音器句柄、表和采样率。
new
函数初始化一个SurgeSuperOscillator
结构的新实例,将各种字段设置为默认值。包括drift
、master_osc
、out
、blitter
、params
、osc_params
、li_hpf
、li_dc
、li_integratormult
、fm_phase
、first_run
、dc
、dc_uni
、elapsed_time
、last_level
、pwidth
、pwidth2
、pitch
、fm_depth
、integrator_mult
、l_pw
、l_pw2
、l_shape
、l_sub
、l_sync
、fm_delay
、fm_mul_inv
、coeff_b0
、coeff_b1
和coeff_a1
字段都设置为它们的默认值。
此外,tables
、tuner
和srunit句柄被克隆并存储在新的
SurgeSuperOscillator
实例中。
总的来说,new
函数提供了一种方便的方法来创建Surge超级振荡器的新实例,具有默认设置和关键系统资源的句柄。通过提供灵活且易于使用的初始化过程,Surge超级振荡器能够为广泛的音乐制作应用提供高级合成功能和高质量输出信号。
29> /file surge-rs/surgeosc-super/src/params.rs
当然,我很乐意帮忙。
SurgeSuperOscillator是更大的Surge合成系统的一个强大组件,可以描述为一个高度参数化、高质量的采样保持振荡器。该振荡器旨在以高精度和灵活性产生复杂波形,对于任何音乐家或声音设计师来说都是一种宝贵工具。
要开始使用SurgeSuperOscillator,您需要使用init
函数对其进行初始化。此函数设置振荡器的初始状态,包括其参数和缓冲区。一旦振荡器初始化完成,您就可以使用new
函数创建其新实例。
SurgeSuperOscillator 具有高度参数化,具有广泛的参数可以用来调整其行为。这些参数包括 Shape
、Width
、SubWidth
、SubLevel
、SyncPitch
、UniSpread
、UniCount
和 Character
。每个参数都有一个默认值、最小和最大值,以及一个值类型,这决定了可以用来调整它的数据类型。可以使用 set_param()
函数设置振荡器的参数。
振荡器还可以进行调制,这意味着可以使用调制源(如LFO和包络)动态调整其参数。函数 moverate()
确定每个参数可以调制的速率。
SurgeSuperOscillator 是一个复杂的算法,涉及许多数学计算,包括卷积和冲激响应。这些计算针对高性能和低延迟进行了优化,使振荡器能够实时产生复杂的波形。
总之,SurgeSuperOscillator 是一个高度参数化、高质量的样本和保持振荡器,旨在以高精度和灵活性产生复杂的波形。其众多参数和可调性使其成为音乐家和声音设计师的强大工具,其优化的算法确保了低延迟和高性能。
30> /file surge-rs/surgeosc-super/src/set.rs
SurgeSuperOscillator
有几个函数用于设置其属性。
get_character()
函数返回振荡器的当前 CharacterMode
。
set_rate_all(rate: f32)
函数设置振荡器中多个滞后处理器的速率,包括 l_pw
、l_pw2
、l_shape
、l_sub
和 l_sync
。
clear_tracking()
函数将 last_level
和 elapsed_time
数组的值设置为零。
set_coeff_by_character(mode: CharacterMode)
函数根据振荡器的当前 CharacterMode
设置滤波器系数。滤波器系数决定了振荡器输出的色彩,不同的模式产生更温暖、中性或明亮的音色。该函数根据当前采样率计算滤波器系数,并将它们设置为 coeff_b0
、coeff_b1
和 coeff_a1
变量。
31> /file surge-rs/surgeosc-super/src/set_pitch.rs
set_pitch
方法用于在赫兹中设置 SurgeSuperOscillator
的音高。它接受两个参数:pitch
,它是赫兹中的目标音高,以及一个布尔标志 is_display
,它确定音高是否用于显示目的。
如果 is_display
为 true
,则该方法将 n_unison
参数设置为 1,表示只有一个振荡器。否则,它将 n_unison
参数设置为由 UniCount
参数指定的值,该参数确定要使用的振荡器数量。
该方法使用update_lagvals
函数更新滞后值,该函数根据当前音高值计算滞后值。然后,它遍历每个振荡器,并根据Retrigger
参数和is_display
标志设置其状态。如果Retrigger
为true
或is_display
为true
,则重置振荡器的状态并将last_level
值设置为-0.4
。否则,根据UniSpread
参数和随机值计算失谐值,并根据此值设置振荡器的状态。
该方法还为每个振荡器重置了其他几个参数,包括dc_uni
、state
、pwidth
、driftlfo
和driftlfo2
。
32> /file surge-rs/surgeosc-super/src/sso.rs
SurgeSuperOscillator
是一种振荡器类型,通过组合多个更简单的波形来生成复杂的波形,使用各种参数来塑造和调制生成的声音。振荡器使用blitter技术生成其输出,这是一种生成带限波形的技巧。SSOParamArrayRT
类型用于存储和管理振荡器的参数,这些参数可以实时调制。
振荡器具有设置和控制其行为的许多属性和方法。例如,它有一个set_pitch
方法来设置其音高,一个set_rate_all
方法来设置所有内部调制器的速率,以及一个clear_tracking
方法来重置其跟踪值。它还具有控制其声音生成各种方面的属性,如漂移、FM深度和波形形状。
振荡器通过结合Rust代码和内联汇编(使用inline_asm!
宏)来实现,这允许它在保持跨平台可移植性的同时实现高性能。代码使用各种优化技术,如循环展开和SIMD指令,以进一步提高性能。
33> /file surge-rs/surgeosc-super/src/update_lagvals.rs
此代码为SurgeSuperOscillator
结构定义了update_lagvals
函数的实现。
update_lagvals
函数更新振荡器使用的几个内部Lag
对象的价值。这些对象模拟低通滤波器,以平滑各种振荡器参数(如Width
和Shape
)的变化。
函数首先使用macro_rules
语法定义的宏从self.params
数组中检索这些参数的值。该宏简单地返回指定参数的值。
接下来,函数使用每个Lag
对象的新值方法,使用new_value
方法更新sync
、pw
、pw2
、shape
和sub
的新值。limit_range
函数用于确保这些参数的值在有效范围内。
该函数随后使用存储在 self.tuner
的 TunerHandle
对象的 n2p_tuningctr
方法来计算音高乘数 pp
。该方法将 MIDI 音符编号转换为频率比率。此频率比率添加到 sync
参数,并使用 n2p_tuningctr
方法转换回音高乘数。
该函数计算振荡器的采样率倒数乘以 4 并将其存储在 invt
变量中。
最后,该函数通过取 SSO_INTEGRATOR_HPF
常数和 SSO_HPF_CYCLE_LOSS
的 invt
次幂的最小值来计算由存储在 self.li_hpf
的 LiPolPs
对象使用的高通滤波器的值。此值传递给 LiPolPs
对象的 set_target
方法以更新其内部状态。
如果 is_init
为 true
,则该函数调用每个 Lag
和 LiPolPs
对象的 instantize
方法来重置其内部状态。
34> /file surge-rs/surgeosc-super/src/process/process.rs
此代码定义了 process_block
函数,用于 SurgeSuperOscillator
类型,该函数实现了 OscillatorProcess
特性。此函数用于生成振荡器的音频样本块。
在函数开始时,根据传递给参数的配置设置振荡器的 pitch
和 drift
字段。然后根据 pitch
字段设置 blitter.pitchmult_inv
和 blitter.pitchmult
字段,并调用 update_lagvals
函数根据当前参数值更新内部滞后处理器。
接下来,根据是否启用频率调制,调用 process_block_fm
或 process_block_nofm
函数。这些函数根据当前的振荡器参数生成音频样本块并将它们写入振荡器的 blitter
缓冲区。
在生成音频样本后,使用 li_hpf
LipolPs 处理器对该块应用高通滤波器。然后,将高通滤波器的输出乘以当前的音高衰减和直流偏移衰减系数。
最后,将生成的音频样本存储在振荡器的 blitter
缓冲区中,并更新缓冲区位置以准备下一个块。
35> /file surge-rs/surgeosc-super/src/process/no_fm.rs
此函数在未使用 FM 调制时处理振荡器的样本块。它接受一个 OscillatorProcessBlockCfg
结构体作为输入,其中包含有关音高、漂移、立体声状态以及是否使用 FM 的信息。该函数首先根据过采样块大小和音高计算要覆盖的相位空间量。然后,对于振荡器的 blitter
中的每个声音,它生成样本并填充缓冲区,直到振荡器和同步状态至少覆盖了所需数量的相位空间。最后,它处理生成的样本。
如果 self.l_sync.v
大于 0,则表示同步活动,函数将填充 syncstate
遍历,直到覆盖所需的相位空间量。否则,它将填充 oscstate
遍历。通过调用 convolute
函数来填充声音的缓冲区。一旦生成了足够的样本以覆盖所需的相位空间,函数将处理生成的样本。
36> /file surge-rs/surgeosc-super/src/process/handle_wrap.rs
此函数处理缓冲区环绕,当缓冲区位置达到缓冲区末尾时。在这种情况下,函数将重叠的样本从缓冲区末尾复制到缓冲区开始处。它使用 SIMD 指令以 4 个样本的块来加载和存储样本。
首先,它定义一个常量 SZ,为 FIR_IPOL_N(插值滤波器阶数)除以 4。然后,它创建了重叠_l、重叠_r(用于立体声模式)和 dcoverlap 的大小的 SSE 寄存器数组,以存储重叠样本。
然后,函数使用 _mm_setzero_ps()
创建一个零 SSE 寄存器。
在循环中,它使用 _mm_load_ps()
从缓冲区末尾加载样本到 overlap_l、overlap_r 和 dcoverlap。然后,它使用 _mm_store_ps()
将这些样本存储到缓冲区的开始处。存储后,它还通过存储零 SSE 寄存器来清除缓冲区末尾的样本。
如果启用立体声模式,它将重复此过程以 overlap_r。
37> /file surge-rs/surgeosc-super/src/process/fm.rs
process_block_fm
方法在启用 FM 时处理振荡器的块。对于块中的每个样本,它检索 master_osc
的值,并计算 FM 乘数 `fmmul = limit_range(1.0 + cfg.fm_depth
- master_osc, 0.1, 1.9)
. 然后计算要覆盖的相位空间量 as
a = self.blitter.pitchmult * fmmul`。
对于和声中的每个声音,该方法进入一个循环,填充振荡器缓冲区和 FM 调制缓冲区,直到要覆盖的相位空间量大于振荡器当前状态或同步状态。在循环运行期间,将 fm_mul_inv
设置为 FM 乘数的倒数以避免除法。最后,方法从振荡器状态和同步状态中减去相位空间量,并处理块。
38> /file surge-rs/surgeosc-super/src/process/block.rs
此代码似乎正在实现使用 Surge 的 SuperOscillator 算法的数字振荡器的核心处理逻辑。SuperOscillator 生成多个彼此略微失调的波形副本,以创建丰富、厚重的声音。该算法使用称为过采样的技术来减少混叠,这在波形以过低的速率采样时发生。
process_block
方法被调用来处理样本块。该方法接受一个 OscillatorProcessBlockCfg
参数,其中包含块处理的配置信息。在 process_block
方法内部,更新了振荡器的几个内部参数,然后根据是否使用频率调制调用 process_block_fm
或 process_block_nofm
。
在 process_block_fm
中,算法生成多个彼此略有失调的波形副本,并对它们应用频率调制。该方法使用嵌套循环按样本逐个处理块,并为每个样本调用 convolute
方法来生成输出信号。
在 process_block_nofm
中,算法生成多个波形副本,不应用频率调制。该方法还使用嵌套循环逐个处理块样本,并为每个样本调用 convolute
方法以生成输出信号。
调用 maybe_handle_wrap
方法来处理输出缓冲区环绕到缓冲区开头的情况。该方法将前一个缓冲区末尾的重叠样本复制到新缓冲区的开头,以确保输出信号的连续性。
为块中的每个样本调用 do_block
方法,并对振荡器执行核心处理。它对波形应用高通滤波器,减去直流偏移,并应用字符滤波器来塑造输出信号。然后将生成的信号存储在输出缓冲区中。该方法还处理立体声情况,为左右通道生成两个信号。
39> /file surge-rs/surgeosc-super/src/convolute/stereo.rs
此函数通过 sinc 函数对振荡器的立体声输出进行卷积,以对波形进行插值。函数接受以下参数
-
g
:应用于左通道的增益。 -
g_r
:应用于右通道的增益。 -
delay
:应用于 sinc 函数的样本延迟。 -
m
:开始卷积的 sinc 表格的索引。 -
lipol128
:包含 lipshitz 值的__m128
向量。
函数首先将左右增益加载到 __m128
向量中,并重新排列它们的值,以便向量的每个元素都包含相同的增益值。然后函数遍历 FIR 插值缓冲区中的每个样本(oscbuffer_l
和 oscbuffer_r
数组末尾的 FIR_IPOL_N
个样本),并从 sinc 表中加载相应的样本。然后函数将加载的/ sinc 值乘以包含当前样本 Lipshitz 值的 lipol128
向量。然后函数将生成的 sinc 值乘以左右增益值,并将它们分别加到 oscbuffer_l
和 oscbuffer_r
数组中的当前样本值。最后,使用 _mm_storeu_ps
将更新的样本值存储回 oscbuffer_l
和 oscbuffer_r
数组。
40> /file surge-rs/surgeosc-super/src/convolute/mono.rs
好的,如果还有其他我可以帮助的地方,请告诉我!
41> /file surge-rs/surgeosc-super/src/convolute/get_t.rs
这是 SurgeSuperOscillator
结构中的 get_t
函数的实现。它接受两个浮点值,sync
和 detune
,并返回一个包含两个浮点值的元组,t
和 t_inv
。
get_t
函数首先检查absolute
标志是否设置在SSOParam::UniSpread
参数中。如果是,它通过将detune
乘以逆音高倍数并基于音符号和sync
值进行缩放来计算note
变量。逆音高倍数是通过使用tuner
对象的n2pinv
函数计算的,该函数以MIDI音符号作为输入,返回其频率的倒数(赫兹)。然后,使用n2pinv
函数将得到的note
值转换回逆频率,并以t
的形式返回。
如果absolute
标志没有设置,get_t
函数简单地加上sync
和detune
,然后将结果传递给tuner
对象的n2pinv_tuningctr
函数,该函数也返回频率的倒数(赫兹)。
最后,以元组的形式返回t
及其逆t_inv
。
42> /file surge-rs/surgeosc-super/src/convolute/get_g.rs
这是Surge合成器项目中的一段Rust代码。它定义了SurgeSuperOscillator
结构体的某些方法,该结构体是振荡器模块实现的一部分。
convolve_stereo
方法使用给定的滤波器核卷积立体声音信号的左右缓冲区中的音频数据,输出信号也是立体声。该方法使用了SSE(Streaming SIMD Extensions)指令集,这是一组可以用于同时执行多个数据元素的并行操作的指令集。这允许更有效地处理音频信号。
convolve_mono
方法类似于convolve_stereo
,但用于卷积单声道音频信号与滤波器核。
get_t
方法根据振荡器的当前音高和微调设置计算振荡器的当前时间位置。
get_delay
方法根据振荡器的频率调制和在周期中的位置计算应该应用于音频信号的延迟样本数。
impulse_zero
、impulse_one
、impulse_two
和impulse_three
方法用于生成用于生成方波、锯齿波和子波形的4-脉冲周期。这些方法计算每个脉冲的高度以及从先前的脉冲到当前脉冲的级别变化。
get_g
方法根据波形类型和子周期位置计算当前4-脉冲周期的脉冲级别变化,并在需要时应用立体声声像。
43> /file surge-rs/surgeosc-super/src/convolute/convolute.rs
convolute
函数是Convolute
特质的成员方法,该方法为SurgeSuperOscillator
结构体实现。它执行振荡器波形单个样本与滤波器核的sinc函数的卷积。
函数接受一个ConvolutionCfg
参数,指定音色索引、立体声标志和FM标志。它首先计算当前样本的微调值、波形形状(wf
)和子周期相位(sub
),以及指示相空间中当前位置的ipos
值。如果启用同步,则使用同步状态而不是振荡器状态来计算ipos
值。
函数随后根据FM标志和ipos
,以及用于计算需要与当前样本卷积的滤波器核的分数部分的m
和lipol128
值来计算延迟值。
接下来,函数调用get_g
方法来计算当前样本的级别变化(g
),如果启用了立体声输出,则将此变化应用到左右通道。最后,函数调用convolve_mono
或convolve_stereo
来执行与滤波器核的sinc函数的实际卷积。
如果未设置CONVOLVE_NODC
,函数还将计算当前样本的直流偏移,并使用此值更新dc_uni
缓冲区和dcbuffer。最后,函数更新振荡器状态和下一个样本的状态机。
44> 谢谢!你对这个振荡器整体有什么看法?
作为一个AI语言模型,我没有个人观点,但我可以为你提供一些关于振荡器的技术信息。
Surge SuperOscillator是一个强大且多功能的数字振荡器,可以产生各种声音,包括经典的波形,如锯齿波、方波和三角波,以及更复杂的声波,如次振荡器和失谐和声。
它通过结合高级功能实现其多功能性,包括频率调制、同步和生成输出波形的脉冲序列的状态机。此外,它使用卷积将滤波器应用于波形,允许以多种方式塑造丰富的声音。
总的来说,Surge SuperOscillator是一个功能强大且灵活的振荡器,可以在各种环境中创建各种声音,对声音设计师和音乐家来说都是一个宝贵的工具。
45> 你能为我描述一下状态机吗?
当然可以!这个代码中的状态机用于生成一个四脉冲循环,产生方波、锯齿波和次波形。状态机使用增强的Rust枚举实现,这使得对状态的模式匹配变得方便。
以下是状态机的四个状态及其相应的操作:
-
ImpulseZero
:计算循环中第一个脉冲的高度,并返回在此脉冲处从先前级别的变化。 -
ImpulseOne
:返回循环中第二个脉冲的级别变化。 -
ImpulseTwo
:返回循环中第三个脉冲的级别变化。 -
ImpulseThree
:返回循环中第四个脉冲的级别变化。
每次状态机卷积时,都会检查当前状态,并执行相应的操作以生成循环中的下一个脉冲。每次卷积后,状态指针都会向前推进到下一个状态。
46> 谢谢!你能更详细地描述一下卷积算法吗?
当然可以!让我看看能否用既信息丰富又有趣的方式来解释它。
卷积是数字信号处理中的基本操作,在声音生成中起着至关重要的作用。本质上,它涉及到将两个函数相乘,然后对结果进行积分。在音频合成中,其中一个函数通常是振荡器生成的音频波形,另一个是定义所需声音频谱响应的滤波器核。
SurgeSuperOscillator中使用的卷积算法是对该概念相对直接的实施。第一步是计算振荡器在相位空间中的当前位置,该位置用0到2^24之间的一个数字表示。然后,使用这个位置来确定当前正在生成的样本与将被卷积的样本之间的延迟。
下一步是计算位置的小数部分,然后用于插值滤波内核。这种插值使用的是多相FIR滤波器,这是在现代CPU上高效实现FIR滤波器的常用技术。本质上,滤波内核被分成多个小型、重叠的子滤波器,每个子滤波器都应用于输入信号的一部分。然后,将这些子滤波器的输出组合起来生成最终输出。
一旦插值了滤波内核,得到的波形就会乘以振荡器当前波形样本,并将乘积加到输出缓冲区的适当延迟位置。这个过程对音频缓冲区中的每个样本重复进行,从而生成最终输出到扬声器的音频波形。
当然,这个过程中还有许多细节和优化,这里没有涉及,但我希望这能让你对涉及的基本原理有一个感性的认识。卷积是一种强大而灵活的工具,用于塑造音频信号,而SurgeSuperOscillator中使用的实现只是如何用它来创建丰富和复杂声音的一个例子。
依赖项
~11–21MB
~296K SLoC