如果你喜欢音乐,为什么不自己写一首歌呢?听过一万首歌,不如自己写一首。本文介绍一个简单的用代码写音乐片段的玩具 sonic-pi.
简介
Sonic-pi的主要作者是Sam Aaron,他在他的TED演讲(youtube)上分享了他的理念和对sonic-pi的一个简单介绍。总的来说,我非常赞同作者的价值观:技术应该给人更多的创作与表达的自由。
如果用一句最简单的话描概括它:这是一个能用ruby脚本生成声音的软件。作者本人并没有假设使用人会写任何代码,他的说法是尽量让10岁的小孩也能用代码来写出音乐:
I’d need to be confident the code was useful and understandable by 10 year olds.
在我实际体验这个东西以后,我的感觉……一言难尽。事实上,我觉得它有点简单过头了,以致与难以完成稍微复杂的东西。但依然,这是个好东西,在某些设计上,我十分钦佩它的创造力。
我自己这个玩具上鼓捣过一阵子,大部分情况下,它写出的东西都只能被称为是声音,而不是音乐——至少按照我自己标准是这样。
这是用sonic-pi写出的一个小片段:
周某的片段
感觉还行。
本文假定读者有简单的乐理知识。当然,没有也没关系。
安装
在sonic-pi官网 上有对不同操作系统的安装方式。
注意事项:
- Windows版本无法选择、更换安装语言,只会安装系统默认语言。中文的支持并不完整。
- 部分情况下sonic-pi可能会独占声卡,打开软件的时候不能听到系统任何其他的声音。
- Linux只有apt的软件包,并且不是最新版本。
- Linux的树霉派分发版上已经预装此软件。
界面
sonic-pi的界面相当简单。
Screenshot_20190828_224638.png- 左上的3个按钮分别是播放、停止和录音,右上的一排按钮不重要。
- 左的一大块是代码区域。下面的Buffer按钮是标签,可以在多个文件编辑窗口之间切换。
- 代码区域右边的部分是运行日志。在播放时会不断刷出来,显示播放状态。
- 下面的部分是帮助区域,左边显示目录,右边显示对应的具体内容。帮助区的标签从左到右分别是:
- 文档,跟我们看到在线文档区别不大。
- 示例,一些代码的例子,很多都是作者本人写的,大部分很难听。
- 合成器。就是各种可配置的电子音色。
- 效果器。给声音加上各种效果(失真、均衡、混响等)的东西。
- 采样器。提前录制好的声音片段。
- 接口列表,sonic-pi的所有支持函数都在这里能找到。
声音播放
官方有一个非常简单易读的文档。他把用sonic-pi写音乐称作live code,在文档开头的有这样的一小段话:
在我们讨论这个话题的时候,让我给你一个我在多年的live code音乐编程方面所学到的建议 - 没有错误,只有机会。 这是我经常听到的一个关于爵士乐的观点,但对编程来说,同样有效。 无论你有多少经验 - 完全的小白,还是到经验丰富的coder,你都会运行一些具有完全意想不到的结果的代码。在这种情况下运行它,可能听起来非常酷。 然而,它也可能听起来完全不和谐而且不合适。 发生了什么事情并不重要 - 重要的是你接下来要做什么。 聆听声音,操纵它并将其变形为令人敬畏的东西。 人群将会为你疯狂。
简单的声音播放
在代码区写
play 70
点击左上的Run按钮,或者快捷键(alt+R),你就可以听到一声'bee~'。
然后再来:
play 75
你可以听到一声音调更高的'bee~'。play
后面得数字指的是它在钢琴键盘上从左边数的位置。60是中央C的位置,位于第4个八度的第一个音。play
后面也可以直接用它的音名+第几八度来表示。
play :A4
表示第4八度的A音,与play 70
一致。注意音名前面要加:
(备注:在ruby中,这种前面带冒号的东西被称为symbol,一个名字的symbol在ruby进程中是唯一的,所以一般用作表示不会变的东西。在sonic-pi编辑器中symbol会被高亮成红色)。
play 72
play 75
play 79
你可以听到3个音同时发出。这是一个小三和弦。
如果想听到3个音依次播放,得这么写:
play 72
sleep 1
play 75
sleep 1
play 79
正如代码的字面意义:播放完一个声音后,暂停1,再去播放下一个音。我们可以sleep
调整后面的数字让它速度更快一点。
play :C3
sleep 0.5
play :D3
sleep 0.5
play :E4
在数字前加上s
或b
可以表示升半音与降半音,如play :Fs3
或play :Eb3
。
速度、音量、声像、音长
在上面提到的sleep
后面的数字的单位是“拍”。而sonic-pi默认的播放速度是60bpm(拍/每分钟),相当于1秒1拍。我们可以在文件开头用use_bpm
指定bpm。
use_bpm 75
事实上use_bpm
可以放在代码的任何地方,播放速度是可以动态改变的。
在函数play
后面还可以继续接一些参数,参数之间都是用逗号隔开的。这里介绍俩个:amp和pan,分别表示音量和声像(音源相对位置)。amp的默认值是1。下面的句子:
play :F4, amp: 0.5
表示播放音量是默认音量的一半。
pan的默认值是0,表示正中间,-1表示左边,1表示右边。
play :F4, pan: 1
如果你带上耳机,会听到声音只会从右边传过来。
还有一个参数是释放时间release
。它是指声音从播放到完全消逝的时间,默认也是1拍。
play :F4, release: 2
这样声音就拖得长了一点。其实除release
外,还有好几个参数控制声音播放周期内音量变化。因为有点复杂且不够合理,这里略过。
合成器与采样器
合成
合成器,就是把简单的波形通过各种方式组合起来的音色。sonic-pi提供了一些内置的声音合成器,在代码中用use_synth
函数来指定音色。
use_synth :saw
play :C4
sleep 0.25
play :D4
sleep 0.25
play :E4
这种声音被称为 "saw",是因为它在频域上的波形是锯齿状的。同样use_synth
也可以用在代码的任何地方,它会改变后面的声音的音色。sonic-pi默认的音色是"beep",这是一个单纯的正弦波。
除了少数几个音色比较接近自然音外,大部分都是明显的电音。
采样
所谓采样,就是提前录制好的声音,可以直接在音频中使用。使用采样的函数是sample
。
sample :drum_snare_soft
这是一个鼓点的声音。
和play
函数一样,sample
同样也支持amp
,pan
参数。不过要注意的是,release
参数并不会使声音播放的速度更长,采样的音量控制机制与play
不一致。这里略过。
sample
有一个很特殊的参数rate
。正如它的字面意思,它可以控制播放速度。不过它居然会直接拉伸或压缩时域波形,而且没有任何其他操作,所以这样会引起音高的变化。如果设置rate: 2
,那么速度快一倍,声音的频率也会高一倍,相当于提高了一个八度。所以我并不建议使用这个参数。有趣的地方在于它可以被设置为负数,是的,你把它设置为-1,它就可以把声音倒着放。
sonic-pi自带的采样大部分都是打击乐的各种鼓点。它的文档中还提到了可以导入外部采样,但我并没有导入成功过。
控制流
sonic-pi的最大特色,就是引用程序语言中的常见的流程控制概念来控制音乐“流”。
运行调试
在运行sonic-pi时,可以在右边窗口实时地看到运行日志输出,类似这样:
{run: 40, time: 1.25}
└─ synth :beep, {note: 67.0, release: 0.625}
{run: 40, time: 1.5625}
└─ synth :beep, {note: 64.0, release: 0.625}
{run: 40, time: 1.875}
└─ synth :beep, {note: 62.0, release: 0.625}
在大部分情况下,这些日志都是可以忽略的,只需要看着它随着音乐刷下来。但有时候我们需要确定播放状态的或者其他的信息的时候,就需要用print
或puts
函数手动把一些内容输出到日志窗口。
print [1,2,3], "abc", note(:C5)
print sample_duration :drum_snare_soft
点击播放,就能在日志窗口看到:
{run: 51, time: 0.0}
├─ [1, 2, 3] abc 72
└─ 0.3147845804988662
循环
sonic-pi最擅长的,是写各种loop。比如下面的代码
- loop,循环播放
use_bpm 75
bass = :drum_heavy_kick
snare = :drum_snare_soft
tom = :drum_tom_hi_soft
hat = :drum_cymbal_closed
loop do
sample bass
sleep 1
sample snare
sleep 0.5
sample bass
sleep 0.5
sleep 0.25
sample snare
sleep 0.25
sample bass
sleep 0.5
sample snare
sleep 1
end
就是一段简单的鼓点loop,点此试听。其中,在do
和end
之间的部分,被称为代码块(block),在一个block的前面加上loop
,表示这段代码会反复执行。我们还可以通过n.times
来让代码执行n次。
4.times do
sample :drum_heavy_kick
2.times do
sample :elec_blip2, rate: 2
sleep 0.25
end
sample :elec_snare
4.times do
sample :drum_tom_mid_soft
sleep 0.125
end
end
除了这两种外,还有其他的处理循环的手段,这里略过。
条件
假设我们需要这样的一个效果:我们希望有一半的几率播放一种声音,另一半的几率出现另一种声音,这时候就要用到条件句if ... else ...
,如下所示:
loop do
if one_in(2)
sample :drum_heavy_kick
sleep 0.5
else
sample :drum_cymbal_closed
sleep 0.25
end
end
在代码中出现的one_in(2)
是指[1,2] 中随机地选出的数字等于1的真值,这是sonic-pi自带的一个函数,所以上面的代码有1/2的几率会播放第一个sample。if
也可以写在语句后面:
loop do
play 50, amp: 0.3, release: 2
play 53, amp: 0.3, release: 2 if one_in(2)
play 57, amp: 0.3, release: 2 if one_in(3)
play 60, amp: 0.3, release: 2 if one_in(4)
sleep 1.5
end
线程
线程thread这是sonic-pi的控制流中最重要的概念。我们来看下面这个代码
loop do
sample :drum_heavy_kick
sleep 1
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
第一个loop
会无限循环,这样永远都不会播放到下一个loop。如果想让两个loop同时运行,需要把这两个loop放在不同的thread内:
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
我认为这里使用音轨(audio track)这个概念更合适一点。
略复杂的声音播放
仅依靠play
和sleep
组合,理论上就可以写出任何旋律了。但事实上不会有人这样做的,因为它写起来太长了。并且这也不符合人对于乐句的思维方式:一个乐句应该是一个连贯的序列,而不是被sleep
作为中断的一系列独立事件。这里介绍几个新的方法,结合上面提到的控制语句,可以更简单地写出flow。
- 使用和弦chord
下面的三个音组成了根音为C4的大三和弦
play :C4
play :E4
play :G4
它与下面的写法等价:
play [:C4, :E4, :G4]
这种用中括号包装的东西被称为数组。把多个音放在一个数组里表示同时播放。
我们可以用chord
函数来表示特定和弦,比如下面这样又是一种等价写法:
play chord(:C4, :M)
chord
的两个参数分别表示根音以及和弦类型。和弦类型可以在接口列表查到。比如常用的:M
:大三和弦; :m
:小三和弦; :7
:属7和弦等等。chord
还可以有第三个参数,表示和弦转位。
play chord(:C4, :M, invert: 1)
效果与下面相同:
play [:E4, :G4, :C5]
如果你了解过一点和声学,就可以看出来使用这种记法的好处是我们不再直接处理没有意义的数组,而是处理有意义的和弦。
- play_pattern_timed,依次播放一个数组里的声音。
play_pattern_timed chord(:E3, :m7), 0.25
表示以0.25拍为间隔播放:E3
上的小七和弦。
play_pattern_timed chord(:E3, :m13), [0.25, 0.5]
效果就等同于:
play 52
sleep 0.25
play 55
sleep 0.5
play 59
sleep 0.25
play 62
sleep 0.5
play 66
sleep 0.25
play 69
sleep 0.5
play 73
play_pattern_timed会依次播放第一个数组里的声音,同时迭代第二个数组,用后者的值作为时间间隔。这样可以极大地简化旋律的写法。
函数
play_pattern_timed
是一个非常好用的函数。不过遗憾的是对于采样sample
没有类似的函数。sonic-pi提供了方法让我们可以自己定义函数。所谓函数,就是给一段代码取个名字,在代码执行时可以调用。定义函数用define
,后面接函数符号。
define :foo do
sample :drum_heavy_kick
sleep 0.5
sample :drum_snare_soft
sleep 0.5
end
foo
在上面的函数中,我们先把一段代码块的命名为foo
,然后调用这个函数foo
,能听到代码块中底鼓+军鼓的声音。如果想在函数中带参数,需要把参数写在||
之间(备注:这是ruby的一个诡异的语法规则)。
define :sample_pattern_timed do |sample_array, time_array|
sample_array.zip(time_array) do |items|
sample items[0]
sleep items[1]
end
end
bass = :drum_heavy_kick
snare = :drum_snare_soft
hat = :drum_cymbal_closed
loop do
sample_pattern_timed [bass, hat, snare, hat], [0.5, 0.5, 0.5, 0.5]
end
在上面的代码中,我们参考play_pattern_timed
函数定义了一个类似的sample_pattern_timed
。上面的效果是一个无限循环的“动次打次”。
(备注:ruby中的def
在sonic-pi中不建议使用。由于sonic-pi中的代码的封装方式,使用def
会导致作用域错乱。)
效果器
效果器之于声音,正如滤镜之于照片一样。如果你在网上搜效果器,会看到各种花花绿绿带几个按钮的小方盒。这些东西可以把音色改造出令人惊叹的效果,尤其是电吉他。
sonic-pi是用block的方式提供效果器功能的。简单来说,如果想使用xxx
效果器,就得把需要这个效果的代码块写在with_fx: xxx do ... end
里面。这种封装的方式更接近上下文。比如这样:
with_fx :reverb do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
reverb
就是混响效果。这种效果听起来像是在一个房间里与它的回声混合在一起的感觉,更“浑厚”一些。你在洗澡间里唱歌就是这效果。
示例
在介绍完上文后,来简单地写一个demo吧!
use_bpm 96
define :sample_pattern_timed do |sample_array, time_array|
sample_array.zip(time_array) do |items|
sample items[0]
sleep items[1]
end
end
in_thread do
use_synth :pluck
use_synth_defaults release: 2, pan: -0.4, amp: 0.8
sleep 2
with_fx :reverb, mix: 0.5 do
chord_progression = [
[chord(:A3, :m), :E2],
[chord(:F2, :M), :C2],
[chord(:G3, :M), :D2],
[chord(:E3, :m), :B2],
]
live_loop :pluck_chords do
2.times do
for chord_note in chord_progression do
4.times do
play_chord chord_note[0]
sleep 0.5
play chord_note[1], amp: 0.2
sleep 0.5
end
end
end
end
2.times do
play_chord chord(:A3, :m)
sleep 1
end
end
end
in_thread(name: :drum) do
bass = :drum_heavy_kick
snare = :drum_snare_soft
tom_hi = :drum_tom_hi_soft
tom_lo = :drum_tom_lo_soft
sleep 0.75
sample_pattern_timed [snare, snare, tom_hi, tom_lo], [0.25, 0.25, 0.25, 0.5]
live_loop :base_drum_loop do
3.times do
sample_pattern_timed [bass, snare, bass, snare, bass, snare], [1, 0.5, 0.5+0.25, 0.25, 0.5, 1]
end
sample_pattern_timed [bass, snare, bass, bass, snare, snare, snare, tom_hi, tom_lo],
[1, 0.5, 0.5, 0.25, 0.5, 0.25, 0.25, 0.25, 0.5]
end
l = [[:drum_cymbal_open, 0.3]] + [[:drum_cymbal_closed, 0.4]]*31
live_loop :base_hat_loop do
loop do
x = l.ring.tick
sample x[0], amp: x[1], pan: 0.5
sleep 0.5
end
end
end
in_thread do
use_synth :beep
use_synth_defaults sustain: 0.8, release: 0.8, amp: 0.3, pan: 0.3
sleep 2
live_loop :chords_2nd do
play_pattern_timed [:E4, :A3, :A3, :E4], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:E4, :A3, :A4, :E4], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:C4, :A3, :A3, :C4], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:C4, :A3, :F4, :C4], [0.5 ,0.5 ,0.5, 0.5]
#sleep 2
play_pattern_timed [:D4, :G3, :G3, :D4], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:D4, :G3, :G4, :D4], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:B3, :G3, :B3, :B3], [0.5 ,0.5 ,0.5, 0.5]
play_pattern_timed [:B3, :G3, :D4, :B3], [0.5 ,0.5 ,0.5, 0.5]
end
end
in_thread do
use_synth :piano
use_synth_defaults sustain: 0.8, release: 1.2, amp: 1.1
with_fx :reverb, mix: 0.7, room: 0.8 do
live_loop :main do
play_pattern_timed [:A3, :B3, :C4, :A3, :B3, :C4, :A4], [1, 1, 1, 0.5, 0.5+1, 1, 2]
play_pattern_timed [:G4, :E4, :D4, :C4, :D4, :G4, :E4], [1, 1, 1, 0.5, 0.5+1, 1, 2]
play_pattern_timed [:A3, :B3, :C4, :A3, :B3, :E4, :D4], [1, 1, 1, 0.5, 0.5+1, 1, 2]
play_pattern_timed [:D4, :E4, :D4, :C4, :D4, :E4, :D4, :C4, :E4], [0.5, 0.5, 0.5, 0.5, 1, 0.5, 0.5+1, 1, 2]
end
end
end
结论
这只是一个对sonic-pi的简单的介绍,我略过了很多内容,包括琶音,高级的随机函数,线程同步,非常重要的live_loop,外接midi,等等。在帮助区域可以查到所有的支持的接口,都是单独的函数。
在最后我还是想提一个它的特别功能:在树霉派分发版上,sonic-pi可以用来玩Minecraft。只要同时启动sonic-pi和Minecraft-Pi,就可以用mc_*
类型的接口来在sonic-pi内玩Minecraft。
我本来以为这只是一个小彩蛋,没想到他们居然煞有介事地实现了一个非常长的接口列表。
- 从它的设计上来看,sonic-pi比较擅长做的是处理声音的风格化的材质、并配合简单的loop,而不擅长复杂的和声与旋律走向。某种程度上这倒与现代音乐的创作思路不谋而合。这点颇为遗憾。
- 我最喜欢它的部分还是控制流的部分,这是个非常有创意的想法。
- sonic-pi当然不是它们官网上宣称的“future of music”。借用一句传播学里的话:媒介的形式决定了媒介的内容,这句话对于创作工具而言,同样成立。所以我们不应该以一个工具是否能方便地做出符合当下标准的内容来作为评估其价值的唯一标准,而要考虑它是否能赋予内容更多的可能性,给予了创造者更多的自由。
用一句话做结论:它并不是我最想要的,但精神可嘉。
written by CC, with love and curiosity.
网友评论