在Linux下玩转MIDI

曾经我非常热衷于MIDI音乐,原因是一直没有机会正儿八经的学习一种我喜欢的乐器,于是在电脑上用HappyEO和Cakewalk制作一些音乐(或者说噪音?)便成为了生活中的一个重要部分。但是最近相当长的一段时间中,我都没有再玩过MIDI音乐,原因只不过是因为我从台式机换成了用笔记本电脑和开始使用Linux……

先科普一下有关MIDI音乐的简单知识:MIDI音乐文件中存放的是音乐的乐谱信息,而不是实际的声音。乐谱信息以一系列的消息组成,比如:用最大力度的50%按下钢琴上小字一组的C键、持续1秒、松开小字一组的C键。电脑在播放MIDI文件时,需要由音源设备把MIDI消息转换成真正声音,再播放出来,音源设备的好坏就直接决定了MIDI音乐的回放质量。

在Linux中玩MIDI,本身也不该是件什么难事,但问题是在于Linux的声音系统一直以来处于非常混乱的状态,各家争鸣百花齐放,使得配置起来很是复杂。另外,现在很多电脑的的声卡上都不带音源设备,这样就没有办法直接用声卡合成声音。

要在Linux下玩转MIDI,就得从这以下方面入手:

驱动层:早期Linux用OSS来驱动声卡,而现在这一块基本上已经由ALSA来一统天下,当然ALSA也提供了OSS的兼容层来保证一些古老的应用。这年头,电脑装好Linux后能正常发声的话,ALSA应该就已经是在正常工作了。

中间层:一些新的发行版都用PulseAudio做为一个应用中间层,通过它去统一应用软件与ALSA的接口。不过这个东西对于玩MIDI这种“专业”的事情来说,不是很有用。我们需要的是Jack。Jack是大部分音频处理软件所使用的输入输出组件,它具有高性能低延迟等优秀的特质。

音源:如果声卡没有硬件的音源,或者硬件音源烂到只能发出电子味很重的声音。我们就需要一个软件音源,通过加载音色库(SoundFont)文件,把MIDI信号合成为声音。Linux下常用的软音源有fluidsynth或Timidity++。另外还需要准备合适的音色库文件。

音序器:简单的说就是用来作曲的软件啦,由于把各种MIDI信号组合到一起形成一个音乐作品。上面说的Cakewalk就是一款Windows下的古董级的音序器了。在Linux下,最有名的当属Rosegarden了。

最终的工作流程就是用Rosegarden谱曲,在fluidsynth或Timidity++中加载好音色库文件,用Jack把Rosegarden的General MIDI Device输出端口连接到fluidsynth或Timidity++生成的Synth输入端口。

以Debian Squeeze为例,在ALSA工作正常的前提下,安装以下软件包及其依赖包:jackd, qjackctl(Jack的图形前端), qsynth(fluidsynth的图形前端), rosegarden, fluid-soundfont-gm (一个音色库文件)。

sudo aptitude install jackd qjackctl qsynth rosegarden fluid-soundfont-gm

安装完后就可以启动JACK Control和Qsynth,首先点Qsynth界面上的Setup按钮,在Soundfonts页中指定要用的音色库文件,如:/usr/share/sounds/sf2/FluidR3_GM.sf2,重启音源引擎。然后可以打开Rosegarden,这时在JACK Control的Connect按钮对应的窗口的ALSA页中会可以看到Rosegarden的输出端口和Qsynth的输出端口,选中它们点“Connect”把它们连起来。同时确认一下在"Audio“页中,Qsynth的输出端口也已经与”system“的playback端口连接正常。

Qsynth设置

Qsynth设置

JACK Control ALSA设置

JACK Control ALSA设置

JACK Control Audio设置

JACK Control Audio设置

大功告成,在Rosegarden中打开一个MIDI文件,或者自己涂鸦几个音符吧,点播放以后就应该可以听到MIDI音乐声响起了。

Rosegarden界面

