当前位置: 首页 > 工具软件 > Sonic Pi > 使用案例 >

不干正事儿系列文章1:Sonic Pi简单应用

鱼安然
2023-12-01


系列文章:
不干正事儿系列文章2:AD/DA转换PCF8591模块和树莓派4

半个月前,我打开了树莓派上的Sonic Pi。从此走上了一条不归路。

——陈端午

Sonic Pi简单应用

可以先看一下我传到B站的我作的乐曲的视频
视频链接

0. Intro

先说说我打开了Sonic Pi之后都干了啥吧。
某一个周五的晚上,我突然看到了一个Sonic Pi的入门视频,然后就入坑了。当天晚上看完了不太完整的入门视频,然后第二天开始研究软件中的Tutorial。略略地看了一下发现很好上手,然后就开始了我的不归路。
当天我就写出了一段旋律(也是唯一的旋律)。然后我就很想让这段旋律可视化好让我发B站。但是我研究了一段时间都没有弄通如何让Python或者其他语言读取设备的音频输出。有说拿PyAudio来做的,有说在shell里面敲命令的。反正我也没整明白。没整明白那就不整了!我决定土法上马!
方法非常的简单粗暴。找一根3.5mm的音频公公线,找三个鳄鱼夹,利用数模转换芯片直接读取音频输出的电压不就好了吗?
在我诞生这个想法之后的两周里,我的生活变得异常的充实。

1. Sonic Pi简单介绍

废话不多说了,还是回到本文的主题Sonic Pi(关于我这两周的生活是如何充实的,我会在未来的文章中进行更新)。
先简单介绍一下Sonic Pi,如它的官网所说的:The Live Coding Music Synth for Everyone. 他一改以往传统的作曲方式,让我们可以通过敲代码的形式来创作自己想要的乐曲。

1.1 安装

如果你拥有一个树莓派的话,那么你无需再额外地安装了,树莓派内置Sonic Pi。点击左上角的开始菜单,选择Programming,其中倒数第4个就是Sonic Pi了。Sonic Pi同样也支持Windows和MacOS,点击前文中提供的官网链接可以很容易地找到安装地址。

1.2 案例

安装完成之后我们打开软件。在文本框中输入如下内容:

play 60

点击左上角的run,我们就可以听到c4了。
再比如利用如下这段简短的内容:

define :chordplayer do |root, type, amp|
  range(-3,3).each do |i|
    play (chord root, type, invert: i), amp: amp
    sleep 0.25
  end
end

我们就可以播放出一段和弦了。
我们可以这样调用:

chordplayer :c, :major, 1

他表示的就是C Major也就是i, iii, v。最后的占据amp位置的1可以用于控制和弦的响度。


如果有兴趣对这个软件有更深入的了解的话,建议可以去看一下再软件下方的官方提供的Tutorial。虽然是全英语的,但是我个人认为语言比较通俗易懂。内容上逐层递进,学习难度不是很高。

2. Sonic Pi和随机数

显然我并不是专业学音乐的,我的乐理知识也非常的有限,所以如果下文内容中有任何不通之处还请在评论区中向我指出。
去年我写过一首曲子。这次学习Sonic Pi的过程当中,我就利用了我之前写的曲子的和弦走向。具体的走向可以在源码中阅读,这里就不多罗嗦了。
下面我先大致的说一下我的思路:我在规定了和弦走向以后,对于旋律的选择非常非常的随意。我先将一个小节分为数个时间长短不一的小段,然后我利用自带的shuffle函数对之进行打乱,这样一来,我就获得了一个由随机数控制的节奏。对于音符的选择,也十分简单的粗暴。预先规定一个音域范围,然后在这个范围内随机地选择和这个小节的和弦当中的音。
现在我来分段说明一下我程序的内容:

2.1 和弦的生成

define :chordplayer do |root, type, amp|
  range(-3,3).each do |i|
    play (chord root, type, invert: i), amp: amp
    sleep 0.25
  end
end

这一段就是之前已经提到过的和弦播放器,一个数组的循环从-3开始到2结束,然后chord可以返回某个和弦经过invert之后的音符。
我来举个例子说明一下invert的作用:比如我们的C是c4, e4, g4。如果我们对之进行Invert: 1的话,那么他就变为了:e4, g4, c5。

2.2 旋律的生成

define :melodyplayer do |root, type, pos, amp, i|
  if i<8
    time = 1.5/6
    seperate = shuffle [1, 2, 3]
    len = 3
  end
  
  if i<16 && i>=8
    time = 1.5/6
    seperate = shuffle [1, 1, 2, 2]
    len = 4
  end
  
  if i<24 && i>=16
    time = 1.5/12
    seperate = shuffle [1,1,2,2,3,3]
    len = 6
  end
  
  if i< 32 && i>=24
    time = 1.5/12
    seperate = shuffle [1,1,1,1,2,1,2,3]
    len = 8
  end
  
  if i<40 && i>=32
    time = 1.5/6
    seperate = shuffle [1, 1, 2, 2]
    len = 4
  end
  
  if i<48 && i>=40
    time = 1.5/6
    seperate = shuffle [1, 2, 3]
    len = 3
  end
  
  if i==47
    notes = []
    range(0,len).each do |i|
      seperate[i] = seperate[i] * time
      notes.append((chord root, type, invert: pos+1)[rrand_i(0,2)])
    end
    range(0,len).each do |i|
      play notes[i], amp: amp
      sleep seperate[i]
    end
    return 0
  end
  
  if i<32 && i>=24
    notes = []
    range(0,len).each do |i|
      seperate[i] = seperate[i] * time
      notes.append((chord root, type, invert: pos)[rrand_i(0,2)])
    end
    
    pmb = (i%2)*2-1
    delta = -((i%2)*2-1)*2.0/(len-1)
    range(0,len).each do |i|
      play notes[i], amp: amp, pan: pmb
      pmb = pmb + delta
      sleep seperate[i]
    end
    return 0
  end
  # 重点
  notes = []
  range(0,len).each do |i|
    seperate[i] = seperate[i] * time
    notes.append((chord root, type, invert: pos)[rrand_i(0,2)])
  end
  range(0,len).each do |i|
    play notes[i], amp: amp
    sleep seperate[i]
  end
