使用Python标准库中的wave模块绘制乐谱的简单教程


Posted in Python onMarch 30, 2015

在本文中,我们将探讨一种简洁的方式,以此来可视化你的MP3音乐收藏。此方法最终的结果将是一个映射你所有歌曲的正六边形网格地图,其中相似的音轨将处于相邻的位置。不同区域的颜色对应不同的音乐流派(例如:古典、嘻哈、重摇滚)。举个例子来说,下面是我所收藏音乐中三张专辑的映射图:Paganini的《Violin Caprices》、Eminem的《The Eminem Show》和Coldplay的《X&Y》。

使用Python标准库中的wave模块绘制乐谱的简单教程

为了让它更加有趣(在某些情况下更简单),我强加了一些限制。首先,解决方案应该不依赖于MP3文件中任何已有的ID3标签(例如,Arist,Genre),应该仅仅使用声音的统计特性来计算歌曲的相似性。无论如何,很多我的MP3文件标记都很糟糕,但我想使得该解决方案适用于任何音乐收藏文件,不管它们的元数据是多么糟糕。第二,不应使用其他外部信息来创建可视化图像,需要输入的仅仅是用户的MP3文件集。其实,通过利用一个已经被标记为特定流派的大型歌曲数据库,就能提高解决方案的有效性,但是为了简单起见,我想保持这个解决方案完全的独立性。最后,虽然数字音乐有很多种格式(MP3、WMA、M4A、OGG等),但为了使其简单化,这里我仅仅关注MP3文件。其实,本文开发的算法针对其他格式的音频也能很好地工作,只要这种格式的音频可以转换为WAV格式文件。

创建音乐图谱是一个很有趣的练习,它包含了音频处理、机器学习和可视化技术。基本步骤如下所示:

    转换MP3文件为低比特率WAV文件。
    从WAV元数据中提取统计特征。
    找到这些特征的一个最佳子集,使得在这个特征空间中相邻的歌曲人耳听起来也相似。
    为了在一个XY二维平面上绘图,使用降维技术将特征向量映射到二维空间。
    生成一个由点组成的六角网格,然后使用最近邻技术将XY平面上的每一首歌曲映射六角网格上的一个点。
    回到原始的高维特征空间,将歌曲聚类到用户定义数量的群组中(k=10能够很好地实现可视化目的)。对于每个群组,找到最接近群组中心的歌曲。
    在六角网格上,使用不同的颜色对k个群组中心的那首歌曲着色。
    根据其他歌曲在XY屏幕上到每个群组中心的距离,对它们插入不同的颜色。

下面,让我们共同看看其中一些步骤的详细信息。
MP3文件转换成WAV格式

将我们的音乐文件转换成WAV格式的主要优势是我们可以使用Python标准库中的“wave”模块很容易地读入数据,便于后面使用NumPy对数据进行操作。此外,我们还会以单声道10kHz的采样率对声音文件下采样,以使得提取统计特征的计算复杂度有所降低。为了处理转换和下采样,我使用了众所周知的MPG123,这是一个免费的命令行MP3播放器,在Python中可以很容易调用它。下面的代码对一个音乐文件夹进行递归搜索以找到所有的MP3文件,然后调用MPG123将它们转换为临时的10kHz WAV文件。然后,对这些WAV文件进行特征计算(下节中讨论)。
 

import subprocess
import wave
import struct
import numpy
import csv
import sys
 
def read_wav(wav_file):
 """Returns two chunks of sound data from wave file."""
 w = wave.open(wav_file)
 n = 60 * 10000
 if w.getnframes() < n * 2:
  raise ValueError('Wave file too short')
 frames = w.readframes(n)
 wav_data1 = struct.unpack('%dh' % n, frames)
 frames = w.readframes(n)
 wav_data2 = struct.unpack('%dh' % n, frames)
 return wav_data1, wav_data2
 
def compute_chunk_features(mp3_file):
 """Return feature vectors for two chunks of an MP3 file."""
 # Extract MP3 file to a mono, 10kHz WAV file
 mpg123_command = '..mpg123-1.12.3-x86-64mpg123.exe -w "%s" -r 10000 -m "%s"'
 out_file = 'temp.wav'
 cmd = mpg123_command % (out_file, mp3_file)
 temp = subprocess.call(cmd)
 # Read in chunks of data from WAV file
 wav_data1, wav_data2 = read_wav(out_file)
 # We'll cover how the features are computed in the next section!
 return features(wav_data1), features(wav_data2)
 
