美文网首页
用代码写音乐: sonci-pi简介

用代码写音乐: sonci-pi简介

作者: 电子肖邦 | 来源:发表于2020-01-30 03:13 被阅读0次

    如果你喜欢音乐,为什么不自己写一首歌呢?听过一万首歌,不如自己写一首。本文介绍一个简单的用代码写音乐片段的玩具 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
    

    在数字前加上sb可以表示升半音与降半音,如play :Fs3play :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同样也支持amppan参数。不过要注意的是,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}

    在大部分情况下,这些日志都是可以忽略的,只需要看着它随着音乐刷下来。但有时候我们需要确定播放状态的或者其他的信息的时候,就需要用printputs函数手动把一些内容输出到日志窗口。

    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,点此试听。其中,在doend之间的部分,被称为代码块(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)这个概念更合适一点。

    略复杂的声音播放

    仅依靠playsleep组合,理论上就可以写出任何旋律了。但事实上不会有人这样做的,因为它写起来太长了。并且这也不符合人对于乐句的思维方式:一个乐句应该是一个连贯的序列,而不是被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和Minecraft

    我本来以为这只是一个小彩蛋,没想到他们居然煞有介事地实现了一个非常长的接口列表。

    • 从它的设计上来看,sonic-pi比较擅长做的是处理声音的风格化的材质、并配合简单的loop,而不擅长复杂的和声与旋律走向。某种程度上这倒与现代音乐的创作思路不谋而合。这点颇为遗憾。
    • 我最喜欢它的部分还是控制流的部分,这是个非常有创意的想法。
    • sonic-pi当然不是它们官网上宣称的“future of music”。借用一句传播学里的话:媒介的形式决定了媒介的内容,这句话对于创作工具而言,同样成立。所以我们不应该以一个工具是否能方便地做出符合当下标准的内容来作为评估其价值的唯一标准,而要考虑它是否能赋予内容更多的可能性,给予了创造者更多的自由。

    用一句话做结论:它并不是我最想要的,但精神可嘉。

    written by CC, with love and curiosity.

    相关文章

      网友评论

          本文标题:用代码写音乐: sonci-pi简介

          本文链接:https://www.haomeiwen.com/subject/vlwejctx.html