有一天突然发现电脑上键这么多,刚好可以用来弹琴!
这个是我很早之前就有的一个想法,终于准备着手做。
一开始打算用c++做,在网上搜了一下c++怎么调用电脑的扬声器模块,发现比较难搞;
于是转而考虑使用python,发现好像还蛮简单的。
我的思路是,先找到do,re,mi,fa,so,la,xi音调对应的音频,然后根据输入的不同按键来播放不同的音频文件就可以啦。
。。。
再去网易云
怎么办呐QAQ
终于。。。!
找到了!!!
csdn上好多
下载链接,一键直达
这么多资源,是不是有人做过?!
下载了音频文件,接下来开始写代码
详细用法见playsound官方文档
以及一篇中文的博客
如何利用Python播放和录制声音
两行代码,播放刚才下好的文件,好用
from playsound import playsound
playsound('tone (1).wav')
python中捕获键盘事件的方法有很多
我用的是pygame里的方法
下面是代码
实现的是按下z键播放一个钢琴按键声
from playsound import playsound
import pygame
pygame.init()
screen = pygame.display.set_mode((600, 400))
pygame.display.set_caption('pygame event')
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_z:
playsound('tone (10).wav')
pygame.display.update()
有几个小的坑:
from playsound import playsound
不能直接写import playsound
screen = pygame.display.set_mode((600, 400))
pygame.display.set_caption(‘pygame event’)
pygame.init()
否则会报错pygame.error: video system not initialized
这样就实现了基本的按键控制钢琴,但是有个不完美的地方(应该叫体验极差)
每个录音文件后面都有一段不短的无声
但必须要等待这个文件播放完毕才会进行下一步的操作
造成这个钢琴很不跟手啊。。。
第一个键按完半天,才能按下一个
真实的钢琴声音应该是一个音未落,另一个音就能弹出来
这样可以按完一个键,不用等文件播放完,就能播放下一个键,甚至可以多个键一起按,更符合真实钢琴的亚子
那么再去找找怎么实现Python多线程。。。
参考几篇博客
Python 多线程操作 <–这篇特别棒
python之多线程
多线程:廖雪峰的官方网站
看了很多文章,发现python的多线程不能并行处理多个任务,因为python解释器在执行代码时,有一个GIL锁,这个锁的作用是保证同一时刻只有一个线程在工作,哭了
又是几篇好文章
多进程:廖雪峰的官方网站
第 10 章 python进程与多进程
经过很长时间的学习和尝试,用python自带的multiprocessing库实现了可以先按一个键,再马上按下一个键,代码如下:
实现的是用两个进程运行控制键盘播放录音的程序:
当按下z时,播放第60个音阶
当按下x时,播放第20个音阶
无需等待
from playsound import playsound
import pygame
from multiprocessing import Process
def window_init():
pygame.init()
screen = pygame.display.set_mode((600, 400))
pygame.display.set_caption('keyboardpiano')
def k_control(key_param):
window_init()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.KEYDOWN:
if event.key == key_param:
if key_param == pygame.K_z:
playsound('tone (60).wav')
elif key_param == pygame.K_x:
playsound('tone (20).wav')
pygame.display.update()
def main():
p1 = Process(target=k_control, args=(pygame.K_z,))
p2 = Process(target=k_control, args=(pygame.K_x,))
p1.start()
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
main()
但是仍然存在两个问题:
问题一
pygame获取键盘事件必须要创建一个窗口,否则会报错
于是当使用多进程时,就需要打开多个窗口
这样只有鼠标选中窗口1时,按下该进程所对应的按键才会有用
即选中哪个窗口才会执行哪个窗口对应的进程
ps:
{
点住窗口一
按下z键
“噔”的一声
瞬间再点住窗口二
按下x键
“Duang”的一声
。。。
}
问题二
有的音乐是几个相同的音阶连在一起的,比如
mi mi mi re mi ,do re do la so
那这个程序还是会按下第一个mi等很长时间才可以按第二个
解决思路:
对于第一个问题,可以尝试一下换用其他的获取键盘事件的方法;
对于第二个问题,可以尝试批量把录音文件剪短;
找了三种方法
pyhook
tkinter
curses
这三个都可以读取键盘事件,可是无一例外的都需要一个GUI窗口
我想可能想要获取键盘事件必须要有一个window才行
因为电脑同时有很多进程在工作,有很多窗口例如浏览器、word文档、IDE,这些窗口都需要获得键盘事件。如果不选中某一个确定的窗口,计算机无法知道当前的键盘事件哪个进程调用的。
也将会出现一些奇奇怪怪的事情,比如你打开着跟你妈聊天的QQ界面,同时在浏览器上输入了可以描述的东西,按下enter键,嗖的一下,消息就到了你妈眼里。。。
于是放弃了这个方案
之前做过一点音频的处理,用的软件叫Cool Edit Pro,还蛮好用的
因为有88个文件,得批量处理
先批量导入音频文件
从音频的波形图可以看出,钢琴声后面很大一部分都是很微弱甚至没有声音,剪之
经过裁剪,发现对于高音效果很好,像上面的低音如果剪断会在结束的时候很突兀,从有声一瞬间变到无声,有一声小小的突变“砰~”
那该怎么办呢???
用pygame里的一个方法
pygame.mixer.init()
tone_1 = pygame.mixer.Sound('tone (1).wav')
tone_1.play()
啥问题都没有
按一下响一下
完事了,去您妈的多进程,去您妈的playsound,pygame牛批!
pygame.mixer.music模块的一些链接
Pygame详解(十四):music 模块
[BUG]pygame.mixer.music.play
最终代码
import pygame
def window_init():
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((600, 400))
pygame.display.set_caption('keyboardpiano')
window_init()
tone_3 = pygame.mixer.Sound('tone (3).wav')
tone_6 = pygame.mixer.Sound('tone (6).wav')
tone_9 = pygame.mixer.Sound('tone (9).wav')
tone_12 = pygame.mixer.Sound('tone (12).wav')
tone_15 = pygame.mixer.Sound('tone (15).wav')
tone_18 = pygame.mixer.Sound('tone (18).wav')
tone_21 = pygame.mixer.Sound('tone (21).wav')
tone_24 = pygame.mixer.Sound('tone (24).wav')
tone_27 = pygame.mixer.Sound('tone (27).wav')
tone_30 = pygame.mixer.Sound('tone (30).wav')
tone_33 = pygame.mixer.Sound('tone (33).wav')
tone_36 = pygame.mixer.Sound('tone (36).wav')
tone_39 = pygame.mixer.Sound('tone (39).wav')
tone_42 = pygame.mixer.Sound('tone (42).wav')
tone_45 = pygame.mixer.Sound('tone (45).wav')
tone_48 = pygame.mixer.Sound('tone (48).wav')
tone_51 = pygame.mixer.Sound('tone (51).wav')
tone_54 = pygame.mixer.Sound('tone (54).wav')
tone_57 = pygame.mixer.Sound('tone (57).wav')
tone_60 = pygame.mixer.Sound('tone (60).wav')
tone_63 = pygame.mixer.Sound('tone (63).wav')
tone_66 = pygame.mixer.Sound('tone (66).wav')
tone_69 = pygame.mixer.Sound('tone (69).wav')
tone_72 = pygame.mixer.Sound('tone (72).wav')
tone_75 = pygame.mixer.Sound('tone (75).wav')
tone_78 = pygame.mixer.Sound('tone (78).wav')
def k_control():
while True:
print('true')
for event in pygame.event.get():
print('event in?')
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.KEYDOWN:
print('key down?')
if event.key == pygame.K_q:
tone_3.play()
elif event.key == pygame.K_a:
tone_6.play()
elif event.key == pygame.K_z:
tone_9.play()
elif event.key == pygame.K_w:
tone_12.play()
elif event.key == pygame.K_s:
tone_15.play()
elif event.key == pygame.K_x:
tone_18.play()
elif event.key == pygame.K_e:
tone_21.play()
elif event.key == pygame.K_d:
tone_24.play()
elif event.key == pygame.K_c:
tone_27.play()
elif event.key == pygame.K_r:
tone_30.play()
elif event.key == pygame.K_f:
tone_33.play()
elif event.key == pygame.K_v:
tone_36.play()
elif event.key == pygame.K_t:
tone_39.play()
elif event.key == pygame.K_g:
tone_42.play()
elif event.key == pygame.K_b:
tone_45.play()
elif event.key == pygame.K_y:
tone_48.play()
elif event.key == pygame.K_h:
tone_51.play()
elif event.key == pygame.K_n:
tone_54.play()
elif event.key == pygame.K_u:
tone_57.play()
elif event.key == pygame.K_j:
tone_60.play()
elif event.key == pygame.K_m:
tone_63.play()
elif event.key == pygame.K_i:
tone_66.play()
elif event.key == pygame.K_k:
tone_69.play()
elif event.key == pygame.K_o:
tone_72.play()
elif event.key == pygame.K_l:
tone_75.play()
elif event.key == pygame.K_p:
tone_78.play()
pygame.display.update()
def main():
k_control()
if __name__ == '__main__':
main()
可以开心地弹琴啦!
问题:pygame.key.get_pressed()不工作,一开始用的这个方法,困扰了很久
在stack overflow找到了答案
原因及解决方法:The problem is that you don’t process pygame’s event queue. You should simple call pygame.event.pump() at the end of your loop and then your code works fine。(在循环的最后面加一句pygame.event.pump)
还有一个问题,pygame虽然好用,但仍有瑕疵,在用pygame.mixer.music播放音乐时,连续按五六下按键,还是会出现停顿,要等一会才能继续按,可能是音乐播放的任务是有上限的
我想了一个办法,用一个list存放最近5次的播放记录,每次有新的键盘事件产生时,关闭除最近5次记录外的所有正在播放的进程。
试了下
果然解决了问题!!!
代码如下
def stop_too_early(tone_now):这个函数关闭了当前按键的五个之前的所有播放进程
import pygame
def window_init():
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((1200, 600))
pygame.display.set_caption('keyboardpiano')
# init pygame
window_init()
# load tunes
tone = []
for i in range(1, 27):
name_str = 'tone (' + '%d' % (i*3) + ').wav'
print(name_str)
tone.append(pygame.mixer.Sound(name_str))
# save keys
key = [pygame.K_q, pygame.K_a, pygame.K_z, pygame.K_w, pygame.K_s, pygame.K_x, pygame.K_e, pygame.K_d, pygame.K_c, pygame.K_r, pygame.K_f, pygame.K_v, pygame.K_t, pygame.K_g, pygame.K_b, pygame.K_y, pygame.K_h, pygame.K_n, pygame.K_u, pygame.K_j, pygame.K_m, pygame.K_i, pygame.K_k, pygame.K_o, pygame.K_l, pygame.K_p]
# save play history
play_history = []
# stop early tune, incase play jam
def stop_too_early(tone_now):
if len(play_history) < 5:
play_history.append(tone_now)
else:
play_history.pop(0)
play_history.append(tone_now)
for t in tone:
if len(play_history) < 5:
break
else:
if t == tone_now:
continue
elif t == play_history[0]:
continue
elif t == play_history[1]:
continue
elif t == play_history[2]:
continue
elif t == play_history[3]:
continue
else:
print('stop')
t.stop()
# use event.type == pygame.KEYDOWN to get keyboard input
def k_control():
while True:
# print('true')
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.KEYDOWN:
print(event.key)
for e in key:
if e == event.key:
tone[key.index(e)].play()
stop_too_early(tone[key.index(e)])
break
pygame.display.update()
def main():
k_control()
if __name__ == '__main__':
main()
ps:这里的代码没有专门性能优化,只是简单地实现了功能,有什么问题可以留言讨论哈~