# Main script starts here
# =======================
 
for path, dirs, files in os.walk('C:/Users/Christian/Music/'):
 for f in files:
  if not f.endswith('.mp3'):
   # Skip any non-MP3 files
   continue
  mp3_file = os.path.join(path, f)
  # Extract the track name (i.e. the file name) plus the names
  # of the two preceding directories. This will be useful
  # later for plotting.
  tail, track = os.path.split(mp3_file)
  tail, dir1 = os.path.split(tail)
  tail, dir2 = os.path.split(tail)
  # Compute features. feature_vec1 and feature_vec2 are lists of floating
  # point numbers representing the statistical features we have extracted
  # from the raw sound data.
  try:
   feature_vec1, feature_vec2 = compute_chunk_features(mp3_file)
  except:
   continue

特征提取

在Python中,一个单声道10kHz的波形文件表示为一个范围为-254到255的整数列表,每秒声音包含10000个整数。每个整数代表歌曲在对应时间点上的相对幅度。我们将分别从两首歌曲中分别提取一段时长60秒的片段,所以每个片段将由600000个整数表示。上面代码中的函数“read_wav”返回了这些整数列表。下面是从Eminem的《The Eminem Show》中一些歌曲中提取的10秒声音波形图:

使用Python标准库中的wave模块绘制乐谱的简单教程

为了对比,下面是Paganini的《Violin Caprices》中的一些片段波形图:

使用Python标准库中的wave模块绘制乐谱的简单教程

从上面两个图中可以看出,这些片段的波形结构差别很明显,但一般来看Eminem的歌曲波形图看起来都有些相似,《Violin Caprices》的歌曲也是这样。接下来,我们将从这些波形图中提取一些统计特征,这些特征将捕捉到歌曲之间的差异,然后通过这些歌曲听起来的相似性,我们使用机器学习技术将它们分组。

我们将要提取的第一组特征集是波形的统计矩(均值、标准差、偏态和峰态)。除了对幅度进行这些计算,我们还将对递增平滑后的幅度进行计算来获取不同时间尺度的音乐特性。我使用了长度分别为1、10、100和1000个样点的平滑窗,当然可能其他的值也能取得很好的结果。

分别利用上面所有大小的平滑窗对幅度进行相应计算。为了获取信号的短时变化量,我还计算了一阶差分幅度(平滑过的)的统计特性。

上面的特征在时间域给出了一个相当全面的波形统计总结,但是计算一些频率域的特征也是有帮助的。像嘻哈这种重低音音乐在低频部分有更多的能量,而经典音乐在高频部分占有更多的比例。

将这些特征放在一起,我们就得到了每首歌曲的42种不同特征。下面的Python代码从一系列幅度值计算了这些特征:

def moments(x):
 mean = x.mean()
 std = x.var()**0.5
 skewness = ((x - mean)**3).mean() / std**3
 kurtosis = ((x - mean)**4).mean() / std**4
 return [mean, std, skewness, kurtosis]
 
def fftfeatures(wavdata):
 f = numpy.fft.fft(wavdata)
 f = f[2:(f.size / 2 + 1)]
 f = abs(f)
 total_power = f.sum()
 f = numpy.array_split(f, 10)
 return [e.sum() / total_power for e in f]
 
def features(x):
 x = numpy.array(x)
 f = []
 
 xs = x
 diff = xs[1:] - xs[:-1]
 f.extend(moments(xs))
 f.extend(moments(diff))
 
 xs = x.reshape(-1, 10).mean(1)
 diff = xs[1:] - xs[:-1]
 f.extend(moments(xs))
 f.extend(moments(diff))
 
 xs = x.reshape(-1, 100).mean(1)
 diff = xs[1:] - xs[:-1]
 f.extend(moments(xs))
 f.extend(moments(diff))
 
 xs = x.reshape(-1, 1000).mean(1)
 diff = xs[1:] - xs[:-1]
 f.extend(moments(xs))
 f.extend(moments(diff))
 
 f.extend(fftfeatures(x))
 return f
 
# f will be a list of 42 floating point features with the following
# names:
 