end

看完这段代码之后我想大家应该也知道了,我也不是学计算机的。不然也不会写出如此丑陋的代码。前面那么多if是因为我不知道它的switch甚至是elif应该怎么写(我也是刚知道这好像是ruby?)。
先解释一下这么多if是干嘛用的,if里面的i是目前进行到的小节数,通过这个小节数我可以设定我预先想要的一些效果。比如某一些小节快一些,某一些慢一些等等。具体的我就不多说了。
让我们着重看最后一段。seperate数组就是之前说我随机生成的节奏类型,比如seperte = [1, 2, 3]就表示第一个音符播放1个单位时间,第二个音符播放2个单位时间,第三个音符播放3个单位时间。
然后notes数组中依次存放着未来要播放的音符,其中invert后面跟的pos就能够对于输出的音所在的大致高低进行一个限制。最后的rrand_i就可以生成一个随机数来决定我到底要播放这个和弦当中的哪个音。
经过这个循环之后,我就可以开始输出了。那么play就直接把音输出来,然后sleep掉相应的间隔,这个函数就结束了,一个小结的旋律就输出完毕了。

2.3 歌曲中和弦走向以及旋律走向的规定

c_roots = [:a, :b, :e, :e, :f, :d, :e, :e]
c_types = [:minor, :dim, :major, :minor, :major, :minor, :sus4, :minor]
c_poss = [1, 0, 1, 0, 1, 0, -1, 1]

roots = []
types = []
poss = []

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

c_poss = [1, 2, 2, 1, 1, 0, -1, 0]

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

c_roots = [:a, :d, :e, :a, :a, :f, :c, :e]
c_types = [:minor, :minor, 7, :minor, :minor, :major, :m7, :sus4]
c_poss = [1, 2, 2, 2, 1, 2, 3, 3]

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

c_roots = [:a, :b, :e, :e, :f, :d, :e, :e]
c_types = [:minor, :dim, :major, :sus4, :major, :minor, :sus4, :minor]
c_poss = [3, 3, 4, 4, 4, 3, 2, 3]

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

c_roots = [:a, :b, :e, :e, :f, :d, :e, :e]
c_types = [:minor, :dim, :major, :minor, :major, :minor, :sus4, :minor]
c_poss = [1, 2, 2, 2, 1, 0, -1, -1]

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

c_roots = [:a, :d, :e, :a, :a, :f, :c, :e]
c_types = [:minor, :minor, 7, :minor, :minor, :major, :m7, :sus4]
c_poss = [1, 2, 2, 1, 0, 0, 0, 1]

roots = roots + c_roots
types = types + c_types
poss = poss + c_poss

mal = 48

这里c_roots中的c表示的是current的意思。我以8个小节为单位对歌曲进行了划分。全曲一共48个小节(那个mal是德语,次数的意思)。poss代表了某一个小节的旋律的高低走向,数字越大音就越高。

2.4 歌曲播放

in_thread(name: :t1) do
  use_synth :tri
  i = 0
  amp = 1.0
  mal.times do
    if i>=40
      amp = amp - 1.0/9
    end
    chordplayer roots[i], types[i], amp
    i = inc i
  end
end

in_thread(name: :t2) do
  use_random_seed 5
  use_synth :fm
  i = 0
  amp = 1
  mal.times do
    if i>=40
      amp = amp - 1.0/9
    end
    melodyplayer roots[i], types[i], poss[i], amp, i
    i = inc i
  end
end

这边这个in_thread应该是多线程吧。通过这个方法我们可以让和弦和旋律同时开始播放。这里的use_synth表示选择播放时的音色。在播放旋律时可以看到我有一个if i>=40。这个的目的是为了最后有一个渐弱的效果。
最后我们再给曲子加上一个鼓点。

in_thread(name: :t4) do
  8.times do
    sleep 1.5
  end
  8.times do
    sample :drum_tom_mid_soft
    sleep 1.5
  end
  8.times do
    sample :drum_tom_mid_soft
    sleep 0.5
    sample :drum_tom_hi_hard
    sleep 1
  end
  8.times do
    sample :drum_tom_mid_soft
    sleep 0.5
    sample :drum_tom_hi_hard
    sleep 0.75
    sample :drum_tom_hi_hard
    sleep 0.25
  end
  8.times do
    sample :drum_tom_mid_soft
    sleep 0.5
    sample :drum_tom_hi_hard
    sleep 1
  end
  amp = 1
  8.times do
    sample :drum_tom_mid_soft, amp: amp
    amp = amp - 1.0/8
    sleep 1.5
  end
end

这里的sample在这里表示的是我选择的鼓的音色。但它其实还有更多的用处,详细的可以参考官方的Tutorial。大概内容的介绍就是这些了。如果想要在Sonic Pi中播放我的曲子的话,从第二章开始,将所有代码都赋值进去点击run就可以听到了。

3. 未来更新计划

最后简单地说一下我未来的更新计划:
介绍AD/DA转换芯片PCF8591在树莓派上的应用(结合NE5532P运放芯片)
介绍MQTT数据传输和pyqtgraph的绘图。


以上

 类似资料: