前言

有一天突然发现电脑上键这么多,刚好可以用来弹琴!
这个是我很早之前就有的一个想法,终于准备着手做。
一开始打算用c++做,在网上搜了一下c++怎么调用电脑的扬声器模块,发现比较难搞;
于是转而考虑使用python,发现好像还蛮简单的。
我的思路是,先找到do,re,mi,fa,so,la,xi音调对应的音频,然后根据输入的不同按键来播放不同的音频文件就可以啦。

那么第一步,先找音频~


苦苦寻找


唉,不好找啊:(

去百度网盘康康!


。。。
再去网易云

怎么办呐QAQ
终于。。。!

找到了!!!
csdn上好多
下载链接,一键直达
这么多资源,是不是有人做过?!

下载了音频文件,接下来开始写代码

用python实现

首先找了下,用playsound库可以实现播放wav文件

详细用法见playsound官方文档
以及一篇中文的博客
如何利用Python播放和录制声音
两行代码,播放刚才下好的文件,好用

from playsound import playsound
playsound('tone (1).wav')

再用键盘控制

python中捕获键盘事件的方法有很多
我用的是pygame里的方法
下面是代码
实现的是按下z键播放一个钢琴按键声

from playsound import playsound
import pygamepygame.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 Processdef 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 pygamedef 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 pygamedef 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:breakelse:if t == tone_now:continueelif t == play_history[0]:continueelif t == play_history[1]:continueelif t == play_history[2]:continueelif t == play_history[3]:continueelse: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)])breakpygame.display.update()def main():k_control()if __name__ == '__main__':main()

ps:这里的代码没有专门性能优化,只是简单地实现了功能,有什么问题可以留言讨论哈~

用Python和电脑键盘做一个电子琴(硬核)相关推荐

  1. Python 用pygame 做一个游戏的开始界面(小白第一篇博客)

    Python 用pygame 做一个游戏的开始界面(小白第一篇博客) 主要功能实现 本篇文章主要是实现了一个游戏开始界面的两个功能: 1,将鼠标放到"开始游戏"或"结束游 ...

  2. 圣诞节快到了,用Python给好友做一个圣诞树小程序吧【保姆式教程】

    圣诞节快到了,用Python给好友做一个圣诞树小程序吧[保姆式教程] 马上圣诞节了,一个人的圣诞节可能会有些孤独,我来教你怎么用代码写一棵超级治愈的圣诞树. 话不多说,下面来看具体怎么实现吧! 文章目 ...

  3. python打分_做一个Python颜值打分系统,比比看杨幂和杨超越到底谁更美?

    下面就来讲讲我设计的这套颜值打分系统,先上图片让大家看一下效果,比如看一下我的女神杨幂的颜值如何: 怎么样,结果是相当的精准吧,大家是不是已经跃跃欲试了呢?下面就针对该颜值打分系统进行讲解. 01. ...

  4. python a股行情_用Python,tushare做一个A股每日收盘行情监测分析(含源代码)

    灵感:对于交易者来说,每日收盘分析是一件必要的功课.现在的看盘软件种类很多,已经有很多整理好的数据,但是对于高阶玩家,可能需要更多自定义化的功能,做出自己的每日分析报表.那我们何不尝试用Python做 ...

  5. python 涨停统计_用Python,tushare做一个A股每日收盘行情监测分析(含源代码)

    灵感:对于交易者来说,每日收盘分析是一件必要的功课.现在的看盘软件种类很多,已经有很多整理好的数据,但是对于高阶玩家,可能需要更多自定义化的功能,做出自己的每日分析报表.那我们何不尝试用Python做 ...

  6. 用Python和pygane做一个简单的3D打飞机教程

    这里是一个用python和pygame做的简单3D打飞机游戏实例教程: 1. 引入pygame和其他库 python  import pygame  from pygame.locals import ...

  7. python代码雨在桌面实现_今天七夕节,外面下着大雨,用Python的tkinter做一个下爱心雨的特效,发给妹子...

    正文 今天七夕,还下着雨,刚好想做一个下着爱心雨的特效 准备图片素材 1.美图秀秀找一个爱心图,大小就50*50就可以,生成的是一个png格式文件 2.由于canvas.create_image只支持 ...

  8. 46 行 Python 代码,做一个会旋转的三维甜甜圈!

    摘要:在三维渲染技术中,符号距离函数很难理解,而本文作者仅用 46 行  Python 代码就展示了这项技术. 链接:https://vgel.me/posts/donut/ 声明:本文为 CSDN ...

  9. python网易云歌词做成词云图_讨好女朋友:用Python给女朋友做一个歌曲词云图

    今天咋们来看看网易云赵雷的歌曲歌词,并做一个词云图.这篇文章可以学习到什么是词云,爬虫的基本流程,简单的可视化操作 一 什么是词云 可视化有很多种,好的数据可视化,可以使得数据分析的结果更加通俗易通. ...

最新文章

  1. NLP突破性成果 BERT 模型详细解读 bert参数微调
  2. C++C#外挂(内存修改)
  3. 程序员致富的若干方法探讨
  4. 百度推出LinearDesign,全球首个mRNA疫苗不稳定性解决方案,仅需16分钟
  5. 计算机系教研工作计划,计算机教研室工作计划怎么写
  6. 如何判断两个平面相交_七年级下册相交线与平行线全章节复习
  7. angular路由传递参数_Angular路由——在路由时候传递数据
  8. core--线程同步(内核模式)
  9. 收藏一些 JQuery 导航插件
  10. Visual Studio 2010如何利用宏
  11. python画超长图-python—networkx:求图的平均路径长度并画出直方图
  12. linux vim命令的意思,Linux 中 vim 是什么意思?
  13. 如何理解海森堡的「不确定性原理」?
  14. 通信协议:CAN总线
  15. 如何做一个真正的男人
  16. layui常用审核弹窗
  17. postgresql常用命令和执行sql脚本
  18. 使用NPOI按照word模板文件生成新的word文件
  19. bash grep 判断_bash 退出状态与条件判断
  20. 2019年《安徽省阜阳市颍州区立体绿化工作实施方案》政策解读

热门文章

  1. 从新手到专家 ——外包团队研发工程师的成长之路
  2. PS学习之餐饮行业修图
  3. 【NLP】TensorFlow实现CNN用于中文文本分类
  4. Android设置竖屏锁定
  5. ssm基于java的儿童成长记录系统
  6. 酷睿i5 12450h和i7 12700h选哪个好
  7. iOS小技能:解决图片压缩之后的模糊问题
  8. OneAlive--游戏音效管理器
  9. 什么是闭包?有什么用?怎么用?
  10. 智慧灯杆网关典型应用案例介绍