Rosegarden界面

除了Rosegarden,Linux中也有其它一些音序器软件,比如tuxguitar,就是专门为吉他爱好者准备的。

HappyEO是一款在Windows下用电脑键盘模拟电子琴的软件,也能当成音序器的MIDI信号输入输出设备。这个软件在2000年到2006年间占用了我无数的时间,我乐在其中的用它制造了无数的“噪音”,直到我换了笔记本电脑(没有了标准键盘)和开始用Linux。在把Linux下的MIDI环境配置好后我就迫不急待的用wine尝试运行这个软件,软件运行成功了!看到久违的界面,听着自己已经不再熟练的弹奏,还真是颇有一点激动呢。

在Debian下用wine运行的HappyEO

在Debian下用wine运行的HappyEO

标准的Firefox

某个我常去的论坛上,时不时会出现贴子布局混乱的问题,如下图(因为原贴太长,所以PS过以减小图片面积):

混乱的网页布局

混乱的网页布局

很明显的,左上角出现的“–>”很好的暗示了我们,HTML中有点乱套了。而且这也常常是网站系统可能存在XSS漏洞的最初现象之一,我不邪恶,不过担心别人会邪恶,所以打算把问题原因找出来,告诉网站管理员。

长期的实践中,我曾对这个问题总结过一个规律:这个问题只在Firefox中出现,并且会乱掉的贴子多半是因为某一个特定ID的人参与了讨论,开始混乱的位置常常是在那个ID的人的签名档往上一点的位置。

今天为了分析解决这个问题,你会怎么推理?我的推理是:由于签名档允许部分HTML,一些不规范的HTML标签导致了页面混乱。

于是去查看HTML源文件,原始文件太长,所以这里就示意一下:

<div>
<!-- Blah blah blah <br />
-------------- <br />
Foo bar Foo bar
-->
Blah blah blah <br />
-------------- <br />
Foo bar Foo bar
</div>
<div style="maxHeightIE: 160px;">
My signautre. My signautre. My signautre. My signautre. My signautre.
</div>

是的,推理错了。显而易见的,签名档中并没有问题,而且HTML代码似乎也没有什么异常的地方。把这段HTML放到一个新的空白网页中,显示也一切正常,没有发生混乱。

也许是网页中有一些不可见的字符,而且复制粘贴后就没有了?Emacs帮我证明了不可见字符这一点,这个网页在我的Linux平台上是以LF字 符做为换行的,但那个贴子内容中的换行符却是Windows中的CR LF。于是我把CR字符都删掉,问题依旧。难道还有更神秘的不可见字符在搞鬼?用二进制文件编辑器查看这个文件,一切正常。

再分析一下,在网页上出现混乱的起始点是–>符号,那看来是在那段注释中就已经出现问题了。用Firefox自带的查看源代码的功能,可以看到,它只把注释的前两行识别成为注释,标记成绿色,从Foo bar那行开始,就变成正常的HTML标签的颜色了。

为什么Firefox会错误的识别HTML注释标签的结尾?难道因为这个文件编码有问题(原始文件中是有中文的,例子中只写了英文字符)?(大家还记得Windows记事本对待联通和移动的不同态度吧) 把网页由GBK编码重存为UTF-8编码,问题依然。

没招了,借助神奇的Google,换了好几次关键词之后,终于用“Firefox comment bug”这个关键词找到了有用的信息

简单的说,HTML4规 范中,注释的起始标签是<!,注释的起始和结束符号是–,注释的结束标签是>。当然,这是一个很严格的形式化的定义,但这跟我们通常想象中 的注释由<!–开始,以–>结束是不一样的。因为HTML4规定,结束符号–和结束标签>间可以有空格隔开,也就是说,并不要求 –和>连在一起写才表示注释结束。规范建议在注释中不要出现两个或以上“-”出现的情况。

规范中没有说结束符号–和结束标签>之间除了空格以外可不可以有别的字符出现,Firefox很激进把它定义为允许出现,所以就带来了我们所看到的问题。

