解读 118 · sound.rs(二):合成与混音
解读对象:
robotd/src/sound.rs(947 行,本篇读 106-669 的合成一半)
需要的前置:解读 117(播放主干与Ride);导览见解读 12
解读 117放的是"形状早已知道的 wav";这一半放活的声音:特雷门的音高随手距变,合唱的声部跟着乐谱走——没有文件可挑,只能现场合成:写线程从 sounds::Stream 拉块,读循环最后存下的参数(sound.rs:17-24)。
1. Live:参数是电平,不是队列
循环(50 Hz)与写线程(48 kHz 出样本)之间靠 Live(sound.rs:111-122):hz、level、open 三个 AtomicU64——Rust 存 f64 进原子的惯用招是 to_bits()/from_bits();外加 playing: AtomicBool。不用通道的理由:"参数是电平——错过更新的写者要的是最新值,不是睡着时错过的每一帧"(sound.rs:106-110)。一物二用:set_position 把拍数存进同一个 hz 字段(sound.rs:141-147),特雷门存的是赫兹——同一条管道两种货物,读的人知道自己要什么(position()/read(),sound.rs:149-168)。
2. 写线程的公共形状:块、步调、淡出
theremin_start(sound.rs:324-430)与 sing_start(sound.rs:437-545)的写线程同一个模子:每次渲染 10 ms 块(SYNTH_BLOCK,sound.rs:49),f32 乘 32767 截成 S16LE(sound.rs:402-405),然后压着写:领先量 = 已写字节折算的秒数 − 实际经过秒数,超过 SYNTH_LEAD_S = 0.03 就睡掉差额(sound.rs:410-413)。0.03 是乐器与骑乘的全部区别:骑乘领先 0.25 s 只是把松手推迟,乐器领先 0.25 s 就是手和音之间隔着四分之一秒(sound.rs:51-53)。aplay 侧配合短缓冲 --buffer-time=40000 --period-time=10000(封装在 open_synth_pcm,sound.rs:581-607)。放手是淡出不是拔插头:playing 清零后电平目标归 0,渲到 is_silent() 才退出(sound.rs:392-397);之后每拍由 theremin_settle(sound.rs:640-644)收尸,收尸前 PCM 仍被占——"放手后立刻的 quack 被丢弃,而不是盖住尾音"。
3. 合唱声部:一只鸭子唱一行谱
sing_start(score, part)(sound.rs:437-545)幂等:同曲同声部直接返回,换声部才重启(sound.rs:442-447)。开局:把这一声部的谱一次拍平成 Vec<Note>——"写线程不得在音频线程上走谱"(sound.rs:467-468);seat_for 查座位,得 detune = 2^(音分/1200)(sound.rs:470-471,几音分的失谐,合唱才不像机器齐唱)和几毫秒的 onset_offset_s 起音差(sound.rs:493);音量乘 CHORALE_LEVEL = 0.55。写线程每块:按 beats × beat_s 找当前音符(sound.rs:494-496);尾段 8% 提前松开(sustain × 0.92),换和弦重新咬字(sound.rs:499-502);音高 midi_hz × detune,口型取元音 open()(sound.rs:503-511)。两个实测量:扬声器 300 Hz 以下就交不了货(SPEAKER_ROLLOFF_HZ,sound.rs:65-70)——130 Hz 低音线在真机上不是小声,是没有,于是让低音走发得出的谐波;每声部 0.55 是因为"四只鸭在一个房间里是声学叠加",离线预览除得动人声部数,真机没人替你除(sound.rs:56-63)。
4. 嗓子从哪来
voice()(sound.rs:658-669)读银行目录的 .seed 标记文件,回退硬件 id,并缓存。用标记是刻意的:"它是这块盘上那些 wav 的生成种子——银行先于一次重播种的鸭子,特雷门仍与身旁的 quack 同嗓"(sound.rs:652-657)。closeness→频率的映射也住在嗓子那侧(theremin_hz_at,sound.rs:571-574):低音鸭唱低音域。循环侧入口极薄:theremin_set/sing_at 在没乐器时静默忽略(sound.rs:613-617)——循环可无条件调用,不必镜像本模块的状态。
你带走的收获
- 跨线程递参数:电平用原子位模式,事件才用通道;先想清楚漏一帧该要哪一帧。
- 音频线程三纪律:块小(10 ms)、领先按内容定(乐器 30 ms/点播 250 ms)、停止是淡出。
- 合唱的"像真人"是三个小数:每声部 0.55、座位失谐几音分、几毫秒起音差。
- 实测数字(300 Hz 下限)进常量。
- 身份(嗓音种子)跟随生成数据的标记,重播种后系统仍自洽。