View the source here: https://github.com/yarmyarch/Midi-in-javascript
Play the demo here: http://yarmyarch.github.io/Midi-in-javascript/demo.html
Source: http://yarmyarch.com/archives/141
Author: The Coding Rider(yarmyarch@live.cn), 2014-1-1
All rights reserved

目录

随便说点啥

  不想看废话的、查询GM标准音色表、MIDI信息状态表等资料的同僚们请直奔附录
  眼看2013就结束了,想着用啥来总结好呢。2013大半时间在路上,有空了小旅馆里开着网想一想,还是蛮有意思的一个东西。HTML5火爆了2013,图像、游戏、交互等等各个分支发展都很喜人。走马观花溜一遍,到了数字音乐领域,感觉有点像看春哥和曾哥对台唱歌一样从繁华喧闹的市集走到胡同脚里一片黯然收场。零零散散搜集到一些资料,算是给自己的2013一个交代。想做的事情越来越多,时间越来越少——希望自己2014年能在感兴趣的方面有一些收获。
  其实之前也跟一些朋友聊起过这个话题,让我比较诧异的是MIDI这东西在很多人眼里还一直是极客手中的玩物——也难怪,这应当归咎于数字音乐在互联网的缓步发展。回顾2013年,有许多令人尖叫的音乐相关作品问世,例如google的吉他doodle多人游戏plink等等无不令人啧啧称赞。
  社交化的数字音乐仍然一如既往拥有很大的市场,受限于技术门槛和不完善的标准支持,这方面国内国外都鲜有建树,即便桌面领域或者基于flash的应用已经相当成熟——这是后话了。
  受限于鄙人学识,以下内容部分纯属胡编滥造,如有误导烦请指正,或者你来打我啊。

虚拟MIDI环境简介

  传统MIDI一系列设备,没有几年功夫和一定的存款,要想搭建一套可以自己谱谱曲的环境,不用烂几块键盘没几个几十G的雅马哈音色库恐怕很难搞定。许多专业录音棚出产的电子乐甚至可以媲美中端音乐厅水平。当然吾等屌丝不需要那么豪华的阵容,硬盘加右手就够了。
  设想在一个只有浏览器的环境中创建可以实时播放的MIDI环境。最基本的,这套工具应该可以方便的让我这样不懂乐理的人也会点出声加点特效,钢琴二胡和隔壁王老坎家骡子拉磨的声音来个混响,最后还能允许我把这声音弄下来挂门上驱灾撵狗啥的。
  为了实现这个简单的目的,我们需要一些基本的道具,包括可以敲的输入设备,用于混响和播放声音的通道,能够管理通道信息和状态的合成器,装了各种声音的音色库(软波表),其中应当有我想要的骡子声乐器,一个正常的喇叭作为输出设备,最后把这些所有东西集合起来可以打包输出文件的——几年前我们一伙人管这叫——数字音乐模拟系统,至今我仍然认为这是个好名字。

MIDI

  MIDI实际上只是一个乐谱,只不过五线谱是给演奏家看的,MIDI是给合成器看的。MIDI文件中定义了在某一时刻用某种节奏的某个音量播放某种乐器,持续多长时间采用什么效果。除此之外还能记录诸如“幕布升起”、“扣钉骑士万岁万岁万万岁”等类似的文本信息和提示内容,提供给读乐谱的程序看。

输入设备

  不赘述,我们有键盘,上百个键位和控制键,完全够用了。可能什么键对应啥操作需要好好想一想。

通道/Channel

  通道是一系列互不干扰的音频流传输介质。MIDI标准中允许一个合成器同时管理16个通道,一个通道同一时间只能维护一种乐器状态。不过理论上来说一个MIDI乐谱中可以同时存在的乐器数无上限的,只是在超过通道限制时需要在乐谱中告诉合成器某某时刻应该切换乐器,合适的时候再切换回去云云。在非真实MIDI硬件设备的模拟环境中尤其对于浏览器环境而言,需要手动实现对于音频流的管理。

合成器/Synthesizer

  简单来说就是一个音频流的管理程序,提供解读乐谱、混响、音效支持和通道控制等功能。合成器通过读取给定的音色库解析和播放MIDI乐谱。对于非人民币玩家,要想在浏览器环境中以原生的方式搞到这个神奇的东西,用代码写吧XD。好在目前HTML5中的AudioAPI提供的一系列音频流处理接口已经基本满足构造一个能跑的合成器的条件。

音色库/Sound Font/Sound Bank

  我们所讨论的软波表,特指装载了一系列乐器音效的一个打包文件。该文件中存储了一系列基本的压缩音频采样,并定义了一套规范用于描述特定的乐器在给定音调/Tone音量/Voice时应当被如何通过已有的采样信息进行混响。解析并执行这一套规范就是合成器的作用。广泛使用的音色库文件如.sf2,几乎已成为MIDI音色库现行标准。好的音色库动辄十几张CD的存储量,这么大的容量得益于其未经压缩的采样率。雅马哈的一些特效音色库制作甚至耗时几年,从亚马逊河流域、阿尔卑斯山脉等地方录下最纯粹和原始的自然声音压制而成。上面这句纯属忽悠。
  目前几乎所有的操作系统中默认装载了声卡里的音色库,例如WIndows下经典的"GS波表软件合成器"——这便是为何可以直接在windows中点击播放MIDI文件的原因——一个约3.4M大小带音色库的合成器,支持GS/GM标准,该音色库存在"C:\Windows\System32\drivers\gm.dls"。好的声卡显而易见会提供更好的音色库,以便专业玩家创造出众的音乐体验。
  音色库文件的解析,在HTML5规范中几乎是一片空白。不过国外有研究团队已经在这方面颇有建树,HTML5 MIDI API的贡献者之一,日本哥Toyoshim的开源项目 T'Sound System Library 已经实现用js读取.sf2格式文件。著名的HTML5音频研究团队的MIDI框架MIDI.js用了一套已经打包为.mp3格式的base64文件来模拟音色库,这也是目前几乎所有web音频类应用的通用做法并且仍然没有更好的策略。即便是大名鼎鼎的Java,对于音色库的支持也一直推迟到JDK7的com.sun.media.sound包中才得以完整实现。

乐器/Instrument

  存储在音色库中的采样音频以及对该音频的预定义播放方式,展现在输出设备中就是乐器。GM音色库标准中定义了128种标准乐器,但不同的音色库中包含的音色信息可能相差非常大,这也是为何.midi格式的文件在不同的系统下可能会播放出不同效果的原因。
  正常情况下,通道K默认对应除了标准128中乐器中的第K个乐器,除非合成器告诉他需要切换乐器;特殊情况为作为打击乐保留通道的第Channel 10,其中的正常音符在默认情况下可能表现出不同的音色。

