用Python制作简单的钢琴程序的教程


Posted in Python onApril 01, 2015

录一段音频,把它的音高改变50次并把每一个新的音频匹配到键盘的一个键位,你就能把电脑变成一架钢琴!

一段音频可以被编码为一组数值的数组(或者列表),像这样:

用Python制作简单的钢琴程序的教程

我们可以在数组中每隔一秒拿掉一秒的值来将这段音频的速度变成两倍。

用Python制作简单的钢琴程序的教程

如此我们不仅将音频的长度减半了,而且我们还将它的频率翻倍了,这样使得它拥有比原来更高的音高(pitch)。

相反地,假如我们将数组中每个值重复一次,我们将得到一段更慢,周期更长,即音高更低的音频:

用Python制作简单的钢琴程序的教程

这里提供一个可以按任意系数改变音频速度的任意简单的Python函数:
 

import numpy as np
 
def speedx(sound_array, factor):
  """ 将音频速度乘以任意系数`factor` """
  indices = np.round( np.arange(0, len(snd_array), factor) )
  indices = indices[indices < len(snd_array)].astype(int)
  return sound_array[ indices.astype(int) ]

这个问题更困难的地方在于改变音频长度的同时保持它的音高(变速,音频拉伸(sound stretching)),或者在改变音频的音高的同时保持它的长度(变调(pitch shifting))。
变速

变速可以通过传统的相位声码器(phase vocoder,感兴趣的朋友可以读一下维基百科的页面)来实现。首先将音频分解成重叠的比特,然后将这些比特重新排列使得他们重叠得更多(将缩短声音的长度)或者更少(将拉伸音频的长度),如下图所示:

用Python制作简单的钢琴程序的教程

困难之处在于重新排列的比特可能很严重的互相影响,那么这里就需要用到相位变换来确保它们之间没有影响。这里有一段Python代码,取自这个网页(打不开的话,您懂的。——译者注):
 

def stretch(sound_array, f, window_size, h):
  """ 将音频按系数`f`拉伸 """
 
  phase = np.zeros(window_size)
  hanning_window = np.hanning(window_size)
  result = np.zeros( len(sound_array) /f + window_size)
 
  for i in np.arange(0, len(sound_array)-(window_size+h), h*f):
 
    # 两个可能互相重叠的子数列
    a1 = sound_array[i: i + window_size]
    a2 = sound_array[i + h: i + window_size + h]
 
    # 按第一个数列重新同步第二个数列
    s1 = np.fft.fft(hanning_window * a1)
    s2 = np.fft.fft(hanning_window * a2)
    phase = (phase + np.angle(s2/s1)) % 2*np.pi
    a2_rephased = np.fft.ifft(np.abs(s2)*np.exp(1j*phase))
 
    # 加入到结果中
    i2 = int(i/f)
    result[i2 : i2 + window_size] += hanning_window*a2_rephased
 
  result = ((2**(16-4)) * result/result.max()) # 归一化 (16bit)
 
  return result.astype('int16')

 
变调

一旦你实现了变速以后,变调就不难了。如果需要一个更高的音高,可以先将这段音频拉伸并保持音高不变,然后再加快它的速度,如此最后得到的音频将具有原始音频同样的长度,更高的频率,即更高的音高。

把一段音频的频率翻倍将把音高提高一个八度,也就是12个半音。因此,要将音高提高n个半音的话,我们需要将频率乘上系数2^(n/12):
 

def pitchshift(snd_array, n, window_size=2**13, h=2**11):
  """ 将一段音频的音高提高``n``个半音 """
  factor = 2**(1.0 * n / 12.0)
  stretched = stretch(snd_array, 1.0/factor, window_size, h)
  return speedx(stretched[window_size:], factor)

 
小程序:电脑钢琴

让我们来玩一下我们的变调器。我们先敲碗来确定一个“标准音高”:

[youku id="XNzM1NDM2NTky"]

接下来我们基于之前的音频创造50个变调的音高,从很低到很高:
 

from scipy.io import wavfile
 
fps, bowl_sound = wavfile.read("bowl.wav")
tones = range(-25,25)
transposed = [pitchshift(bowl_sound, n) for n in tones]

接下来根据这个文件中的顺序,我们把每一个音频匹配到键盘的一个键位,如下图所示:

用Python制作简单的钢琴程序的教程

我们只需要在代码中告诉计算机当一个键按下来的时候播放其对应的声音,然后当按键松开后停止播放就可以了:

import pygame
 
pygame.mixer.init(fps, -16, 1, 512) # 太灵活了 <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_wink.gif" alt=";)" class="wp-smiley">
screen = pygame.display.set_mode((640,480)) # 设置焦点
 
# 得到键盘的键位的正确顺序的列表
# ``keys`` 如 ['Q','W','E','R' ...] 一样排列
keys = open('typewriter.kb').read().split('\n')
 
sounds = map(pygame.sndarray.make_sound, transposed)
key_sound = dict( zip(keys, sounds) )
is_playing = {k: False for k in keys}
 
while True:
 
  event = pygame.event.wait()
 
  if event.type in (pygame.KEYDOWN, pygame.KEYUP):
    key = pygame.key.name(event.key)
 
  if event.type == pygame.KEYDOWN:
 
    if (key in key_sound.keys()) and (not is_playing[key]):
      key_sound[key].play(fade_ms=50)
      is_playing[key] = True
 
    elif event.key == pygame.K_ESCAPE:
      pygame.quit()
      raise KeyboardInterrupt
 
  elif event.type == pygame.KEYUP and key in key_sound.keys():
 
    key_sound[key].fadeout(50) # 停止播放并50ms淡出
    is_playing[key] = False