事情还没完,那为什么把这段代码单独贴成一个HTML文件测试就没有问题呢?因为没有指定DOCTYPE时,Firefox不会按HTML4的规范去解析。如果你在测试文件开头加上HTML4的DOCTYPE就可以重现这个问题了:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

还没完,那为什么这个问问往往出现在某个特定ID参与过的贴子上呢?因为那个人不喜欢用论坛的“引用”功能,他喜欢复制前一个人说过的话,用———隔开,下面写回复。或者就是用———来分隔他的贴子正文的几段内容。

为什么那个BBS系统中要把贴子的内容输出两遍,其中一遍是在注释中呢?不知道。有一种可能性是开发者还不熟悉网页后台系统的开发,他总是习惯用HTML中的注释语法去注释后台他不想要的代码。这种做法基本上是错误的,但犯这样错误的人绝不在少数。

继续,虽然Firefox这样做没有违背规范,但多少有点违背常识,为什么它不修复这个问题呢?这是因为它要兼顾SGML的解析——在那里“–”有着很重要功能。不过由于HTML5中对注释的语法做了更严密的规定,Mozilla说,他们准备在后续的版本中修复这个问题。

最后,Firefox这个问题会不会带来原本可能不存在的XSS攻击?应该说可能性是有的,但主要问题还是会出在网站开发者的身上。比如你在后台调试时,输出了未转意的用户输入内容,并用HTML的注释注释掉了,你以为是安全的,但由于这个问题的存在,带来了XSS攻击的可能性。

这个故事还告诉我们一些道理,不写了。因为我已经把分析/走弯路的过程写得很详细了,那些道理都是简单易见好理解的,只是常常我们做得不太好。

ssh-keygen这个扯蛋的网站

无意中看到www.sshkeygen.com这个网站(故意不做成超链),先感叹了一下现在真是什么事情都可以放到“云端”完成,然后就意识到这里面的问题了。

ssh-keygen是*nix系统中用于生成SSH登录用的密钥对的命令,很多公共的服务器出于安全考虑都要求用密钥对代替密码来登录系统。然而这种方式对于很多人来说是比较陌生的,如果想借助于搜索引擎来解决这个问题,就很可能上了这个网站的当。在线生成密钥对,这就意味着你的密钥已经被这个第三方的“在线”网站所知晓了,私钥泄露,那还有什么“密”可言呢?

仔细看看这个网站上的遣词造句,还颇有一点专业的味道。不过它故意让你多输入了用户名、单位、域名、IP这些生成SSH密钥对根本不需要的信息,显然是别有用心啊。如果再看看它的About的页面,有些看似友好的TO-DO功能改进,实际上更具险恶用心,比如这个:implement key installation via web interface。

简单查了一下,现在好像还没有在线生成GPG密钥对的钓鱼网站,估计因为这个相对SSH密钥来说比较无利可图吧。puttygen(Windows下一个常用的用于生成SSH密钥对的软件)相关的域名也还没有人注册,这个想上去应该比sshkeygen对小白们更有效果啊,有识之士应该抓紧去注册,做一个在线的puttygen系统……

做为一个Best Practice,永远只在本机生成密钥对,并充分保管好自己的私钥。如果非要在公共主机上使用密钥对,一定得给私钥加上一个强劲的Passphrase。把私钥文件设成0600的权限是必须的,但是是没有用的,因为每台服务器的背后都有一个名叫root的“邪恶”帐号。

查询火车票余额的脚本

由于12306网站经历多次更新,并加入了验证码机制,现本文内容已经完全过时。

又到一年春运时,铁道部的www.12306.cn也终于正式上线了,这个网站的最大亮点在于可以查询火票余票票额。根据之前未非式开放时大家测试的结果,虽然查询结果不是实时的,但这个网站的数据还是有比较强的参考价值的。