输出设备

  声卡和音响设备。但凡是个电脑都有的东西,并且浏览器已经通过AudioApi封装了对输出设备的访问,所以不需要我们关心。

小霸王至尊8合1

  事情到目前为止进展的似乎挺不错。我们只需要提供一个可以操作的界面,把这些东西全部打包装在一起,这就是个可以在浏览器端生成MIDI乐谱、可以导出为MP3的超级8合1至尊卡带了。不过等等,MIDI乐谱?

MIDI文件结构

  看来我们遇到了一点小麻烦,在解决如何生成MIDI乐谱之前可能得复习复习MIDI文件结构。关于这方面已经有诸多文献可参考,例如鄙人本科时与好基友大树阴凉儿所著的Bottom-up Digital Music Simulation System Under Program Control(百度貌似也能搜到免费版。。。)。这里有张图简要概括了MIDI文件结构:

音序/Sequence

  我们称一个完整的MIDI乐谱为一条音序。一个音序中至少应当定义MIDI文件类型音轨数、节拍(和节拍定义方式)等基本信息,这些信息被记录在Header Chunk中,即便该音序中不包含任何音符信息——那你要这条音序干嘛。。。

Header Chunk

/**
头部声明区                          | 数据长度声明区                      | 数据位
ASCII M  ASCII T  ASCII h  ASCII d  | Data Length: 6                      | MIDI type: 1     | Track Count: 1   | Division: mode0, 120 ticsks/second
01001101 01010100 01101000 01100100   00000000 00000000 00000000 00000110   00000000 00000001  00000000 00000001  00000000 11110000
 */
  首部块内容如上所示,用于声明一条音序的基本信息,仅用于标注文件声明。其中,4字节ASCII数据“MThd”为Header Chunk标识位,data length值为当前Chunk中数据位的长度。数据位包括3部分内容:

文件类型

  2字节数据,整数。合法的MIDI文件应当属于以下三种类型的一种:

音轨数

  Header Chunk本身并不是音轨,其中以2字节记录除Header Chunk以外的其他类型Chunk(亦即音轨)数量。

节拍定义/division

/**
|H|8-14bit 0-7bit
 0 0000000 00000000
 */
  如上结构的2字节数据位,这是个麻烦的字段。节拍有两种表示方式,取决于字段最高位(H位置处)数据:

Track Chunk

  音轨定义区。这实际上是一个逻辑分区,区分头部和数据部分。

音轨/Track

/**
头部声明区                          | 数据长度声明区                      | 数据区            | 结束标记
ASCII M  ASCII T  ASCII r  ASCII k  | Data Length: unknown                | OOXX....          | Ending the track: FF 2F 00
01001101 01010100 01110010 01101011 | OOOOOOOO XXXXXXXX OOOOOOOO XXXXXXXX | OOOOOOOO XXXXXXXX | 11111111 00101111 00000000
 */
  音轨是一系列数据的集合,每条音轨以声明ASCII“MTrk”开始。音轨是一个完全不受限制的数据定义区域,其中记录乐谱各种信息,包含但不仅限于:版权声明、文本信息、开启/关闭声音(Note On/Note Off)、改变乐器(Program Change)等等。MTrk类型Chunk必须有结束标记,并且结束标记也应当计算入数据区总长度。
  一个音序中通常包含至少1条音轨定义,并且仅在第一条音轨中保存类似版权信息、乐谱通用定义。非0类音轨也会通常将这些信息独立为一条音轨以方便维护。
  音轨本身作为数据集合,与硬件载体并不存在任何关系。但是在实现过程中,通常建议一条包含Channel信息的Track仅对应一个Channel——亦即所保存的除公共信息(例如版权声明)之外的数据均应尽量操作同一个Channel。由于Channel总数不超过16个,因此一个MIDI乐谱中的Track数量通常也不会超过17条;更甚由于一个Channel同时只能维护一种乐器,因此通常情况下,一首MIDI乐谱也不会设计超过16种乐器混响。
  下面我们聊一聊Track中的数据,以便解释一下上文这段话。啊,这将是个沉重的话题。

MIDI事件/Event

/**
 * 一则简单版权声明事件
 *
时间戳   | MIDI消息体
Tick     | Message Type      | Length   | Message Data
00000000   11111111 00000010   00000010   01000110 01000101
 */
  简言之,存储于Track数据区的所有内容均为一则事件,其结构如前文所示,主要包含两部分内容,时间戳MIDI消息。事件是Track内容的基本组成单元,拥有时间戳是其固有属性。一则正确定义的事件将按照节拍的与定义方式(Header Chunk中的division)在合适的时间被合成器触发,并产生相应的效果。

时间戳/Tick

  事件数据之间并不存在严格的边界,因此识别一条事件的依据便是消息体中严格定义的数据消息体长度和下一个事件的时间戳。在我们之前所定义的Header Chunk(1型MIDI,120tick/s)中,tick始终表示“当前事件的发生时刻”与“上一事件发生时刻”之间的时间刻度差值Delta Time。
  在编程模拟方面,维护“一个事件基于其他事件的严格顺序”是一件相当蛋疼的事情,这意味着可能得使用链表来管理一个乃至十万数量级(一首5分钟高清音序中的事件数规模在万级)的数据结构。Java中将Delta Time存储为“从音序开始执行以来所经历的tick”作为哈希的键值来存储和管理这些事件,一定程度上改善了检索、修改等操作的执行效率。可以参考JDK6中的Track类的源码。
  注意由于一首乐谱的时间刻度可能会在规范允许的范围内被定义的相当密集,因此tick的数值理论上可以达到无限大。这可怎么办,设想我们的骡子早上叫了一声晚上叫了一声,这时这两个事件发生的时间间隔恐怕8字节的256个tick无法表达我们对于骡子的热爱之情。
  因此,时间戳必须是一个可变长度数

可变长度数

  可变长度数允许不通过单独的length标记位记录一个长度不固定的正整数。对于可以通过7位以内二进制数表示的数据,看起来跟普通的二进制数没啥区别:
/**
127:
01111111
 */
  对于超过127的数据,可变长度数需要扩展高位字节,并遵循如下规则:   遵循对于以上规则,可变长度数中每个字节只能用来表示128个数。于是我们就有了以下奇葩的数据表示:
/**
127:
01111111

255 - 普通二进制:
11111111
255 - 可变长度数:
10000001 01111111

16383 - 普通二进制:
00111111 11111111
16383 - 可变长度数:
11111111 01111111

16384 - 普通二进制:
01000000 00000000
16384 - 可变长度数:
10000001 10000000 00000000
 */