就这样我们把计算机变成了一台钢琴!至此,让我为您表演一段土耳其进行曲来表达对您耐心阅读此文的谢意吧:

[youku id="XNzM1NDQ1MDA4"]

如果想自己试试的话,在这里可以下载你需要的所有文件。因为不是所有的人都用Python,我也用Javascript/HTML5(在这儿)实现了一台电脑钢琴,但是不是特别理想。如果有经验丰富的HTML5/JS/elm程序员来改进改进,或者从头重写就太好了。
接下来做什么?

更通常的情况下,我发现计算机很少被用来进行表演性质的演奏。我明白使用钢琴键盘或者直接从乐器录音会容易很多,但是请看看仅仅用一个碗和60行的Python代码就能做到什么!

即便是很便宜的计算机也有如此多的控制来实现一个马马虎虎的音乐台:你可以对着麦克风唱歌,对着摄像头做手势,用鼠标来调制,然后用键盘来完成剩下来的玩意儿。有如此多方式来表现自我,而每种方式又有那么一个Python包……有没有具有艺术天赋的大神加入呀?

Python 相关文章推荐
Python实现基于权重的随机数2种方法
Apr 28 Python
python爬虫实例详解
Jun 19 Python
基于Python在MacOS上安装robotframework-ride
Dec 28 Python
django如何实现视图重定向
Jul 24 Python
Python 3.8正式发布,来尝鲜这些新特性吧
Oct 15 Python
win7下 python3.6 安装opencv 和 opencv-contrib-python解决 cv2.xfeatures2d.SIFT_create() 的问题
Oct 24 Python
python主线程与子线程的结束顺序实例解析
Dec 17 Python
Pycharm最新激活码2019(推荐)
Dec 31 Python
使用Tensorboard工具查看Loss损失率
Feb 15 Python
Python figure参数及subplot子图绘制代码
Apr 18 Python
Appium+Python实现简单的自动化登录测试的实现
Jan 26 Python
pytorch中Schedule与warmup_steps的用法说明
May 24 Python
仅利用30行Python代码来展示X算法
Apr 01 #Python
探究数组排序提升Python程序的循环的运行效率的原因
Apr 01 #Python
用Python编写分析Python程序性能的工具的教程
Apr 01 #Python
对Python新手编程过程中如何规避一些常见问题的建议
Apr 01 #Python
利用Django框架中select_related和prefetch_related函数对数据库查询优化
Apr 01 #Python
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
Apr 01 #Python
Python的Django框架中的select_related函数对QuerySet 查询的优化
Apr 01 #Python
You might like
php中设置index.php文件为只读的方法
2013/02/06 PHP
PHP 使用redis简单示例分享
2015/03/05 PHP
php简单检测404页面的方法示例
2019/08/23 PHP
jquery插件制作 自增长输入框实现代码
2012/08/17 jQuery
javaScript矢量图表库-gRaphael几行代码实现精美的条形图/饼图/点图/曲线图
2013/01/09 Javascript
js实现弹窗插件功能实例代码分享
2013/12/12 Javascript
javascript事件冒泡详解和捕获、阻止方法
2014/04/12 Javascript
详谈LABJS按需动态加载js文件
2015/05/07 Javascript
js实现tab切换效果实例
2015/09/16 Javascript
基于Javascript实现弹出页面效果
2016/01/01 Javascript
微信小程序入门教程
2016/11/18 Javascript
原生js实现回复评论功能
2017/01/18 Javascript
基于Bootstrap分页的实例讲解(必看篇)
2017/07/04 Javascript
JS基于正则表达式实现的密码强度验证功能示例
2017/09/21 Javascript
前端axios下载excel文件(二进制)的处理方法
2018/07/31 Javascript
vue下history模式刷新后404错误解决方法
2018/08/18 Javascript
对类Vue的MVVM前端库的实现代码
2018/09/07 Javascript
JavaScript设计模式之享元模式实例详解
2019/01/17 Javascript
VUE 实现复制内容到剪贴板的两种方法
2019/04/24 Javascript
python实现的解析crontab配置文件代码
2014/06/30 Python
浅谈python装饰器探究与参数的领取
2017/12/01 Python
CSS3中Color的一些特性介绍
2012/05/27 HTML / CSS
Html5移动端适配IphoneX等机型的方法
2019/06/25 HTML / CSS
Canvas波浪花环的示例代码
2020/08/21 HTML / CSS
Eclipse面试题
2014/03/22 面试题
YII2 全局异常处理深入讲解
2021/03/24 PHP
后勤岗位职责
2013/11/26 职场文书
群众路线批评与自我批评
2014/02/06 职场文书
单位一把手群众路线四风问题整改措施
2014/09/25 职场文书
教师个人年终总结
2015/02/11 职场文书
2015入党自荐书范文
2015/03/05 职场文书
2015年小学语文教学工作总结
2015/05/25 职场文书
2015年办税服务厅工作总结
2015/07/23 职场文书
中学政教处工作总结
2015/08/13 职场文书
使用Python解决图表与画布的间距问题
2022/04/11 Python
Go gorilla/sessions库安装使用
2022/08/14 Golang