LapOrk (03: A Little More on Sound Waves)
About
之后的一些代码会放到 Github 上面 (li-yiyang/lapork-lecture), 方便复用了.
TLDR
- 声音是波, 一些特别的波型的产生和对他们进行一个调参数
- 采样的播放
- 声道, 输出的设备以及其他
Sound is Waves
咬文嚼字一下
其实严格来说, 声音是振动 \(f(t)\), 而波是声音的传播 (时空分布) \(f(x, t)\). 不知道这样会不会更加严格一些.
比如说打开示波器, 去检查声音信号随时间的变化情况:
(play (* 0.125 (+ (sin-osc.ar 350) (sin-osc.ar 440))))

波形与频谱的显示
lapork.plot
emmm, 但是不是所有时候都能手上拿着一台示波器,
那么是否有没有好的方法来解决这个问题呢?
虽然在 SuperCollider 中有 =plot= 这样的函数,
但是在 cl-collider 中并没有类似的功能.
参考 cl-collider/issue #81 中的方法, 可以用 Gnuplot 简单的实现一个绘图功能.
完整的代码见 lapork/plot.
具体思路的说明
plot: 相当于是在后台开了一个 Gnuplot 程序, 通过生成 inline data 和 plot 命令, 可以用来绘图scope: 例(scope (sin-osc.ar 440) :frames 200)
freqscope:
各种各样的波形的显示
在前面的几节里面已经接触过:
sin-osc: 正弦波blip: 脉冲white-noise: 白噪声类似的还有:
brown-noise: 棕噪声pink-noise: 粉噪声
下面是一些声音种类以及其波形的形状:
| 声音 | 波形 |
|---|---|
sin-osc 正弦波 | |
blip 脉冲波 | |
saw 锯齿波 | |
pulse 方波 | |
white-noise 白噪声 | |
brown-noise 粽噪声 | |
pink-noise 粉噪声 | |
gray-noise 灰噪声 | |
lf-tri 低频 (Low Frequency) 三角波 | |
lf-pulse 低频 (Low Frequency) 方波 | |
sin-osc 和 lf-noise1 (低频噪声) 的合成 |
部分解释:
lf-*开头的 (对应 SuperCollider 中LF) 开头的表示 Low Frequency, 用于去更好地生成一些低频的信号.例: 比较
pulse和lf-pulse的波形, 前者是通过多个正弦波 (傅立叶分解) 叠加得到的, 后者则是直接通过翻转值 (0或1) 来产生的. 在低频的情况下, (通过 CPU 控制的) 直接翻转显然更优, 而高频情况下, 由于会对 CPU 产生较大的负担, 还是不如用声卡的输出来的方便.- 不同的音色
滤波器
你说, 我在听 – 录音的导入和导出
导入声音
可以导入一段声音 (sampling, 采样) 进行播放:
(defparameter layer
(buffer-read "~/Code/lapork-lecture/samples/whispers/layer.aiff"))
(play (play-buf.ar 1 layer (buf-rate-scale.ir layer)))
解释:
buffer-read可以读入一段采样音频文件play-buf.ar给layer这个 buffer 准备了一些预先需要知道的信息:(play-buf.ar channels buffer-id rate)
channels有几个声道buffer-id也就是读到的layerrate播放速率, 默认的rate不知道为啥会让速度变得非常快, 所以这里需要加上
代码调用有些不太优雅了其实
这段采样的来历
在 Lain 每一集开头都会有一段很有意思的机械合成声音, 传说是用 Macintosh 发布会的那个问候同款的合成器制成的 (ref: r/Lain).
在今天的 macOS 里面, 其实还保留了这个神奇的声音生成工具:
say -v 'Whisper' 'Layer, zero two' # read
你可以用:
say -v 'Whisper' -o $OUTPUT 'Layer'
来生成采样.
拿小本本记下 – 导出声音
合成声音 – 一些应用?
Phone Tones
做这个的原因是之前听过一首歌, 开头是一段拨号, 然后是 “Hello, its me… ”, 挺好听的, 只是我忘了具体歌名叫啥了.
根据 Technical features of push-button telephone sets (ITU) 的说明, 可以得到一个拨号声音信号是如下定义的:
在按下按钮的时候, 根据按钮的编码发送一个混合频率的声音:
| Low freq \ High freq | 1209 | 1336 | 1477 | 1633 |
|---|---|---|---|---|
| 697 | 1 | 2 | 3 | A |
| 770 | 4 | 5 | 6 | B |
| 852 | 7 | 8 | 9 | C |
| 941 | * | 0 | # | D |
这, 简单啊:
(defsynth phone-tone ((gate 1) (amp 0.5) (out 0) (low 350) (high 440))
(out.ar out (* 0.5 amp
(+ (sin-osc.ar low)
(sin-osc.ar high))
(env-gen.kr (adsr 0.01 0.01 0.8 0.02) :gate gate))))
解释:
gate用来作为ADSR的开启和关闭的信号, 这个可以参考后文中和 TouchOSC 联动的部分low和high作为拨号信号编码的两个频率默认播放的是
350,440, 也就是常听到的拿起电话准备拨号的声音
那么做一个简单的 TouchOSC 界面:
(start-osc-server)
(defparameter phone (synth 'phone-tone :gate 0))
(let ((on/off 0))
(oscdef :tone-switch (gate)
(ctrl phone :gate (setf on/off gate)))
(oscdef :tone-silent (gate)
(if (zerop gate) ;; OFF
(ctrl phone :gate on/off)
(ctrl phone :gate 0))))
(oscdef :tone1 (on/off)
(if (zerop on/off) ;; OFF
(ctrl phone :low 350 :high 440)
(ctrl phone :low 697 :high 1209)))
;;; ...
解释:
- =start-osc-server= 打开一个 OSC server 来接受控制的 OSC 指令
synth定义了一个初始gate状态为关闭的phone-tone的合成器, 也就是我们的拨号器- 定义了三个 OSC 指令, 分别对应 TouchOSC 中的按钮名称:
toneSwitch: 是否使用拨号器 (开关合成器phone的gate信号, 同时修改局部变量on/off的值为开或关);toneSilent: 一时的静音, 当gate为非0时, 将phone的gate信号设为0, 其他的时候恢复正常状态;tone1: 使用按键1的声音, 若关闭, 则将声音重新置为等待拨号的呜呜声状态- 其他的
tone*同理
完整的 TouchOSC 和控制代码
因为手动一个个设置 tone* 太麻烦了, 所以这里我使用了 Common Lisp 的特性:
(macrolet ((tone* (high-freqs &rest tone-table)
`(progn
,@(loop :for (low . tones) :in tone-table
:collect
(loop :for tone :in tones
:for high :in high-freqs
:collect
`(oscdef ,(format nil "tone~A" tone) (gate)
(if (zerop gate)
(ctrl phone :low 350 :high 440)
(ctrl phone :low ,low :high ,high))))
:into defs
:finally (return (apply #'append defs))))))
(tone* ( 1209 1336 1477 1633)
(697 1 2 3 A)
(770 4 5 6 B)
(852 7 8 9 C)
(941 * 0 |#| D)))
解释:
macrolet定义了一个局部的宏展开tone*, 将输入的参数看作是一个表格, 第一行是高频信息, 剩下的行 (&rest) 为(低频信息 . 按键名称)
完整的代码见: phone.lisp 以及 phone-calling.tosc.
效果如下:
一些鬼点子: 如果用这个生成一段随机拨号音, 然后配上 Lain 的 Whisper 采样 greeting, 估计会有种赛博朋克的感觉.