MIDI消息

  作为MIDI Event的组成部分,MIDI消息才是这通篇的主角,其在MIDI系统中的地位就是北方的面南方的米沙漠里面边下的雨。
  如MIDI事件一节所示,一个MIDI消息包含消息类型、数据长度、数据体三部分。一则乐谱中所有控制信息均存储于消息中,不同的资料会罗列不同的分类方式。通常按照存储消息解析方式的不同分其为四类:Meta消息Sysex消息控制消息通道消息

Meta消息

  虽然所有事件都应当被合成器识别,但Meta消息始终不会被响应为输出。Meta消息仅用于记录,如果一个音序是一页代码,Meta消息就是代码中的注释。
  所有Meta消息的消息类型均以"FF"开头作为标志:
/**
 * 一则简单版权声明事件内的消息体
 *
Meta消息
消息类型: FF 02   | Length:2 | ASCII: F ASCII: E
11111111 00000010   00000010   01000110 01000101
 */
  上图所示的消息类型为“FF 02”,标准中定义其为“版权声明”。比较重要的Meta消息类型有“版权声明”(FF 02)和Track结束标记(FF 2F 00),在任何音序中都会存在。
  Meta消息由于可被用于记录不定长数据,因此其中的Length位也为可变长度数;数据位可以为特定格式标记、ASCII字符或为空(结束标记长度0、数据位空)。例如你可以声明一则长度为4字节的文本消息,然后像个不太能喝的司机被人猛灌一顿之后开着车在32车道的公路上随意发挥,甚至把莎士比亚全集录进去也是允许的,这都取决于Meta消息类型。
  更多Meta消息类型可参考附录二:Meta消息类型表

专用系统消息“F0/F7”/Sysex Message(注:此处存疑)

/**
 * 系统专用信息结构
 *
Meta消息
消息类型 | 0iiiiiii | 任意数据                     | 结束标记
11110000   00000100   10010000   01111111 01111111   11110111
 */
  一个系统专用信息如果以“F0”开始,则必须以“F7”作为结尾。因此,“F7”也被称作“End of Exclusive (EOX)”。
  在多份同样的标准文档中,我看到多个关于此消息的不同解释,分别来自“
Standard MIDI-File Format Spec. 1.1”和“Standard MIDI-File Format Spec. 1.1, updated”。也希对此有深刻认识的同僚能够指出此文谬误。下文中给出两个不同的解释细则仅供参考,其中所列举的字节码亦并不保证正确性。
  释义一:特定厂商专用扩展信息
  专用系统消息(Syntactic System Exclusive Message)的名字来由于其只会对特定的合成器系统起作用。该消息允许对某一事件进行“加密”,合成器在读到该事件时检测“密文”是否与自己所定义的“密文”相匹配。如果不匹配,则忽略该整个事件;否则对其进行解析。
  Sysex消息可以被用作一个“加密包裹”用于包裹任何其他类型的所有事件,也可用作保留字段用于包裹合成器自身的自定义事件(例如当前合成器实现的标准范围外的控制器类型),当然更多是用作发送针对合成器的特殊指令。MIDI中定义了许多保留字段用于后续扩展或交由合成器厂商自己实现,使用Sysex事件修饰的保留字段可以避免该消息被其他合成器错误解析,该扩展方式被称作合成器的“Universal Exclusive Messages”。
  在此类释义下,该信息结构中的0iiiiiii字段即提供给合成器用于鉴别的“密文”:MIDI设备生产商ID,固定长度为8bit并且最高位为0。合成器在不能识别该密文时应当将其中的直至结束标记位置的数据字段全部忽略;否则,解析内部数据。
  在上例中,这则Sysex消息包裹了一条Note On信息。其余任何数据也将被允许。
  释义二:系统高级信息
  与释义一一样,该信息同样用于发送任意数据。在此类释义下,该信息结构中的0iiiiiii字段为包含结束标记在内的数据体长度,与所有其他消息的长度一样也为可变长度数。
  虽然“F0”必须以“F7”作为结尾(此时作为标记位的F7并不会被识别为一条Event而被解析),但“F7”本身却可以独立使用,用于一条标准MIDI消息的承载。此时的“F7”被称作一个“escape”——实在不知道这术语翻译成中文应该叫啥,逃跑?跑的“F0”它娘都不认得?
  这种设计的优势在于,可以允许分片发送大量数据。例如当多个“43 12 00”(据本人肤浅的视野目测,“43 12 00”不代表任何意义。。。)需要被发送时,可以使用如下的命令:
/**
 * F0-F7承载命令示例(16进制)
 *
 00                         // start from tick 0
 F0 03 43 12 00             // start from F0
 81 48                      // 200-tick delta time
 F7 06 43 12 00 43 12 00    // F7 as an escape
 64                         // 100-tick delta time
 F7 04 43 12 00 F7          // F7 as an escape 
                            // together with the end of F0 while EOX is count into the last escape.
 */
  并且“F7”本身也可以作为独立消息使用:
/**
 * F7作为escape使用
00                          // start from tick 0
F7 01 FC                    // short message FC - pause
30                          // 48-tick dalta time
F7 01 FB                    // FB - start
 */
  关于该类型消息的更多解释,还可点击此处参考更详细的字段拆解

控制消息

  老实说苦于并未真正接触过硬件MIDI设备并且所见MIDI文件中大都不包含此类信息,鄙人对于此类消息的理解亦完全基于机翻的文字描述,难免会有所纰漏,同时也希望能有此领域专业人士批评指正。
  非通道相关的控制类消息的部分工作已经被现今在诸多成熟的合成器默认实现了,例如“音调调整”被设计用于兼容老式可能存在音调不准的合成器,现在已经基本废弃。一些特殊的控制类消息可能并不会存在于MIDI文件中,而是被设计于输入设备上发往合成器(例如某个按钮)用于实时的播放控制。
系统专用消息是一类特殊的控制信息。
  控制消息包括两部分,“系统通用消息”和“实时系统消息”。控制类消息均对合成器起作用,其效果可能表现于整个音序的播放阶段。在上文中的“FC”、“FB”即为两个控制消息,分别代表“暂停”和“开始”。更多关于控制消息的细节请移步附录三:控制消息类型表

通道消息/Short Message

