USB MIDIのmidiEventPacket_t

ArduinoのUSB-MIDIのライブラリMIDIUSBでは、MIDIの送受信のための構造体が MIDIMIDIUSB_Defs.hに定義されています。byte1, byte2, byte3 はMIDIのイベントがそのまま 入ります。それではheaderには何が入っているのか調べてみました。

#pragma once
#include <stdint.h>

typedef struct
{
    uint8_t header;
    uint8_t byte1;
    uint8_t byte2;
    uint8_t byte3;
} midiEventPacket_t;

まず、MIDIを受け取りmidiEventPacket_tをシリアルモニターで書き出すようなプログラムを書きます。

#include "MIDIUSB.h"

void setup() {
    Serial.begin(115200);
}
 
void loop() {
    midiEventPacket_t rx;
    char str[128];
    do {
        rx = MidiUSB.read();
        if (rx.header != 0) {
            sprintf(str,"%02x %02x %02x %02x\n",
                rx.header,rx.byte1,rx.byte2,rx.byte3);
            Serial.print(str);
            MidiUSB.flush();
        }
    } while (rx.header != 0);
  }

次のようなMIDIデータをSendAndReceiveからArduino Microへ送ります。



Output MIDIの中に21バイトのデータを記述し、Sendボタンを押します。 これは、以下の9個のMIDIデータを連続して送信することを意味しています。

81 40 7F : Note On
92 3F 7F : Note Off
A3 2E 43 : Polyphonic Key Pressure
B4 3D 40 : Control Change
C5 2D    : Program Change
D6 47    : Channel Pressure
E7 3F 7F : Pitch Bend
F8       : Timming Clock
FE       : Active Sensing

これを上のArduinoで受信すると、Serial Monitorには以下のように表示されます。

08 81 40 7f
09 92 3f 7f
0a a3 2e 43
0b b4 3d 40
0c c5 2d 00
0d d6 47 00
0e e7 3f 7f
0f f8 00 00
0f f8 00 00

上の結果を見ると、headerにはMIDIのステータス(byte1の上位4ビット)と同じ値がコピーされています。 それでは、以下のようなシステムエクスクルーシブメッセージを送って見ることにします。

以下のように、headerには4という数字と7という数字が書かれています。それを除けば、送信した システムエクスクルーシブメッセージと同じデータであることがわかります。

04 f0 01 02
04 03 04 05
04 06 07 08
04 09 0a 0b
04 0c 0d 0e
07 0f 10 f7

この47を理解するには、USB-MIDI1.0の仕様書を見る必要があります。 仕様書はUSBの規格団体からダウンロードすることができます。
https://www.usb.org/document-library/usb-midi-devices-  

仕様書の「4 USB-MIDI Event Packets」にUSB-MIDIで送信される4つのバイトの説明が載っています。

BYTE 1,2,3はMIDIデータにあたり、BYTE 0がheaderにあたります。 BYTE 0には上位4ビットにケーブルナンバー(Cable Number)が、下位4ビットには コードインデックスナンバー(Code Index Number)が入ります、 先の47はこのコードインデックスナンバーにあたります。

コードインデックスナンバーの詳細は仕様書の 「Table 4-1: Code Index Number Classifications」に書かれています。 システムエクスクルーシブメッセージについて抜書きすると以下の表となります。

4 システムエクスクルーシブ スタートと継続
5 1バイトのシステムコモンメッセージ
もしくは残り1バイトでシステムエクスクルーシブで終了
6 残り2バイトでシステムエクスクルーシブ終了
7 残り3バイトでシステムエクスクルーシブ終了

上のシステムエクスクルーシブの例では、F7がBYTE 2に入っていて ちょうど3バイトでシステムエクスクルーシブが終わっていますので 最後ですのでコードインデックスナンバーが7になっています。

以下のシステムエクスクルーシブであれば最後は6になります。

F0 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F                
04 f0 01 02
04 03 04 05
04 06 07 08
04 09 0a 0b
04 0c 0d 0e
06 0f f7 00

以下のシステムエクスクルーシブであれば最後は5になります。

F0 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E                
04 f0 01 02
04 03 04 05
04 06 07 08
04 09 0a 0b
04 0c 0d 0e
05 f7 00 00

さらに以下のシステムエクスクルーシブであれば最後は7に戻ります。

F0 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D
04 f0 01 02
04 03 04 05
04 06 07 08
04 09 0a 0b
07 0c 0d f7

システムエクスクルーシブの受信

上で調べた事を使ってArduinoでシステムエクスクルーシブを受信プロフラムを作ってみます。

unsigned char sysex[128];
uint16_t cnt=0;
extern void procsysex(unsigned char *sysex, uint16_t cnt);
void loop() {
    midiEventPacket_t rx;
    do {
      rx = MidiUSB.read();
      if (rx.header != 0) {
        //send back the received MIDI command
        MidiUSB.sendMIDI(rx);
        switch(rx.header&0x0F){
          case 0x4:
            if(rx.byte1==0xF0){
              cnt=0;
            }
            sysex[cnt++]=rx.byte1;
            sysex[cnt++]=rx.byte2;
            sysex[cnt++]=rx.byte3;
            break;
          case 0x5:
            sysex[cnt++]=rx.byte1;
            procsysex(sysex,cnt);
            break;
          case 0x6:
            sysex[cnt++]=rx.byte1;
            sysex[cnt++]=rx.byte2;
            procsysex(sysex,cnt);
            break;
          case 0x7:
            sysex[cnt++]=rx.byte1;
            sysex[cnt++]=rx.byte2;
            sysex[cnt++]=rx.byte3;
            procsysex(sysex,cnt);
            break;
        }
        MidiUSB.flush();
      }
    } while (rx.header != 0);
  }

procsysexはシステムエクスクルーシブを処理するルーチンで、 ここではシステムエクスクルーシブをシリアルで表示しています。

    void procsysex(unsigned char *sysex, uint16_t cnt){
        uint16_t i;
        char str[32];
        for(i=0; i<cnt; i++){
          sprintf(str,"%02x ",sysex[i]);
          Serial.print(str);
        }
        Serial.println("");
      }