键盘的数据通过内嵌输出插孔输出给PC,但键盘的数据不能直接发送到内嵌输出插孔,而是假设有一个外部输入插孔(虚拟的),将该输入插孔连接到内嵌输出插孔的“输入引脚”上。
对于PC端的数据也类似,数据将到达内嵌输入插孔,将内嵌输入插孔连接到外部输出插孔的输入引脚上,数据就可以发送出去了。
各个插孔都有ID号,可以通过在各自插孔的描述符中指定某个插孔的ID号,把输入和输出插孔连接起来。
描述符简介:
① 类特殊MIDI流接口头描述符(class-specific MS interface header descriptor):头描述符,引导下面三种类特殊MIDI接口描述符。
② MIDI输入插孔描述符(MIDI IN jack descriptor) :描述输入插孔;
③ MIDI输出插孔描述符(MIDI OUT jack descriptor):描述输出插孔;
④ 元件描述符(element descriptor) :描述USB MIDI设备的元件,例如MIDI合成器等(本例未用到)
描述符的结构和具体实现:
① 类特殊MIDI流接口头描述符:
② MIDI输入插孔描述符:
bJackType为插孔的类型,可以选择内嵌(0x01)或者外部(0x02);
bJackID 为插孔的唯一ID,可以用在输出插孔的输入源选择上。
③ MIDI输出插孔描述符
前5个字节同输入插孔意义相同。
中间的省略号部分表示可以有多组baSourceID和BaSourcePin;
如“插孔数据流向”部分左图所示,这里指定内嵌输入插孔的ID为1;外部输入插孔的ID为2;内嵌输出(到PC)插孔的ID为3;外部输出(从PC)插孔的ID为4,则:
在内嵌输出插孔的baSourceID中指定ID为2,即外部输入插孔;
在外部输出插孔的baSourceID中指定ID为1,即内嵌输入插孔。
④ 元件描述符(Element Descriptor)在本实例中未用到,这里不详述了。可以在USB MIDI设备协议中查到。
最终设置好的类特殊MIDI流接口描述符包含下列部分:
/************* 内嵌输入插孔描述符***********/
/************* 外部输入插孔描述符***********/
/************* 内嵌输出插孔描述符***********/
/************* 外部输出插孔描述符***********/
7.5.6 端点描述符和类特殊端点描述符
作用:
标准批量数据端点描述符:
类特殊MIDI流批量数据端点描述符:描述内嵌插孔是如何在端点上组织的,跟在每个标准的批量数据端点描述符后面。
描述符的结构:
① 类特殊MIDI流批量数据端点描述符
bNumEmbMIDIJack:本例仅有一个内嵌输入插孔或内嵌输出插孔,所以为1;
baAssocJackID :可以有多个,视该端点内嵌插孔的数量而定。对于输入端点,指定为内嵌输出插孔的ID,对于输出
端点,指定为内嵌输入插孔的ID。
最终设置好的代码如下:
/***************标准批量数据输入端点描述符*********/
/*************** 类特殊MIDI流批量数据端点描述符***/
/***************标准批量数据输出端点描述符*********/
/*************** 类特殊MIDI流批量数据端点描述符***/
7.5.7 字符串描述符
修不修改无所谓
7.6 修改好描述符后的测试
7.7 USB MIDI键盘的数据返回
本MIDI键盘的功能:产生一条Note On(音符开)消息。
32位MIDI事件包格式(16进制)为:P9,9n,kk,vv,其中
第一个字节P9:为USB MIDI协议中增加的包头,P为某一路MIDI消息的编号(这里仅有一个内嵌输出插孔,为0);9为该包的ID标识。
后面三个字节为实际的MIDI消息,9n表示在通道n上发送Note On消息;kk表示音符的音高;vv表示音符的力度(响度,127为最大声)。
按键对应音高:KEY1~KEY8对应简谱的5、6、1、2、3、5、6、1,第一个1为中央C,按照书中的介绍,可以计算出它们在MIDI消
息中的音高值分别为55、57、60、62、64、67、69、72。
代码修改:将原来返回报告的函数SendReport改为SendNoteOnMsg,在该函数中根据不同按键的按下和抬起来发送Note On消息。
当某个按键按下时,就发送力度值为最大的Note On消息,让某个音符发声;当某个按键弹起时,就发送力度为0的消
息,这将停止该音符的发声。
数据输出:因为实验板无法设置为31.25kb/s波特率,所以对于端点2输出的数据直接丢弃。仅在端点2输出中断处理中清除中断标志
和清空缓冲区。
需要注意的是,输出数据的处理速度一定要够,否则可能导致应用软件停止响应甚至整个操作系统崩溃。如果设备用不
到MIDI输出,则干脆在外部输出插孔描述符中修改baSourceID为0x02,选择输入源为外部输入插孔。这样内嵌输入插孔
就没有被使用,从而Windows就不会增加MIDI输出设备。正常使用时,将config.h中定义的调试宏删除,避免多余的消耗。
7.8 USB MIDI键盘的使用
使用HappyEO电子琴软件
该MIDI键盘连接上PC后,会产生一个MIDI输出设备,Windows操作系统有时会自动将它选择为“MIDI音乐播放”的默认设备。如果此时播放一个MIDI文件,数据将发送到USB MIDI设备上去,从而PC声卡无声音输出。可以进入控制面板的“ 声音和音频设备 ”中选择需要的MIDI设备。如下图所示,一般声卡使用“ Microsoft GS波表软件合成器”。如果不想改这个MIDI音乐播放器的默认设置,参考7.7中最后一段。
7.9 单片机自动弹奏的实现
找到曲子后,把每个音符翻译成MIDI消息,然后按照谱子中给定的时间间隔发送就行了。
减少数据存储量的方法:
定义一种结构,曲子以行的格式保存,每行在同一个时刻(实际上是比较短的时间内分多个包发送的)声音。每行第一个字节为该行需要发声的个数;接下来的两个字节反别是音符的音高和力度,一行有多个音节时,音高和力度如此重复下去;最后两个字节为该行音符发送后停留的时间。
整首曲子保存在一个数组中,数组的前两个字节为整个曲子的行数,接下来就是一行行的MIDI消息数据。具体数据格式参考源代码中的song.c文件,里面包含了曲子的数据和播放曲子的函数。
怎么区分通道:
旋律音和打击乐是在不同通道上的,代码中利用0xff(数据中未用到0xff)作为通道切换指示。当遇到0xff时,就切换到另一个通道。
代码中同时按下KEY1和KEY8后,开始自动播放。
7.10 本章小结
本章简单介绍了USB MIDI键盘的实现,MIDI以及音乐方面的知识;
实现了USB MIDI设备的基本功能。
若要制作一个完整、符合规范的USB MIDI键盘,还有很多工作要做。