/**
 * 通道消息标准格式
 *
类型和通道 | 数据1    数据2
1 ttt cccc   0xxxxxxx 0yyyyyyy
1 001 0000   01111111 01111111
 */
  好吧,怀旧一下。Short Message其实是Java中的叫法,Java把控制消息和通道消息全部合并称为短消息。
  顾名思义,这种类型消息与通道相关,这也是到目前为止唯一能跟通道和音轨之间的联系扯上点关系的数据了。一则通道消息包含2个或3个字节,并且没有长度标记。对于合成器而言,识别通道消息的标识就是高位字节首位为“1”并且紧邻1个或2个首位为“0”的低位字节(其他类型消息通过数据区长度或结束标记指定)。至于为何没有首位为0(状态位数值在128以下)的MIDI消息,鄙人目测也是为了防止消息数据区被数识别为另一则消息造成数据污染。
  如上图所示,通道信息中的“ttt”为信息类型标识,“cccc”为“0~15”共计16个通道标识,这也是为何MIDI通道只有16个的原因——超出了就根本表示不出来么...“数据1”和“数据2”所指代的含义有异于不同的通道信息类型。
  根据“ttt”的不同,通道类消息包括Note OffNote On(触后)键力度调整/Polyphonic Key Pressure(After Touch)控制器变化/Control Change程序变化(切换乐器)/Program Change(触后)通道压力调整/Channel Pressure(After Touch)音轮调节/Pitch Wheel Change共计7个。虽然数据格式通常是一定的,但是不同的合成器对于各类消息的几个字段解释可能有异。以下涉及实例部分均以使用最广泛的GS波表软件合成器为例讲解。
  全部通道消息细节信息烦请查阅“附录四:通道消息类型表”。

js生成数字音乐

  啧啧。。。目前看起来,为了能把我们想要的骡子声用来挂门上辟邪,我们的至尊8合1小霸王可能得加点位宽才顶得住。不过好歹也算是有些眉目了,我们已经生成了一串串可以出声的字节,接下来只需要把这一坨东西写出为一个".mid"文件就行。
  因此,“Js生成数字音乐”步骤中一个亟待解决的问题是:用Javascript抽象Midi文件结构
  浏览一下关于MIDI文件格式的定义,在“音调轮变化”一节实例《残忍之快速的巨神之刃》中的二进制串已经包含了一首完整Midi所需要的大部分数据,让我们补充一些组成一首完整Midi文件所需的细节内容:一个 Header Chunk,Track Chunk 头和结束标记(0xFF 0x2F 0x00),上文中提到的通道模式调整和版权声明。差不多够了,所以最后这坨东西看起来可能像下面这样——这将是一个拥有2001字节的数据串。
/**
 * 完整版“残忍之快速的巨神之刃”
 *
01001101 01010100 01101000 01100100 // MThd
00000000 00000000 00000000 00000110 // header lentgh: 6 bytes
00000000 00000001                   // Midi Type: 1
00000000 00000001                   // Track count: 1
00000000 00011000                   // Division: 48 ticks / second

01001101 01010100 01110010 01101011 // MTrk
00000000 00000000 00000111 10111011 // Track Length: 955 bytes - length-fixable data
00000000 11111111 00000010 00011100 // tick 0, 0xFF 0x02 0x1C - CopyRight, length: 28 bytes
00101000 01100011 00101001 00110010 // CopyRight Contents in 28 bytes.
00110000 00110001 00110011 00100000
01100010 01111001 00100000 01111001
01100001 01110010 01101101 01111001
01100001 01110010 01100011 01101000
01000000 01101100 01101001 01110110
01100101 00101110 01100011 01101110

00000000 11000000 00010001          // Program Change
00000000 10010000 00111100 01111111 // Note On
00000001 10110000 00001011 01111111 // 0xB0 0x11 0x7F
00000000 11100000 00000000 01000000 // 0xE0 0x00 0x40
00000001 10110000 00001011 01111111
00000000 11100000 00000000 01000000
00000001 10110000 00001011 01111110
00000000 11100000 00000000 01000001 // 0xE0 0x00 0x41
                                    // 117 No.11 Controller
00000001 10110000 00000001 01111111 // 0xB0 0x01 0x7F
                                    // 120 No.11 Controller
00000001 10110000 00001011 00000000 // 0xB0 0x11 0x00
00000000 11100000 00000000 01111111 // 0xE0 0x00 0x7F
00000000 10000000 00111100 00111111 // Note Off

00000000 11111111 00101111 00000000 // tick 0, 0xFF 0x2F 0x00 - End of Track Chunk
 */
  有兴趣的同僚们可以用二进制文件编辑器,例如UltraEditor之类的打开《残忍之轻快的巨神之刃》,对照以上二进制数据。如果发现有些不一样的地方——不要在意那些细节啦——这文件打开之后看起来大概像这样:

  我们差不多得到了它。由于这是一组显而易见的二进制数据,我们总能找到方法将其写出文件,尤其是在HTML5的支持下。

Blob写出文件/Midi数据序列化

  Html5的 File API绝逼是个臣诚惶诚恐死罪死罪的伟大发明,尤其是Blob这样的变态玩意——允许将任意数据序列化出为可被传输的二进制数据。啊,骡子出头有望,我们可以把上面一系列数据改造成一个Unit8数组,再通过Blob写出即可实现挂墙上的宏伟目标。
var dataString = "", // contents of the midi given above
    dataArray = dataString.split(/\D/).map(function(str) {
        return parseInt(str, 2);
    }),
    blob = new Blob([new Uint8Array(dataArray)], {type:"audio/midi"});
  有了Blob事情就好办多了,该文件在内存中创建完毕,已经可以通过DataUrl提供访问了。设想我们让用户点击直接下载:
var url = URL.createObjectURL(blob);
document.body.onclick = function() {
    window.open(url);
    document.body.onclick = "";
}

类组织结构

  当然我们不能总是用二进制说话,骡子肯定是听不懂的。我们倒是可以把Midi文件结构中的数据部分按类别划分出来,并创建一系列便于访问并且兼顾性能的接口用于操纵文件数据。UML图就不画了,直接贴5年前的——吐槽一下,这领域的更新足见有多让人捉襟见肘,数年前的资料放在如今仍然适用,没准还会一直适用下去。。。

  对于这个结构鄙人思索良久。对于大量事件中的特定事件进行增删改查操作是一件相当蛋疼的事,以至于最初在Java中我们只能采用遍历来实现。或许我们仍然可以沿用Java中关于时间戳的天才设计——事件tick表示从Sequence开始播放时到当前事件发生时的偏移量。在这种设计下,可以基于时间戳哈希事件,提供高性能操作的可能性。
  通过基于tick哈希的设计对上述UML图进行优化,我们可以很容易整理出如下的一些基本类,其核心为三个数据层:MessageTrackSequence

Message

  Midi音序的基本数据单元,维护typedata两个字段并提供相应的增改功能,同时提供序列化支持。需要注意的是对于除通道类消息之外的消息类型,序列化时需要在type和data之间添加可变长度数表示的数据字段长度。

