之前借助Python利用虚拟钢琴软件弹奏了一曲东风破, 虽然是成功了, 但是终究还是要下载一个虚拟钢琴, 那么我想, 我能不能直接用电脑的蜂鸣器来弹奏一曲东风破呢? 感谢 @刘之帅 提供的创意.
不过做这个之前, 需要先普及一点乐理知识, 否则后面的代码会看不懂, 乐理这个东西, 我也是一个初学者, 可能有我理解的不对的地方, 也请各位看官指出.
预备知识说到音乐, 那肯定离不开声音的三要素: 音调, 音色, 音量. 对于音量, 其实也就是声音的响度, 说白了就是声音的大小, 这个对于蜂鸣器来说, 是固定的, 所以我们暂时不需要考虑这个. 对于音色, 这个东西和发声器相关, 具有同样响度和音调的声音, 不同乐器发出声音听起来的感觉也是不同的, 比如我们用二胡和吉他弹奏同一首曲子, 虽然他们都是同一首曲子, 但是我们还是能够分辨是那种乐器弹奏出来的. 接下来是重点要解释的部分, 音调, 通俗解释就是人们对于声音高低的感觉, 比如男低音或者女高音, 说的就是音调, 这个主要和声波的频率有关. 对于蜂鸣器来说, 我们肯定是不能改变他的音色和音量的, 然鹅, 我们可以改变它发声的频率, 从而改变音调, 来弹奏一首曲子.
十二音律说到音调, 我们自然不可能绕过十二音律这个东西, 我们先来看一张图.
十二音律是怎么来的呢, 就是把一定频率的音分成12份, 通常来说, 是[440, 880]
, 这个作为基准, 每升高一个八度, 频率翻一番, 每降低一个八度, 频率减少一半. 在钢琴中, 正好有7个白键, 5个黑键正好12个. 因为常用的键位是7个, 所以白色键位是常用键位. 黑色是不常用键位, 因此白色它比较宽, 比较大, 具体这些细节不是本文的重点, 因为我也不是学这个的, 可能我理解的也不是很到位, 这里的重点是我们如何找到钢琴中每个键位对应的频率, 如果不简单理解一下这些的话, 后面频率部分的代码, 可能是不太好理解的, 所以在这里也简单说一下. 下面先给出一个频率对应表.
这张表, 当然不可能手敲进去, 当然, 如果闲得无聊的话, 手敲也是可以的, 因为这个是有规律的, 上文说过, 这其实是把频率分成了12份, 构成一个等比数列, 公比是2^(1/12)
, 所以我们只需要找到上图中的O1
, 然后不断做乘法就可以生成上面那张表.
def generate_piano_table():
start = 27.5
table = []
for i in range(8):
tmp = []
for j in range(12):
tmp.append(round(start))
start *= 2 ** (1 / 12)
table.append(tmp)
return table
这里考虑到一个问题, 就是蜂鸣器接收频率, 只能写整数, 因此这里我们做个四舍五入, 近似一下. 再考虑到, 这个表是固定的, 因此我们生成一边之后, 复制出来就行了, 没必要每次都运行. 在这里给大家一个福利吧, 我直接把这张表贴出来. 也省的在运行了.
PIANO_TABLE = [
[28, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52],
[55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, 104],
[110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208],
[220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415],
[440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831],
[880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661],
[1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322],
[3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645]
]
但是注意一下, 这张表是从A开始的, 而Do
是C
, 蛤, 因此看下面代码的时候需要注意到这个问题, 否则你的音会不对, 而且要注意到, 这是12音, 一般来说, 我们只需要do, re, mi, fa, sol, la, si
这七个, 因此我们做一个简单的处理, 把正常的C调, 以及高八度, 低八度, 都提取出来, 其他的嘛, 我暂时也用不到, 需要的人, 自己处理一下吧.
SIMPLE_TABLE = [
[33, 37, 41, 44, 49, 55, 62],
[65, 73, 82, 87, 98, 110, 123],
[131, 147, 165, 175, 196, 220, 247],
[262, 294, 330, 349, 392, 440, 494], # C^1
[523, 587, 659, 698, 784, 880, 988],
[1047, 1175, 1319, 1397, 1568, 1760, 1976],
[2093, 2349, 2637, 2794, 3136, 3520, 3951]
]
得到这个频率表, 我们就可以开始尝试用蜂鸣器来弹奏了. 上面这个表中每一行代表一个八度, 然后从Do
到Si
顺序排列. 不过, 在正式开始写代码之前, 还要补充最后一个知识点, 那就是曲速, 我们可以发现, 我们在听音乐的时候, 每个音的长短是不一样的, 因此, 我们如果想演奏出来的效果更好一些的话, 我们肯定是需要考虑一个十分重要的问题, 也就是曲速, 我们先来看一下东风破的简谱.
这里我们做一下简单化处理, 因为这些复杂的节奏, 我也把握不准, 因此我们这里按照下面的基准来区分节奏.我们按照800ms
算一拍, 也就是一个四分音符, 然后八分音符是400ms
暂时不考虑连音.
到这里, 我们就完成基础的乐理知识的讲解了, 当然本文的重点不在这块上面, 这些知识是便于读者理解后面的代码.
编码之前一篇文章, 已经说过, 如何调用系统层面的东西了, 这里蜂鸣器, 我们依然通过win32api
来调用. 这里因为之前我生成谱子是按照400ms做的, 但是发现间隔太短了, 后来我也没改, 然后时间直接翻倍就好了.
player = ctypes.windll.kernel32
def beep(rate, _time=400):
if rate == -1:
time.sleep(_time/1000)
player.Beep(rate, _time)
这个函数比较简单, 第一个参数是频率, 第二个参数是时间. 这里一看应该就明白了, 不需要我再多解释什么了.
接下来就是最重要的部分, 谱子了, 这个谱子, 我们需要记录音调和时间, 手动来个简单的结构表示.
{
"tone": 330,
"time": 400
}
第一个参数是音调, 按照上文所说的表中, 第几行第几列, 来获取对应的频率, 第二个参数是时间. 接下来就是写谱子了, 手动来吧. 如果tone = -1
, 表示不发声.
有了谱子之后, 播放就非常简单了, 代码如下:
for i in music:
print(i)
beep(i['tone'], i['time'] * 2)
这样, 蜂鸣器版本的东风破就制作完成了, 虽然代码不是很复杂, 但是对于我这种五音不全的人来说, 难点完全不在于代码上, 哈哈, 不过这个小挑战也算是成功了.
源码和简谱图片, 以及谱子, 大家可以关注我的微信公众号(Coder小Q), 回复关键词: 东风破 获取.