随手涂鸦了一个BASH脚本,用于在Linux命令行下通过这个网站查询火车票票额,放在GitHub上。有兴趣的朋友可以拿来用用。免费版的GitHub是一个公共GIT仓库,所以也欢迎任何人继续涂鸦和改进这个脚本(当前的代码Just works,但很smelly)。

脚本的代码可以在这里直接下载:http://github.com/lifanxi/train-query.git/train.sh

简要的使用说明在:http://github.com/lifanxi/train-query.git/README

其实核心代码只有一句:

curl http://dynamic.12306.cn/TrainQuery/iframeLeftTicketByStation.jsp
-d lx=00 -d nyear3=$YEAR -d nyear3_new_value=true -d nmonth3=$MON
-d nmonthe3_new_value=true -d nday3=$DAY
--data-urlencode startStation_ticketLeft=$START
--data-urlencode arriveStation_ticketLeft=$TO
-d nday3_new_value=true
-d startStation_ticketLeft_new_value=true
-d arriveStation_ticketLeft_new_value=true -d trainCode=$TRAINCODE
-d trainCode_new_value=true -d rFlag=1 -d name_ckball=value_ckball
-d tFlagT=T -d tFlagZ=Z -d tFlagDC=DC -d tFlagK=K -d tFlagPK=PK
-d tFlagQT=QT -m 10 2>/dev/null |
grep -v &quot;//&quot; |
grep addRow > $TMPFILE

试图把脚本放到Windows上可用,试了一下win-bash,不行,算了,Windows下喜欢脚本的人应该不多。实在要用的,估计用Cygwin可以。

加速Linux程序编译

项目越来越大,每次需要重新编译整个项目都是一件很浪费时间的事情。Research了一下,找到以下可以帮助提高速度的方法,总结一下。

  • 1. tmpfs

有人说在Windows下用了RAMDisk把一个项目编译时间从4.5小时减少到了5分钟,也许这个数字是有点夸张了,不过粗想想,把文件放到内存上做编译应该是比在磁盘上快多了吧,尤其如果编译器需要生成很多临时文件的话。

这个做法的实现成本最低,在Linux中,直接mount一个tmpfs就可以了。而且对所编译的工程没有任何要求,也不用改动编译环境。

mount -t tmpfs tmpfs ~/build -o size=1G

用2.6.32.2的Linux Kernel来测试一下编译速度:

用物理磁盘:40分16秒

用tmpfs:39分56秒

呃……没什么变化。看来编译慢很大程度上瓶颈并不在IO上面。但对于一个实际项目来说,编译过程中可能还会有打包等IO密集的操作,所以只要可能,用tmpfs是有益无害的。当然对于大项目来说,你需要有足够的内存才能负担得起这个tmpfs的开销。

  • make -j

既然IO不是瓶颈,那CPU就应该是一个影响编译速度的重要因素了。

用make -j带一个参数,可以把项目在进行并行编译,比如在一台双核的机器上,完全可以用make -j4,让make最多允许4个编译命令同时执行,这样可以更有效的利用CPU资源。

还是用Kernel来测试:

用make: 40分16秒

用make -j4:23分16秒

用make -j8:22分59秒

由此看来,在多核CPU上,适当的进行并行编译还是可以明显提高编译速度的。但并行的任务不宜太多,一般是以CPU的核心数目的两倍为宜。

不过这个方案不是完全没有cost的,如果项目的Makefile不规范,没有正确的设置好依赖关系,并行编译的结果就是编译不能正常进行。如果依赖关系设置过于保守,则可能本身编译的可并行度就下降了,也不能取得最佳的效果。

  • ccache

ccache用于把编译的中间结果进行缓存,以便在再次编译的时候可以节省时间。这对于玩Kernel来说实在是再好不过了,因为经常需要修改一些Kernel的代码,然后再重新编译,而这两次编译大部分东西可能都没有发生变化。对于平时开发项目来说,也是一样。为什么不是直接用make所支持的增量编译呢?还是因为现实中,因为Makefile的不规范,很可能这种“聪明”的方案根本不能正常工作,只有每次make clean再make才行。