Track

  基于tick哈希设计的Message容器。此处由于需要对tick进行直接管理,因此在Message类和Track类中间省略MidiEvent层。作为容器对象,提供基本的容器操作,并提供序列化的支持。
  通过以上简单探讨,Track作为容器应当具备以下一些特性:   在以上规则和约束下,鄙人只能想到哈希+数组的存储方式。该解决方案的局限性也很明显,同一tick下存在的消息过多时也伴随性能将明显下滑。
/**
 * key: tick count from the start of the sequence.
 * value: msgArr {Array} messages that're sharing the same tick which should 
 *     be contributed by type and data, and they could be serialized.
 */
events = {
    0 : [
        0x903F7F,
        // other messages in tick 0
    ],
    120 : [
        0x803F7F,
        // other messages in tick 120
    ]
}

Sequence

  我们直接跳过了作为逻辑结构的“Chunk”。同时 Header Chunk 作为一个Midi乐谱的必备数据,因此可以直接拆解为音序的一系列属性,甚至可以写入构造函数。从这个角度看,Sequence是一个纯粹的Track容器,提供基本容器方法和对 Header Chunk 属性的访问,并允许序列化。

扩展和封装

  我一直怀疑自己是不是点错了技能树。讨论了大半篇幅到太阳都快落山了的Midi文件数据,三个基本类就已全部囊括。如果我们再提供一些对于动态加载、二进制/十六进制数据运算、用于传递数据用的Base64编码和Blob支持,就几乎得到了一个比较完善的数字音乐模拟系统——的数据部分,亦即上文中UML图的Sequencer以下的部分。
  当然我在这里已经做了一部分工作,也欢迎大家前去围观批评。对于我们在今天早些时候提到的“《残忍之轻快的巨神之刃》”,代码就可以这样写了:
var seq = new Midi.Sequence(1, 24),
    track = seq.getTrack(0);

track.addEvent(0, new Midi.Message(0xc0, 0x11));        // Program Change to No.17
track.addEvent(0, new Midi.Message(0xB0, [124,0]));     // omni on
track.addEvent(0, new Midi.Message(0xB0, [126,1]));     // mono on & omni on
track.addEvent(0, new Midi.Message(0x90, [60,127]));    // Note On Key 0x3C
track.addEvent(120, new Midi.Message(0xB0, [1,127]));   // Vibrato Controller
track.addEvent(240, new Midi.Message(0x80, [60,127]));  // Note Off

// Apply Voice Change and Pitch Wheel Change
for (var i = 1; i <= 240; ++i) {
    track.addEvent(i, new Midi.Message(0xB0, [11, 127 - ~~((127 / 240) * i)]));
    track.addEvent(i, new Midi.Message(0xE0, [0, 64 + ~~((63 / 240) * i)]));
}

// Click & Download
var blob = new Blob([seq.toByteArray(1)], {type:"audio/midi"});
var url = URL.createObjectURL(blob);
document.body.onclick = function() {
    window.open(url);
    document.body.onclick = "";
}
  你也可以到这里来现场玩一玩,或者点击这里查看源码

MIDI实时播放

  当然把一个Midi乐谱下载到本地用GS波表软件合成器播放肯定是不存在问题的。但是对于一个存在实时交互的系统,合成器应当能够对用户输入做出反应,这是“下载文件并交由本地合成器播放”策略所不能解决的问题。
  设想我们希望在浏览器端创建最初所提到的完整数字音乐模拟系统,允许用户自由创作音符并实时对以创作的内容进行修改,该模拟应当可以对用户的输入进行反馈,即提供按键或操作的声音预览。前文中我们致力于并成功解决了用Javascript解析和封装Midi乐谱的问题,下一个问题是:产生的Midi序列如何实时播放?   浏览器不能直接解析Midi文件,因为浏览器中并不集成音色库,也没有合成器。访问声卡合成器的功能就别想了,几年内是出不来的,看看使用显卡的硬件加速进程就知道。不过仍然有多种方法可供选择。   以上几种方法或多或少都会存在各种问题。在flash淘汰声一浪高过一浪的今天,使用插件不会是什么明知的选择。借助 Html5 Audio API,预处理的压缩音频音色库能解决许多问题,但其欠缺对于音色播放的完全控制,也缺乏必要的扩展性和操作难度。为了能够使用当前已不可计数的各种优秀音色库和实时播放Midi乐谱,我们需要一个工具,该工具能够解析音色库文件,以允许用户使用本地音色库而不用消耗漫长的时间下载预定义的数据,因为Midi本身应当是很优雅和简约的存在;同时能够提供对Midi乐谱文件的解析功能,能够响应合理的通道事件并在输出设备上产生效果。
  也就是说,我们需要在浏览器端使用 Html5 Audio API 编码一个浏览器端的合成器

浏览器端的合成器

  这是另一个话题,该话题的含量甚至已经超过了这篇文章到目前为止加上附录的所有内容长度,现在也还没有这方面案例可以参考。这也是鄙人今后相当长一段时间内的调研方向,FML...
  基于以上原因此处不对合成器的模拟实现做过多讨论,因为......我TM也不懂啊!玩蛋去。。。下面是一些可用的参考,制作列举不做评论。   先就这么多吧,就这俩已经够让人喝一壶了。各位同僚们如果有什么好的建议也烦请不吝赐教。

现状&将来

  总的来说 Web MIDI 的发展势头还是很强劲的,无奈专业设备的准入门槛实在不在鄙人这样的资深屌丝能承受之范围,各种高大上合成器应用也非我这五音不识的认知所能理解。不过可惜的是,HTML5 MIDI API 对合成器和模拟方面似乎并没有什么兴趣,该草案2013年的主要进展仍然是尝试在浏览器中添加MIDI硬件设备接口——这也基本上宣布了该草案在成型之内一两年里的缓步发展状况,因为目前还未有纯粹基于浏览器的合成器面世,键盘接进浏览器也暂时还处于只能玩蛋的状态——兴许后续的扩展中会有浏览器厂商愿意解决合成器的问题,即便这意味着得招一帮既懂音乐又会写内核的人来开发这一系列API的实现——不过谁知道呢,在那之前我们可能还得继续关注并着手解决如何才能够实现让挂在浏览器里的骡子音色可以用来驱灾撵鬼的问题。
  最后附上大树阴凉儿的成名曲猴子.mid。这首曲子虽然看起来做得很粗糙,但却有其之所以值得铭记的独到原因——它是数年前由好基友一个字节一个字节敲出来的。
  谢谢观赏,后会有期。

附录