# amp1mean
# amp1std
# amp1skew
# amp1kurt
# amp1dmean
# amp1dstd
# amp1dskew
# amp1dkurt
# amp10mean
# amp10std
# amp10skew
# amp10kurt
# amp10dmean
# amp10dstd
# amp10dskew
# amp10dkurt
# amp100mean
# amp100std
# amp100skew
# amp100kurt
# amp100dmean
# amp100dstd
# amp100dskew
# amp100dkurt
# amp1000mean
# amp1000std
# amp1000skew
# amp1000kurt
# amp1000dmean
# amp1000dstd
# amp1000dskew
# amp1000dkurt
# power1
# power2
# power3
# power4
# power5
# power6
# power7
# power8
# power9
# power10

选择一个最优的特征子集

我们已经计算了42种不同的特种,但是并不是所有特征都有助于判断两首歌曲听起来是否相同。下一步就是找到这些特征的一个最优子集,以便在这个减小的特征空间中两个特征向量之间的欧几里得距离能够很好地对应两首歌听起来的相似性。

变量选择的过程是一个有监督的机器学习问题,所以我们需要一些训练数据集合,这些训练集能够引导算法找到最好的变量子集。我并非通过手动处理音乐集并标记哪些歌曲听起来相似来创建算法的训练集,而是使用了一个更简单的方法:从每首歌曲中提取两段时长为1分钟的样本,然后试图找到一个最能匹配同一首歌曲中的两个片段的算法。

为了找到针对所有歌曲能够达到最好平均匹配度的特征集,我使用了一个遗传算法(在R语言的genalg包中)对42个变量中的每一个进行选取。下图显示了经过遗传算法的100次迭代,目标函数的改进情况(例如,一首歌的两个样本片段通过最近邻分类器来匹配到底有多么稳定)。

使用Python标准库中的wave模块绘制乐谱的简单教程

如果我们强制距离函数使用所有的42个特征,那么目标函数的值将变为275。而通过正确地使用遗传算法来选取特征变量,我们已经将目标函数(例如,错误率)减小到了90,这是一个非常重大的改进。最后选取的最优特征集包括:

    amp10mean
    amp10std
    amp10skew
    amp10dstd
    amp10dskew
    amp10dkurt
    amp100mean
    amp100std
    amp100dstd
    amp1000mean
    power2
    power3
    power4
    power5
    power6
    power7
    power8
    power9

在二维空间可视化数据

我们最优的特征集使用了18个特征变量来比较歌曲的相似性,但是我们想最终在2维平面上可视化音乐集合,所以我们需要将这个18维的空间降到2维,以便于我们绘画。为了实现这个目的,我简单地使用了前两个主成分来作为X和Y坐标。当然,这会引入一些错误到可视化图中,可能会造成一些在18维空间中相近的歌曲在2维平面中却不再相近。不过,这些错误无可避免,但幸好它们不会将这种关系扭曲得太厉害—听起来相似的歌曲在2维平面上仍然会大致集聚在一起。
将点映射到一个六角网格

从主成分中生成的2D点在平面上不规则地分布。虽然这个不规则的分布描述了18维特征向量在2维平面上最“准确”的布置,但我还是想通过牺牲一些准确率来将它们映射到一个很酷的画面上,即一个有规律间隔的六角网格。通过以下操作实现:

    将xy平面的点嵌入到一个更大的六角网格点阵中。
    从六角形最外层的点开始,将最近的不规则间隔的主成分点分配给每个六角网格点。
    延伸2D平面的点,使它们完全填充六角网格,组成一个引人注目的图。

使用Python标准库中的wave模块绘制乐谱的简单教程

为图上色

这个练习的一个主要目的是不对音乐集的内容做任何假设。这意味着我不想将预定义的颜色分配给特定的音乐流派。相反,我在18维空间中聚合特征向量以找到聚集听起来相似的音乐的容器,并将颜色分配给这些群组中心。结果是一个自适应着色算法,它会找出你所要求的尽可能多的细节(因为用户可以定义群组的数量,也即是颜色数量)。正如前面提到的,我发现使用k=10的群组数量往往会给出好的结果。
最终输出

