《圈圈教你玩USB》 第七章 USB MIDI键盘 看书笔记

协议- USB 专栏收录该内容
12 篇文章 7 订阅

修改前的代码: 《圈圈教你玩USB》第四章 USB键盘 UsbKeyboard

修改后的代码: 《圈圈教你玩USB》第七章 USBMIDI键盘 UsbMidiKeyboard

7.1 MIDI简介

7.2 MIDI的工作原理

1.MIDI控制命令之一:让指定通道发声——Note On Message
格式(16进制):9n, kk, vv。
                           9:表示该消息的ID;
                           n:表示发送到的通道,可以选择为0~15,共16个通道;
                            kk:表示音符的音高,60(十进制)为中央C音;
                            vv:表示演奏的力度,0为关闭声音,127为最大音量。

7.3 USB MIDI设备的数据流模型

   一个USB MIDI设备可以有很多模块,例如MIDI合成器、MIDI转换器(必须的)、插孔等。本文设计无合成器等模块,只有MIDI插孔和转换器。
模块作用:
   转换器:将各个插孔和模块连接起来;
   插孔    : 是一个抽象概念,供MIDI信息输入输出使用。
传递MIDI消息的方法:批量端点,一个批量端点可以传输多路MIDI消息。
    此处传输的不是原始的MIDI消息,而是由打包为32位的USB-MIDI事件包发送。
    eg:Note On消息,打包方法:
        在前面加上0xP9,其中P为某一路MIDI消息的编号,本实例只有一路,设置未0即可,故为0x09
 

7.4 设备描述符

    将设备描述符中的PID改为0x0008.

7.5 配置描述符集合

删除无用描述符:
   配置描述符集合中的HID描述符;
   报告描述符、获取报告描述符的代码;
添加新的描述符:
    MIDI流接口描述符;
    类特殊接口描述符;
    类特殊端点描述符;
详细见7.5.1~7.5.7和USB MIDI设备类的协议文档。


7.5.1 配置描述符

该USBMIDI键盘的配置需要两个接口。
修改内容:
   修改配置描述符中的bNumInterfaces为0x02。

7.5.2 音频控制类接口描述符

      每个音频设备都有一个音频控制接口AC(Audio Control),可以有多个音频流接口或者MIDI流接口。
    作用:这里,音频控制接口没有实际的用途,仅是协议强制要求而已。
   所以使用的端点数目为0,接口类为音频类(0x01),子类为音频控制子类(0x01),没有使用协议,为0值。
重点添加内容:
    bNumEndpoints为0;
    bInterfaceClass为0x01;
    bInterfaceSubClass为0x01;
    bInterfaceProtocol为0x00;

7.5.3 类特殊音频控制接口描述符

    作用:本描述符用来描述该音频控制接口的属性,这里仅有一个头描述符,用来说明从属于该音频控制接口的其他接口有哪些。
重点添加内容:
   这里只有一个流接口,并且它的编号为1。
    bInCollection 为0x01        //流接口的数目
    baInterfaceNr(1)为0x01   //哪个MIDI流接口属于该音频控制接口(这里为接口1,即MIDI流接口)

7.5.4 MIDI流接口描述符

    作用:使用两个批量端点分别输入和输出MIDI流数据。
   本描述符使用标准的接口描述符类型,将前面的音频控制接口描述符复制一份修改为MIDI流接口描述符。
重点修改部分:
    bInterfaceNumber改为0x01   //这里属于第二个接口
    bNumEndpoints改为0x02      //端点数目
    bInterfaceSubClass改为0x03//子类代码,0x03表示MIDI流接口子类

7.5.5 类特殊MIDI流接口描述符

    作用:描述USB MIDI设备内部各模块(包括元件与插孔)间的连接关系。
    插孔分类:内嵌输入插孔、内嵌输出插孔、外部输入插孔、外部输出插孔(方向是对设备来说的,与前面的端点方向刚好相反)。
    插孔(USB MIDI设备中)简介:

插孔数据流向:


 键盘的数据通过内嵌输出插孔输出给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键盘,还有很多工作要做。


  • 4
    点赞
  • 0
    评论
  • 15
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

sz189981

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值