附录一:GS波表软件合成器音色表(GM2标准)

点击查看全部GM2标准音色表,或了解GM音色标准以及 Channel 10 保留音色

 钢琴

0

Acoustic Grand Piano

大钢琴(声学钢琴)

1

Bright Acoustic Piano

明亮的钢琴

2

Electric Grand Piano

电钢琴

3

Honky-tonk Piano

酒吧钢琴

4

Rhodes Piano

柔和的电钢琴

5

Chorused Piano

加合唱效果的电钢琴

6

Harpsichord

羽管键琴(拨弦古钢琴)

7

Clavichord

科拉维科特琴(击弦古钢琴)

色彩打击乐

8

Celesta

钢片琴

9

Glockenspiel

钟琴

10

Music box

八音盒

11

Percussive

颤音琴

12

Marimba

马林巴

13

Xylophone

木琴

14

Tubular Bells

管钟

15

Dulcimer

大扬琴

风琴

16

Hammond Organ

击杆风琴

17

Percussive Organ

打击式风琴

18

Rock Organ

摇滚风琴

19

Church Organ

教堂风琴

20

Reed Organ

簧管风琴

21

Accordian

手风琴

22

Harmonica

口琴

23

Tango Accordian

探戈手风琴

吉他

24

Acoustic Guitar (nylon)

尼龙弦吉他

25

Acoustic Guitar (steel)

钢弦吉他

26

Electric Guitar (jazz)

爵士电吉他

27

Electric Guitar (clean)

清音电吉他

28

Electric Guitar (muted)

闷音电吉他

29

Overdriven Guitar

加驱动效果的电吉他

30

Distortion Guitar

加失真效果的电吉他

31

Guitar Harmonics

吉他和音

贝司

32

Acoustic Bass

大贝司(声学贝司)

33

Electric Bass(finger)

电贝司(指弹)

34

Electric Bass (pick)

电贝司(拨片)

35

Fretless Bass

无品贝司

36

Slap Bass 1

掌击Bass 1

37

Slap Bass 2

掌击Bass 2

38

Synth Bass 1

电子合成Bass 1

39

Synth Bass 2

电子合成Bass 2

弦乐

40

Violin

小提琴

41

Viola

中提琴

42

Cello

大提琴

43

Contrabass

低音大提琴

44

Tremolo Strings

弦乐群颤音音色

45

Pizzicato Strings

弦乐群拨弦音色

46

Orchestral Harp

竖琴

47

Timpani

定音鼓

合奏/合唱

48

String Ensemble 1

弦乐合奏音色1

49

String Ensemble 2

弦乐合奏音色2

50

Synth Strings 1

合成弦乐合奏音色1

51

Synth Strings 2

合成弦乐合奏音色2

52

Choir Aahs

人声合唱

53

Voice Oohs

人声

54

Synth Voice

合成人声

55

Orchestra Hit

管弦乐敲击齐奏

铜管

56

Trumpet

小号

57

Trombone

长号

58

Tuba

大号

59

Muted Trumpet

加弱音器小号

60

French Horn

法国号(圆号)

61

Brass Section

铜管组(铜管乐器合奏音色)

62

Synth Brass 1

合成铜管音色1

63

Synth Brass 2

合成铜管音色2

簧管

64

Soprano Sax

高音萨克斯风

65

Alto Sax

次中音萨克斯风

66

Tenor Sax

中音萨克斯风

67

Baritone Sax

低音萨克斯风

68

Oboe

双簧管

69

English Horn

英国管

70

Bassoon

巴松(大管)

71

Clarinet

单簧管(黑管)

 

 

72

Piccolo

短笛

73

Flute

长笛

74

Recorder

竖笛

75

Pan Flute

排箫

76

Bottle Blow

[中文名称暂缺]

77

Shakuhachi

日本尺八

78

Whistle

口哨声

79

Ocarina

奥卡雷那

合成主音

80

Lead 1 (square)

合成主音1(方波)

81

Lead 2 (sawtooth)

合成主音2(锯齿波)

82

Lead 3 (caliope lead)

合成主音3

83

Lead 4 (chiff lead)

合成主音4

84

Lead 5 (charang)

合成主音5

85

Lead 6 (voice)

合成主音6(人声)

86

Lead 7 (fifths)

合成主音7(平行五度)

87

Lead 8 (bass+lead)

合成主音8(贝司加主音)

合成音色

88

Pad 1 (new age)

合成音色1(新世纪)

89

Pad 2 (warm)

合成音色2 (温暖)

90

Pad 3 (polysynth)

合成音色3

91

Pad 4 (choir)

合成音色4 (合唱)

92

Pad 5 (bowed)

合成音色5

93

Pad 6 (metallic)

合成音色6 (金属声)

94

Pad 7 (halo)

合成音色7 (光环)

95

Pad 8 (sweep)

合成音色8

合成效果

96

FX 1 (rain)

合成效果 1 雨声

97

FX 2 (soundtrack)

合成效果 2 音轨

98

FX 3 (crystal)

合成效果 3 水晶

99

FX 4 (atmosphere)

合成效果 4 大气

100

FX 5 (brightness)

合成效果 5 明亮

101

FX 6 (goblins)

合成效果 6 鬼怪

102

FX 7 (echoes)

合成效果 7 回声

103

FX 8 (sci-fi)

合成效果 8 科幻

民间乐器

104

Sitar

西塔尔(印度)

105

Banjo

班卓琴(美洲)

106

Shamisen

三昧线(日本)

107

Koto

十三弦筝(日本)

108

Kalimba

卡林巴

109

Bagpipe

风笛

110

Fiddle

民族提琴

111

Shanai

山奈

打击乐器

112

Tinkle Bell

叮当铃

113

Agogo

[中文名称暂缺]

114

Steel Drums

钢鼓

115

Woodblock

木鱼

116

Taiko Drum

太鼓

117

Melodic Tom

通通鼓

118

Synth Drum

合成鼓

119

Reverse Cymbal

铜钹

声音效果

120

Guitar Fret Noise

吉他换把杂音

121

Breath Noise

呼吸声

122

Seashore

海浪声

123

Bird Tweet

鸟鸣

124

Telephone Ring

电话铃

125

Helicopter

直升机

126

Applause

鼓掌声

127

Gunshot

枪声

附录二:Meta消息类型表

译自:标准MIDI文件格式规范1.1,或点此进入原文链接

Meta Message

FF 00 02 ss ss

音序号

这是一个可选的事件,它只能产生在第一个track,并且在非零时刻之前。

格式2文件中,这个用来识别每个 track,如果忽略,这个序列号从而用 track 出现的次序表示。.