为了娱乐,这里给出我音乐集中3668首歌曲的可视化图。全分辨率图片可以从这里获得。如果你放大图片,你将会看到算法工作的相当好:着色的区域对应着相同音乐流派的音轨,并且经常是相同的艺术家,正如我们希望的那样。

使用Python标准库中的wave模块绘制乐谱的简单教程

Python 相关文章推荐
python3使用urllib模块制作网络爬虫
Apr 08 Python
Python基于回溯法子集树模板解决最佳作业调度问题示例
Sep 08 Python
Python设计模式之MVC模式简单示例
Jan 10 Python
Python实现接受任意个数参数的函数方法
Apr 21 Python
使用Django开发简单接口实现文章增删改查
May 09 Python
opencv resize图片为正方形尺寸的实现方法
Dec 26 Python
python opencv 实现对图像边缘扩充
Jan 19 Python
Python递归函数特点及原理解析
Mar 04 Python
Python内建序列通用操作6种实现方法
Mar 26 Python
解决python运行效率不高的问题
Jul 20 Python
pycharm 如何取消连按两下shift出现的全局搜索
Jan 15 Python
我对PyTorch dataloader里的shuffle=True的理解
May 20 Python
Python中使用语句导入模块或包的机制研究
Mar 30 #Python
优化Python代码使其加快作用域内的查找
Mar 30 #Python
Python中分数的相关使用教程
Mar 30 #Python
Python2.x中str与unicode相关问题的解决方法
Mar 30 #Python
分享一个常用的Python模拟登陆类
Mar 29 #Python
python实现查询IP地址所在地
Mar 29 #Python
python实现定时播放mp3
Mar 29 #Python
You might like
curl实现站外采集的方法和技巧
2014/01/31 PHP
PHP swoole和redis异步任务实现方法分析
2019/08/12 PHP
关于捕获用户何时点击window.onbeforeunload的取消事件
2011/03/06 Javascript
用jquery生成二级菜单的实例代码
2013/06/24 Javascript
createTextRange()的使用示例含文本框选中部分文字内容
2014/02/24 Javascript
js之ActiveX控件使用说明 new ActiveXObject()
2014/03/03 Javascript
jQuery中offset()方法用法实例
2015/01/16 Javascript
JavaScript实现简单的二级导航菜单实例
2015/04/15 Javascript
jquery实现点击label的同时触发文本框点击事件的方法
2015/06/05 Javascript
浅析JS运动
2015/12/28 Javascript
JS实现pasteHTML兼容ie,firefox,chrome的方法
2016/06/22 Javascript
js实现文字选中分享功能
2017/01/25 Javascript
vue父组件给子组件的组件传值provide inject的方法
2019/10/23 Javascript
Vue element-ui父组件控制子组件的表单校验操作
2020/07/17 Javascript
vue-cli脚手架的.babelrc文件用法说明
2020/09/11 Javascript
echarts柱状图背景重叠组合而非并列的实现代码
2020/12/10 Javascript
[01:00:06]加油DOTA_EP01_网络版
2014/08/09 DOTA
python开发中range()函数用法实例分析
2015/11/12 Python
Python 正则表达式入门(中级篇)
2016/12/07 Python
python中获得当前目录和上级目录的实现方法
2017/10/12 Python
python 判断网络连通的实现方法
2018/04/22 Python
python绘制已知点的坐标的直线实例
2019/07/04 Python
Python实现的爬取豆瓣电影信息功能案例
2019/09/15 Python
Django 请求Request的具体使用方法
2019/11/11 Python
python 子类调用父类的构造函数实例
2020/03/12 Python
使用pth文件添加Python环境变量方式
2020/05/26 Python
mui几种页面跳转方式对比总结概括
2017/08/18 HTML / CSS
UGG美国官网:购买UGG雪地靴、拖鞋和鞋子
2017/12/31 全球购物
Under Armour安德玛法国官网:美国高端运动科技品牌
2018/06/29 全球购物
什么是测试驱动开发(TDD)
2012/02/15 面试题
技术总监个人的自我评价范文
2013/12/18 职场文书
《小松树和大松树》教学反思
2014/02/20 职场文书
寻找最美家庭活动方案
2014/08/20 职场文书
2015年秋季小学开学标语
2015/07/16 职场文书
用React Native制作一个简单的游戏引擎
2021/05/27 Javascript
volatile保证可见性及重排序方法
2022/08/05 Java/Android