谁在用Python弹奏一曲东风破 - 第二期 - 蜂鸣器版

之前借助Python利用虚拟钢琴软件弹奏了一曲东风破, 虽然是成功了, 但是终究还是要下载一个虚拟钢琴, 那么我想, 我能不能直接用电脑的蜂鸣器来弹奏一曲东风破呢? 感谢 @刘之帅 提供的创意.

不过做这个之前, 需要先普及一点乐理知识, 否则后面的代码会看不懂, 乐理这个东西, 我也是一个初学者, 可能有我理解的不对的地方, 也请各位看官指出.

11e29bad3a7ba1250a5667c3aed9a08f.png

预备知识

说到音乐, 那肯定离不开声音的三要素: 音调, 音色, 音量. 对于音量, 其实也就是声音的响度, 说白了就是声音的大小, 这个对于蜂鸣器来说, 是固定的, 所以我们暂时不需要考虑这个. 对于音色, 这个东西和发声器相关, 具有同样响度和音调的声音, 不同乐器发出声音听起来的感觉也是不同的, 比如我们用二胡和吉他弹奏同一首曲子, 虽然他们都是同一首曲子, 但是我们还是能够分辨是那种乐器弹奏出来的. 接下来是重点要解释的部分, 音调, 通俗解释就是人们对于声音高低的感觉, 比如男低音或者女高音, 说的就是音调, 这个主要和声波的频率有关. 对于蜂鸣器来说, 我们肯定是不能改变他的音色和音量的, 然鹅, 我们可以改变它发声的频率, 从而改变音调, 来弹奏一首曲子.

十二音律

说到音调, 我们自然不可能绕过十二音律这个东西, 我们先来看一张图.

image

十二音律是怎么来的呢, 就是把一定频率的音分成12份, 通常来说, 是[440, 880], 这个作为基准, 每升高一个八度, 频率翻一番, 每降低一个八度, 频率减少一半. 在钢琴中, 正好有7个白键, 5个黑键正好12个. 因为常用的键位是7个, 所以白色键位是常用键位. 黑色是不常用键位, 因此白色它比较宽, 比较大, 具体这些细节不是本文的重点, 因为我也不是学这个的, 可能我理解的也不是很到位, 这里的重点是我们如何找到钢琴中每个键位对应的频率, 如果不简单理解一下这些的话, 后面频率部分的代码, 可能是不太好理解的, 所以在这里也简单说一下. 下面先给出一个频率对应表.

view.jpg

这张表, 当然不可能手敲进去, 当然, 如果闲得无聊的话, 手敲也是可以的, 因为这个是有规律的, 上文说过, 这其实是把频率分成了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开始的, 而DoC, 蛤, 因此看下面代码的时候需要注意到这个问题, 否则你的音会不对, 而且要注意到, 这是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]
]

得到这个频率表, 我们就可以开始尝试用蜂鸣器来弹奏了. 上面这个表中每一行代表一个八度, 然后从DoSi顺序排列. 不过, 在正式开始写代码之前, 还要补充最后一个知识点, 那就是曲速, 我们可以发现, 我们在听音乐的时候, 每个音的长短是不一样的, 因此, 我们如果想演奏出来的效果更好一些的话, 我们肯定是需要考虑一个十分重要的问题, 也就是曲速, 我们先来看一下东风破的简谱.

东风破

这里我们做一下简单化处理, 因为这些复杂的节奏, 我也把握不准, 因此我们这里按照下面的基准来区分节奏.我们按照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), 回复关键词: 东风破 获取.

在这里插入图片描述

发布了27 篇原创文章 · 获赞 46 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/anonymous_qsh/article/details/104812063