安装完ccache后,可以在/usr/local/bin下建立gcc,g++,c++,cc的symbolic link,链到/usr/bin/ccache上。总之确认系统在调用gcc等命令时会调用到ccache就可以了(通常情况下/usr/local/bin会在PATH中排在/usr/bin前面)。

继续测试:

用ccache的第一次编译(make -j4):23分38秒

用ccache的第二次编译(make -j4):8分48秒

用ccache的第三次编译(修改若干配置,make -j4):23分48秒

看来修改配置(我改了CPU类型…)对ccache的影响是很大的,因为基本头文件发生变化后,就导致所有缓存数据都无效了,必须重头来做。但如果只是修改一些.c文件的代码,ccache的效果还是相当明显的。而且使用ccache对项目没有特别的依赖,布署成本很低,这在日常工作中很实用。

可以用ccache -s来查看cache的使用和命中情况:

cache directory                   /home/lifanxi/.ccache
cache hit                           7165
cache miss                         14283
called for link                       71
not a C/C++ file                     120
no input file                       3045
files in cache                     28566
cache size                          81.7 Mbytes
max cache size                     976.6 Mbytes

可以看到,显然只有第二编次译时cache命中了,cache miss是第一次和第三次编译带来的。两次cache占用了81.7M的磁盘,还是完全可以接受的。

  • distcc

一台机器的能力有限,可以联合多台电脑一起来编译。这在公司的日常开发中也是可行的,因为可能每个开发人员都有自己的开发编译环境,它们的编译器版本一般是一致的,公司的网络也通常具有较好的性能。这时就是distcc大显身手的时候了。

使用distcc,并不像想象中那样要求每台电脑都具有完全一致的环境,它只要求源代码可以用make -j并行编译,并且参与分布式编译的电脑系统中具有相同的编译器。因为它的原理只是把预处理好的源文件分发到多台计算机上,预处理、编译后的目标文件的链接和其它除编译以外的工作仍然是在发起编译的主控电脑上完成,所以只要求发起编译的那台机器具备一套完整的编译环境就可以了。

distcc安装后,可以启动一下它的服务:

/usr/bin/distccd --daemon --allow 10.64.0.0/16

默认的3632端口允许来自同一个网络的distcc连接。

然后设置一下DISTCC_HOSTS环境变量,设置可以参与编译的机器列表。通常localhost也参与编译,但如果可以参与编译的机器很多,则可以把localhost从这个列表中去掉,这样本机就完全只是进行预处理、分发和链接了,编译都在别的机器上完成。因为机器很多时,localhost的处理负担很重,所以它就不再“兼职”编译了。

export DISTCC_HOSTS=&quot;localhost 10.64.25.1 10.64.25.2 10.64.25.3&quot;

然后与ccache类似把g++,gcc等常用的命令链接到/usr/bin/distcc上就可以了。

在make的时候,也必须用-j参数,一般是参数可以用所有参用编译的计算机CPU内核总数的两倍做为并行的任务数。

同样测试一下:

一台双核计算机,make -j4:23分16秒

两台双核计算机,make -j4:16分40秒

两台双核计算机,make -j8:15分49秒

跟最开始用一台双核时的23分钟相比,还是快了不少的。如果有更多的计算机加入,也可以得到更好的效果。

在编译过程中可以用distccmon-text来查看编译任务的分配情况。distcc也可以与ccache同时使用,通过设置一个环境变量就可以做到,非常方便。

总结一下:

  • tmpfs: 解决IO瓶颈,充分利用本机内存资源
  • make -j: 充分利用本机计算资源
  • distcc: 利用多台计算机资源
  • ccache: 减少重复编译相同代码的时间

这些工具的好处都在于布署的成本相对较低,综合利用这些工具,就可以轻轻松松的节省相当可观的时间。上面介绍的都是这些工具最基本的用法,更多的用法可以参考它们各自的man page。