物理建模合成:几十行写一根弦
全课题骨架:采样不是"取点",是"用一串离散值 + 一个重建核,去猜那个连续的东西"。
前两课在采一个已经存在的东西:先看清"多采几个点为什么不是答案",再拆开"先滤再采"里那台滤波器。
这一课反过来 —— 不去采一根弦,而是用一条几十个采样长的环形延迟线,把一根弦"长"出来:
音高由"这一圈走几拍"决定,衰减由"环路里那个滤波器"决定。整件事在 1978 年被 Alex Strong 想出来,
1983 年由 Karplus 与 Strong 发表,当时的卖点是"只需要加与移位"—— 几十行、没有乘法。
四段路:环的长度定音高(18.1)→ 环路滤波器让高频先走(18.2)→ 滤波器的形状决定衰减时间(18.3)→
拨的那一下决定哪些谐波根本没被激发(18.4)。
★ 本页同样不发声(判据必须能在这里逐样本复算):画面画的是环形缓冲里那一圈采样(弦的形状)
与频谱 / 单圈增益 / 衰减包络。想听的话,把 18.1 件一里那六行按 8000 Hz 塞进 AudioBuffer 播一遍即可 ——
但本页不声称页面上的数与"你会听到的数"逐位相同(浏览器会按上下文采样率重采样,这条在第 1 课已写明)。
保真度:真机可证(可测) —— 整根弦逐样本算,四张表、十条判据都能离线复算。
一手源:Karplus & Strong 1983(算法本体与"周期 = p + ½")、Jaffe & Smith 1983(同一期的配套论文:单圈增益、环长、T60)、
J. O. Smith《PASP》(初值取白噪声 / KS 是波导特例 / 反馈环对增益失真的敏感)、拨弦点的梳状零点(Penn State 与 Am. J. Phys. 84)。
本页的原理与四张数值表照常可读(画面只是把同一份状态画出来)。
(脚本启动中…)
定点观察:(脚本启动中…)
(按「跑一遍读数」看当前这一档的四个谐波;按「扫一遍环路延迟」看六档 p 的实测音高)
本帧的读数
(脚本启动中…)
18.1 一根弦就是一个延迟线:环的长度定音高
本段只增加一样东西:环路延迟 p(旋钮 = p;其余控件与本课起点相同,滤波器固定在"两点平均"、g = 1)。
上一段的缺口:前两课的滤波器都是"给 f₀ 与 Q、反解出五个系数"来的解析设计。可"拨响一根弦"这件事 ——
一根金属弦被拨片推了一下,然后自己响了十几秒 —— 能不能不设计任何滤波器、几十行就得到?
Karplus-Strong 的做法只有两步:① 往一条环形缓冲里填一段随机数("拨"的那一下);② 之后每走一步,
把缓冲里相邻两点加起来除以二,写回最老那一格("弦"自己往下传)。原论文原话:这"只需要加与移位"。
★ 于是 p 是这一课最像"弦长"的那个旋钮:缓冲要装 M = p + 1 个槽 ——
可这一圈真正走几拍,不是 M。原论文自己写着:音高"对应一个 p + ½ 个采样的周期"(f_s/(p + ½))。
那半个拍是那个"相邻两点平均"贡献的相位延迟(18.2 会把它单独量出来)。
★ 能拿笔核:f₁ = f_s/(p + ½)。p = 40、f_s = 8000 ⇒ 197.5309 Hz;
若按"缓冲槽数"算会得到 8000/41 = 195.1220 Hz —— 差 0.4%,而两条独立路线(自相关 / 加窗 FFT)都站在 197.5 这一边(差 ≤0.02%)。
★ 副观察(它是这一课最干净的一条"精确"判据):把滤波器换成"不滤",一圈恰好走 M 拍,
波形会每 M 个采样逐位重复(实测 lag = 41,差 0);而一加上两点平均,环长成了 40.5 ——
这是一个非整数,波形从此再也不会逐位重复(实测在 2…4M 内找不到任何 lag)。
| p | 缓冲 M | 环长 p+½ | 闭式 f₁ (Hz) | 自相关 (Hz) | 偏差 | 加窗 FFT (Hz) | 偏差 |
|---|
| 目标音 | 想要 (Hz) | 需要 p+½ | 取整 p | 实际 (Hz) | 偏差 (cent) |
|---|
18.2 每一次绕圈,高频先走:环路滤波器的形状
本段只增加一样东西:环路滤波器档(旋钮 = 不滤 / 两点平均 / 四点平均;p 沿用上一段、g 仍为 1)。
上一段的缺口:18.1 量到"一圈走 p + ½ 拍"—— 那半个拍是从哪冒出来的?就是那个"相邻两点平均"贡献的相位延迟。
可它既然会改相位、那就一定还有另一半作用没看:它对不同频率的增益也不一样。
★ 一段噪声为什么会被听成"一根弦"?因为延迟线让每一圈都叠起来 —— 只有那些"一圈正好走整数倍波长"的频率
能站住(就是 f₁ 的整数倍),于是噪声成了一排谐波。而每一圈都要过一次那个滤波器,
于是每个谐波的幅度每圈就乘一次 |H_a(f)|:高频乘得少、低频乘得多,这就是"衰减"的全部来源。
★ 能拿笔核(Jaffe & Smith 1983):两点平均档 |H_a(f)| = |cos(π f T_s)|;
四点平均档(两级串联)cos²(π f T_s);不滤档恒为 1。
第 8 次谐波(1560.98 Hz)单圈 0.813552,绕 20 圈只剩 1.61e−2;
而同一档的第 1 次谐波 20 圈后还有 0.942 —— "高频先走"就是这一列数字。
★ 副观察(本段的彩蛋):不滤档 12 次谐波的"末/首"= 0.9775(一路平 —— 原论文原话说这样听起来像簧风琴),
两点平均 0.3228,四点平均 0.1483;而音高:不滤 195.1220 Hz、两点平均 197.5309 Hz、
四点平均又回到 195.1220 Hz —— 因为两个 ½ 拍的相位延迟相加正好是 1 拍,反把音高还回来了。
| 档 | 缓冲 M | 环长 | 闭式 f₁ (Hz) | 实测 自相关 / FFT (Hz) |
|---|
| 谐波 n | f (Hz) | A 实测 | A 闭式 | B 实测 | B 闭式 | C 实测 | C 闭式 | B 档 20 圈后 | C 档 20 圈后 |
|---|
18.3 滤波器的形状决定衰减时间
本段只增加一样东西:环路增益 g(旋钮 = g,0.9000 … 0.9999;滤波器固定在"两点平均"、p 沿用上两段)。
上一段的缺口:18.2 说了"高频衰减得快",可具体多快?把整个环的增益写成
G(f) = g · |H_a(f)|,每绕一圈乘一次 G,于是每个谐波都是一条几何衰减,
它的时间常数与 −60 dB 时间有闭式(Jaffe & Smith 1983 式 3–5、9)。
★ 能拿笔核:τ(f) = −(p + ½)·T_s / ln G(f)、t60(f) = ln(1000)·τ ≈ 6.91 τ。
p = 40、g = 1 时基频 τ = 1.681006 s、t60 = 11.61198 s;第 8 次谐波只有 0.16948 s —— 相差 68 倍,
这正是原论文那句"第 n 次谐波的衰减时间大致 ∝ p³/n²"(页面下方把这条指数律做成了实测的 log-log 斜率:−2.04 与 +2.98)。
★ 本段最该带走的一条:τ ∝ 1/(−ln G) —— 这个 1/ln 把增益的小变化放大成千百倍
(g = 1 时 1/(−ln G) ≈ 330):g 只从 1 降到 0.99,基频 t60 就从 11.61 s 掉到 2.68 s(4.3 倍)。
J. O. Smith 在讲延迟线插值时专门警告过这件事("feedback loops can be very sensitive to gain distortions")——
本页把那条警告量成了一个具体倍数。
| g | 谐波 n | f (Hz) | G 闭式 | G 实测 | τ (s) | t60 (s) |
|---|
| g | 闭式 t60 (s) | 端到端实测 t60 (s) | 相对差 | 1/(−ln G) 放大约数 |
|---|
18.4 拨的那一下:哪些谐波根本没被激发
本段只增加一样东西:初始形状(旋钮 = 初形档「噪声 / 三角」+ 拨弦点 x₁/L,0.05 … 0.5;前四段的旋钮全部沿用)。
上一段的缺口:前三段把"环"讲完了 —— 音高、衰减都算得出来。可"拨"的那一下呢?
KS 用一段随机数(那是"用噪声敲一下"),而真弦的初形是拨片位置决定的一条折线:
拨在离端点 x₁ 的地方,弦被拉成一个三角形。
★ 于是有一样东西被整片拿掉了:三角初形在拨弦点处是一个"波节",所有把节点落在拨弦点上的谐波
一次都没被激发。理想弦的模态幅度是 b_n ∝ sin(nπx₁/L)/n²
(Penn State 的推导原文:"the coefficients An will be zero when n is an integer multiple of L/d")。
★ 能拿笔核:拨在 1/5 处 ⇒ 第 5、10、15… 次谐波整个不存在;拨在 1/3 处 ⇒ 第 3、6、9、12 次;
拨在正中 ⇒ 所有偶次都不存在。表④ 的 12 次谐波逐项差 ≤ 1.9e−3(差的那一点点是"41 个采样去逼近一条折线"的离散化)。
★ 而音高与衰减一位都不变:初形只决定"激发了多少",不改环长、也不改单圈增益 ——
这一条把前三段与本段干脆地分开了:环管"能站住哪些频率",初形管"往这些频率里各放多少"。
| x₁/L | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|
这一课带走了什么
三行:① 一根弦可以就是一条环形延迟线 —— 音高由"一圈走几拍"(p + ½)决定,
而那个 ½ 是环路滤波器贡献的相位延迟(所以"缓冲槽数"不是"弦长");② 衰减不是另外设计的,
它是"每一圈乘一次的那个增益"(G(f) = g·|H_a(f)|)的必然结果,
衰减时间对 g 的敏感度被 1/(−ln G) 放大成千百倍;③ 音色由"拨的那一下"决定 ——
初形只往那些能站住的频率里分配份量,末了还会按拨弦点削掉一整族谐波。
★ 前两课的"滤波器"是给形状反解系数;这一课的滤波器只有两项(`[½,½]`),
却是从一个物理过程里长出来的:它既是弦的损耗,又顺带把音高挪了半拍。
下一课指针:到这一课为止,声音都是"在一条时间线上"算的(一个采样接一个采样)。
下一课换一个维度:把一个方位角变成左右两个增益 —— 同样是一行式子,同样能拿笔核,
同样能在这页之外的任何播放器里听出来。
本页用到的做法,出处在哪
- 算法本体与"周期 = p + ½" —— Kevin Karplus & Alex Strong, "Digital Synthesis of Plucked-String and Drum Timbles", Computer Music Journal 7(2):43–55, 1983(作者主页托管的 JSTOR 扫描全文): 递推式 "Y_t = ½( Y_{t-p} + Y_{t-p-1} )";"a period of p + ½ samples (frequency f_s/(p + ½))"; "Since no multiplication is required (only adding and shifting), the algorithm is fast and easy to implement"; 三个参数(pitch / amplitude / decay);衰减的指数律 "the decay time for the n-th harmonic is roughly proportional to p³/n²"; 不滤档 "a random wavetable has essentially equal harmonics up to the Nyquist frequency, sounding like a reed organ"; 初值 ±A 等概率、−p ≤ t ≤ 0。 users.soe.ucsc.edu/~karplus/papers/digitar.pdf ★ 如实标注:原论文只有递推式与框图,没有伪码;本页件一里那几行代码是本页自己的实现,递推式逐字照论文。
- 传递函数、单圈增益、环长、T60 —— David Jaffe & Julius O. Smith, "Extensions of the Karplus-Strong Plucked-String Algorithm", Computer Music Journal 7(2):56–69, 1983(同一期的配套论文): "H(z) = 1 / ( 1 − ((1+z⁻¹)/2) z^{−N} ) = 1 / ( 1 − H_a(z) H_b(z) ),其中 H_a(z) ≜ (1+z⁻¹)/2, H_b(z) ≜ z^{−N}"; "G_a(f) ≜ |H_a(e^{jωT_s})| = |cos(ωT_s/2)| = |cos(π f T_s)|, G_b(f) ≜ 1"; "loop gain = cos(π f T_s), and loop length = N + ½ (samples)"; "f_1 = f_s/(N+½)";"α_f(t) = [cos(π f T_s)]^{t f_s/(N+1/2)}"; "τ_f = − (N + ½) T_s / ln cos(π f T_s)";"t_60(f) = ln(1000) τ_f ≈ 6.91 τ_f"; 以及"整数延迟线导致音准被量化,补救是在环里加一个只贡献相位的一阶全通"。 Jaffe & Smith 1983 全文 PDF
- KS 是数字波导的特例 / 初值取白噪声 / 反馈环对增益失真极敏感 —— Julius O. Smith III, Physical Audio Signal Processing(CCRMA 在线专著): "the author recognized the filtered-delay-loop in the Karplus-Strong algorithm as having the same overall structure as the transfer function of an idealized vibrating string… deriving the Karplus-Strong algorithm as a special case of a digital waveguide string model"; "the delay-line initial conditions used to "pluck" the string consist of random numbers, or "white noise.""; "Feedback loops can be very sensitive to gain distortions."(本页 18.3 把这条警告量成了 330 倍)。 ccrma.stanford.edu/~jos/pasp/、 …/Delay_Line_Interpolation.html
- 拨弦点削掉一族谐波 —— Penn State(Daniel A. Russell)的拨弦 Fourier 演示页: "An = (2h/n²π²)(L²/(d(L−d))) sin(d nπ/L)"; "Because of the sin(dnπ/L) term, the coefficients An will be zero when n is an integer multiple of L/d. …plucked at a point 1/3 of its length… all of coefficients A3, A6, A9, A12, etc. will be zero"; 同行评审版:Perov, Johnson & Perova-Mello, "The physics of guitar string vibrations", Am. J. Phys. 84 (2016), "b_n = 2A sin(nπx₁/L) / [ (x₁/L)(1−x₁/L) π² n² ]"。 acs.psu.edu/drussell/Demos/Pluck-Fourier、 Am. J. Phys. 84 (2016)
- ★ 三处如实标注:①
|cos(ωT_s/2)|这条频响与"KS 是波导特例"都不在 Karplus & Strong 1983 里, 分别出自 Jaffe & Smith 1983 与 J. O. Smith 教材,本页分开标;② 原论文里的 "p³/n²" 原文是 "roughly", 本页把它做成实测的 log-log 斜率(−2.04 / +2.98),并写明 n 方向偏离 −2 的原因(n 大时小角近似失效); ③ 把 T60 与环路增益直接挂钩的"整圈"简化式(g = 10^{−3τ/T60})我们只在 UCSD 课程讲义(二手)里找到, 本页只用 Jaffe & Smith 的一手形式(带频率相关项),不引那个二手式。