格式1文件中,这个事件只能产生在第一个 track

ss ss

音序号,16 bit 二进制数。

FF 01 <长度> <数据>

文本事件

这个事件是用来注释 track 的文本。
独立的8位数据(其它的ASCII文本) 也是允许的。

<长度>

<文本> 的长度(可变长度数)

<文本>

<长度>个字节的 ASCII 文本或8位二进制数

FF 02 <长度> <数据>

版权声明

这个事件是用ASCII文本表示的版权通告,
例如你可以这样写:“© Wow! The coding rider!”

若要使用该事件,其必须为第一个track的第一个事件。

FF 03<长度> <数据>

音序 / Track 名称 

音序或 track 的名称,同样由可变长度数与ASCII数据组成。

FF 04 <长度> <数据>

乐器名称  

这个用来详细的记述(这个) MIDI channel 在 (这个)track 里使用的乐器。注意该事件并不会真正影响Channel中的音频流。如果希望改变当前Channel中的乐器(而非仅仅标注),请使用Program Change

FF 05 <长度> <数据>

歌词

通常每个音节都有自己的歌词,MIDI中允许将ASCII歌词嵌入乐谱作为消息并加上时间戳,交由合成器解释。

FF 06 <长度> <数据>

标记

通常用于格式0或格式1的第一个 track
标记有意义的点(如:诗篇1”

FF 07<长度> <数据>

暗示

用来表示舞台上发生的事情。如:幕布升起退出,台左等。

FF 20 01 cc

MIDI Channel 前缀

关联紧跟的 所有meta-events sysex-events MIDI channel,直到出现下一个包含Channel信息的标准消息出现。

cc

MIDI channel 1-16
范围:00-0F

FF 2F 00

Track 结束标记

这个事件是必须的,并且时间戳刻度应当在所有其他事件之后。
它用于清除当前Track定义的长度,因为MTrk Chunk的长度定于并不总是可靠。其本质是用于标记当前 track 是循环还是连接另一个 track

FF 51 03 tt tt tt

拍子

这个标注1/4音符的速度,用微秒表示。这个意味着改变一个 delta-time 的单位长度。 (注意1)

如果没有指出,缺省的速度为 120/分。这个相当于 tttttt = 500,000

FF 54 05 hh mm ss fr ff

SMTPE 偏移量 

可选事件,描述 track 开始时的 SMTPE 时间。

这个事件必须发生在非零 delta-time之前,且在第一个事件之前。

 在格式1中,这个事件必须在第一个 track 中。

hh mm ss fr

小时///帧 用 SMTPE 格式。
这个必须与消息 MIDI Time Code Quarter Frame 一致。

ff

Fractional frame, in hundreth's of a frame

FF 58 04 nn dd cc bb

拍子记号

拍子记号的形式:

nn/2^dd
如: 6/8 nn=6dd=3 表示。

这个参数 cc 是表示每个 MIDI 时钟的节拍器的 tick 数目。

通常24 MIDI 时钟为一个1/4音符。可是一些软件允许用户自己设置这个值。参数 bb 定义:24 MIDI 时钟(这个一般(表示)1/4音符)中 1/32音符的数目。

nn

拍子记号,分子 

dd

拍子记号,分母表示为 2 的(dd次)冥。
例:一个分母 4 dd=2 表示。

cc

每个 MIDI 时钟节拍器的 tick 数目。 

bb

24MIDI时钟中1/32音符的数目(8是标准的)。

FF 59 02 sf mi

音调符号

音调符号,表示升调或降调值,大调或小调的标志。

0 表示 C 调,负数表示降调,正数表示升调

sf

升调或降调值
-7 = 7
升调
0 = C

+7 = 7
降调

mi

0 = 大调
1 =
小调

FF 7F <len> <id> <data>

音序器描述 Meta-event

这个在 MIDI 文件中等同于系统高级事件。

  MIDI 文件中用这个事件表示制造商音序器统一化的描述。

<长度>

长度 of <id>+<数据> (可变长度数)

<id>

13个字节表示制造厂商。
这个值同样的作为 MIDI System Exclusive messages 用。

<数据 >

8位二进制数

附录三:控制消息类型表

译自:标准MIDI文件格式规范1.1,或点此进入原文链接

系统通用消息System Common Message

状态位(D7-D0

数据位(D7-D0

信息含义

11110000

0iiiiiii

专用系统消息

0ddddddd

见前文中关于“SysexMessage”的描述。

11110001

/

MIDI时间代码转换信息
这个信息是把SMPTE在同步的工作中的状态转化为MIDI时间代码(MIDI Time Code)的格式:小时(Hour:分钟(Minute:秒(Second:帧(Frame
有关MTC Quarter Frame的说明,可以点击这里查看

11110010

0lllllll

乐曲位置指针信息

0mmmmmmm

这个信息是一个内部十四位寄存器存储了从乐曲开始计数时的MIDI的节拍数(在MIDI协议中,一节拍相当于六个MIDI时钟单位)
其中(lllllll)为计数器的低7位,(mmmmmmm)为计数器的高7位。

11110011

0sssssss

乐曲选择信息

该信息指定了以什么序列或哪首乐曲将被演奏。

11110100

/

未定义

11110101

/

未定义

11110110

/

音调调整要求信息

当模拟合成器收到这个信息时,都要调整它们的震荡器的震荡频率,这个信息是为老式的电子合成器而保存的.因为在老式的合成器的使用时常常发生音调不准,需要此信息对其进行震荡器的微调.而今天的合成器已经不需要它了。

11110111

/

结束系统专用信息
见前文中关于“SysexMessage”的描述。

系统实时消息System Real-Time Message

状态位(D7-D0

数据位(D7-D0

信息含义

11111000

/

时钟信息

当有同步要求时,该信息每四分之一音符发送24次。

11111001

/

未定义

11111010

/

开始信息

开始现有的序列演奏(通常时钟信息紧跟在本信息后面连用)。

11111011

/

继续信息

本信息是命令音序在被停止的地方继续演奏。

11111100

/

停止信息

停止/暂停当前音序,可被恢复。

11111101

/

未定义

11111110

/

联系激活信息

这条信息的使用是可以选择的。如果使用了该信息,接收器将在每300(最大值)毫秒内准备接收下一个联系激活信息,否则它会自动认为连接已经终止。如果接收器认为连接已经终止后,它将停止所有发音并恢复到正常的工作状态(非联系激活工作状态)。

11111111

/

复位信息

复位信息将系统内所有接收器都恢复到电源打开的初始状态。在一些特殊状态下,它可以不复位到电源打开状态下。

附录四:通道消息类型表

译自:标准MIDI文件格式规范1.1,或点此进入原文链接

通道声音信息Channal Voice Message

状态位(D7-D0

数据位(D7-D0

信息含义

1000cccc

Onnnnnnn

音符关闭

Ovvvvvvv

这个信息是在一个音结束时发出的,如键盘的某个键被放开
其中(nnnnnnn)是音符音高的编号代码,(vvvvvvv)是力度的编号代码。

1001cccc

Onnnnnnn

音符打开

Ovvvvvvv

这个信息是在一个音开始时发出的,如键盘的某个键被按下,其中(nnnnnn)是音符音高的编号代码,C大调音高为“01100000”;(vvvvvvv)是力度的编号代码。

1010cccc

Onnnnnnn

和弦(触后)键压力变化

Ovvvvvvv

这个信息是在先前被按下的琴键的压力变化时发出的,其中(nnnnnnn)是音符的编号代码,(vvvvvvv)是变化后新力度的编号代码。

1011cccc

0ccccccc

控制器变化

0vvvvvvv

这个信息是在某个控制器的控制值发生变化时发出的,例如:踏板等变化
有些控制器号码被作为保留字段(参见本页下方通道模式消息栏的说明)。
其中(ccccccc)是控制器的号码,(vvvvvvv)是力度的编号代码

1100cccc

0ppppppp

程序变化(乐器切换)

这个信息是在音色号码被改变时发出的,其中(ppppppp)是新的音色号码。GM标准音色号可在附录一中查询。

1101nnnn

0ccccccc

通道(触后)压力变化

这个信息是在通道的压力发生变化时发出的,当有一些对力度敏感的键盘不支持上文中提到的和弦触后(Polyphonic Aftertouch)时,可以通过发送这个信息来改变当前所有被按下的键中力度最大的单个键的力度信息,其中(ccccccc)是控制代码。

1110nnnn

0lllllll

(触后)音调轮变化

0mmmmmmm

这个信息的发送说明音调轮有变化
音调轮是由一个14位二进制数衡量描述的,其中值,即音调轮没有变化时的值为“00000000 01000000”,其变化的灵敏度与传递介质有关
其中(lllllll)是14位数中的低7位,(mmmmmmm)是其高7

通道模式信息Channel Mode Message

1011nnnn

0ccccccc

通道模式信息

0vvvvvvv

通道模式消息是“控制器消息”中的特殊情况,特定的控制器类型被作为保留字段识别为该类型信息。


通道模式
  Mono Mode:当前通道同一时刻只能演奏一个音符;
  Poly Mode(默认):当 Mono Mode Off 时的通道状态;
  Omni Mode On(默认):输出设备将尝试响应所有通道信息;
  Omni Mode Off: 设备仅响应自身所定义的保准通道(如果存在)中的通道信息。


本地控制(Local Control
在本地控制被关闭时,所有某指定通道的MIDI设备都只对MIDI协议的数据信息有反应,而对演奏数据等信息予以忽略.本地控制打开时,设备恢复正常控制器的功能。
    c=122 v=0:
本地控制关闭
    c=122 v=127:
本地控制打开

所有音符关闭:
当一个所有音符关闭信息被接收到时,所有设备的震荡器都被关闭
    c=123 v=0:
所有音符关闭
    c=124 v=0:omni
方式关闭
    c=125 v=0:omni
方式打开
    c=126 v=M:
单模式(Mono Mode)打开(即复模式,PolyMode关闭),其中M用于指定通道(Channel)号(1-16,此时代表omni关闭)或0omni打开)。
    c=127 v=0:
复模式打开(单模式关闭)
(注意:以上四个信息同时出现时将导致所有音符关闭)

附录五:控制器类型表

译自:标准MIDI文件格式规范1.1,或点此进入原文链接

MIDI控制器一览表

控制器编号

参数意义

0

音色库选择MSB

1

颤音深度(粗调)

2

呼吸(吹管)控制器(粗调)

3

N/A

4

踏板控制器(粗调)

5

连滑音速度(粗调)

6

高位元组数据输入(Data Entry MSB

7

主音量(粗调)

8

平衡控制(粗调)

9

N/A

10

声像调整(粗调)

11

力度控制器(粗调)

12-15

N/A

16-19

一般控制器

20-31

N/A

32

插口选择

33

颤音速度(微调)

34

呼吸(吹管)控制器(微调)

35

N/A

36

踏板控制器(微调)

37

连滑音速度(微调)

38

低位元组数据输入(Data Entry LSB

39

主音量(微调)

40

平衡控制(微调)

41

N/A

42

声像调整(微调)

43

力度控制器(微调)

44

效果FX控制1(微调)

45

效果FX控制2(微调)

46-63

N/A

64

保持音踏板1(延音踏板)

65

滑音(在音头前加入上或下滑音做装饰音)

66

持续音

67

弱音踏板

68

连滑音踏板控制器

69

保持音踏板2

70

变调

71

音色

72

放音时值

73

起音时值

74

亮音

75-79

声音控制

80-83

一般控制器(#5-#8

84

连滑音控制

85-90

N/A

91

混响效果深度

92

(未定义的效果深度)

93

合唱效果深度

94

(未定义的效果深度)

95

移调器深度

96

数据累增

97

数据递减

98

未登记的低元组数值(NRPN LSB

99

未登记的高元组数值(NRPN MSB

100

已登记的低元组数值(RPN LSB

101

已登记的高元组数值(RPN MSB

102-119

N/A

120

关闭所有声音

121

关闭所有控制器

122

本地键盘开关

123

关闭所有音符

124

Omni模式关闭

125

Omni模式开启

126

单音模式

127

复音模式

附录六:Note Key 与音阶的对应关系

译自:标准MIDI文件格式规范1.1,或点此进入原文链接

音阶 #

Note Keys

 

C

C#

D

D#

E

F

F#

G

G#

A

A#

B

-1

0

1

2

3

4

5

6

7

8

9

10

11

0

12

13

14

15

16

17

18

19

20

21

22

23

1

24

25

26

27

28

29

30

31

32

33

34

35

2

36

37

38

39

40

41

42

43

44

45

46

47

3

48

49

50

51

52

53

54

55

56

57

58

59

4

60

61

62

63

64

65

66

67

68

69

70

71

5

72

73

74

75

76

77

78

79

80

81

82

83

6

84

85

86

87

88

89

90

91

92

93

94

95

7

96

97

98

99

100

101

102

103

104

105

106

107

8

108

109

110

111

112

113

114

115

116

117

118

119

9

120

121

122

123

124

125

126

127

 

 

 

 

参考列表太长,就算了吧,所有引用已在原文中注明出处,若存在版权问题烦请与博主联系。谢谢。