PSoC5LP Prototyping Kit (31) --- USB MIDI (3)

前回示したように、USBMIDI コンポーネントを「USB-MIDI コンバータ」として利用するために必要なコード量は 10 行程度なので、「USB 接続の音源」を作る場合には、「レガシー MIDI 入力の音源」+「USB-MIDI コンバータ」という構成にするのが最も容易です。
それぞれを独立に使うこともできますし、USB-MIDI コンバータ部の MIDI 出力と、音源部の MIDI 入力とを MIDI ケーブルで接続、あるいは基板上でジャンパで接続すれば「USB 入力の音源」として機能させることができます。
MIDI データはソフトウェア上のデータとしてやり取りできれば十分で、わざわざレガシー MIDI ハードウェア信号に変換するのは、UDB、外部ハードウェア、ソフトウェアのリソースが無駄という場合には、USBMIDI コンポーネントのカスタマイザの画面で UART を使わない設定にすることができます。
カスタマイザを開いて、「MIDI Descriptor」のタブを選択した後の画面を下に示します。

これは、MIDIstreaming subclass のディスクリプタを設定する画面ですが、USBMIDI コンポーネントではすでに設定ずみなので、ディスクリプタ自身はいじる必要はありません。
画面左下に赤い枠線で囲って示してある「External Mode」の「チェックボックス」の状態を、デフォルトのチェック状態から変更して、非チェック状態にすると UART を使わないモードになります。
非チェック状態なら、UART、入出力ピン、インバータを削除して、USBFS コンポーネント本体だけを残して配置することが可能です。
ソフトウェア的に MIDI データをやり取りするために、次のふたつの API が用意されています。 これらは UART を使ってレガシー MIDI 信号を扱う場合にも有効です。

void USB_callbackLocalMidiEvent(uint8  cable, 
                                uint8* midiMsg);

uint8 USB_PutUsbMidiIn(uint8 ic, 
                       const uint8 midiMsg[], 
                       uint8 cable);

ここで、USBMIDI コンポーネントインスタンス名を「USB」として、API 関数名に「USB_」というプリフィクスが付くことを想定しています。
USB_callbackLocalMidiEvent() 関数は前回も登場しましたが、デバイスの OUT EP にホストから MIDI パケットが到着した際に呼び出されるユーザ定義の関数です。
第 1 引数の cable は virtual cable number (を 4 ビット左シフトしたもの) を表します。
USBMIDI コンポーネントでは、MIDIstreaming データには IN/OUT それぞれひとつずつのエンドポイントを割り当てており、2 ポート構成の MIDI IN/OUT に対してはケーブル・ナンバーで対応しています。
1 ポート構成では、cable == 0 のデータしか表れません。
第 2 引数の midiMsg は 4 バイト構成の MIDI パケットの先頭 (バイト 0) の CN/CIN (Cable Number / Code Index Number) を除いた、バイト 1 〜 バイト 3 の MIDI データ本体を納めた配列を表すポインタです。
システム・エクスクルーシブ・メッセージの中間部を除いて、midiMsg[0] がステータス・バイトなので、これを見てメッセージの種類を判別し、何バイト長のメッセージであるかを決定します。
midiMsg[0] が SOX (Start Of eXclusive) であることを確認したら、エクスクルーシブ処理モードに入り、後続するバケット中に EOX (End Of eXclusive) を発見するまでエクスクルーシブ処理モードに留まります。
実は、midiMsg はバッファ中に保存されている (完全な) MIDI パケットの 2 番目のバイトを指しているので、「行儀」が悪いですが、midiMsg[-1] でバッファ中の CN/CIN バイトをアクセスすることができ、その MIDI パケットのタイプを知ることが可能です。
API 関数 USB_PutUsbMidiIn() は、引数として与えられた MIDI メッセージから USB に乗せる 4 バイトの MIDI パケットを生成し、USBMIDI IN バッファ (デバイスからホストへの出力方向のバッファ) に蓄積していく関数です。
第 1 引数 ic は (主に) メッセージ長、第 2 引数 midiMsg は対象のメッセージを格納してある配列を示すポインタ、第 3 引数の cable はケーブル・ナンバー (を 4 ビット左シフトしたもの) です。
USBMIDI IN バッファがいっぱいで、渡された入力メッセージを MIDI パケットに変換したものが IN バッファにおさまらない場合には、IN エンドポイントのバッファ (USB モジュールの内部メモリ) に転送して、メインメモリ中の IN バッファを空ける操作を行います。
それ以外では、自発的には IN エンドポイントのバッファへの転送は行なわず、ユーザが USB_MIDI_IN_Service() 関数を実行することによって始めて IN エンドポイントのバッファへの転送が行なわれます。
「External Mode」をチェックしてあって、レガシー MIDI の UART からの入力が有効な状態では、USB_MIDI_IN_Service() 関数の実行の際に UART 入力側のデータが MIDI パケットに変換され、すでに IN バッファに格納されていたパケットの後に追加されて、IN エンドポイントに転送されます。
したがって、1 〜 3 バイトの「ショート・メッセージ」であれば、お互いのメッセージの「まとまり」が破壊されることなく混合されます。
4 バイト長以上のシステム・エクスクルーシブ・メッセージについては、メッセージ途中で互いに混合するのを避けるメカニズムは存在しておらず、ユーザが面倒を見る必要があります。
下に示すように、USB_PutUsbMidiIn() の第 1 引数 ic の値により動作が異なります。

  • 1 〜 3
  • 4 以上
  • SOX, EOX ステータス・バイト
  • システム・リアルタイム・メッセージのステータス・バイト

ic = 1 〜 3 の場合には、(エクスクルーシブとシステム・リアルタイム・メッセージを除く) 完全な (ステータス・バイトを省略しない) MIDI メッセージが midiMsg 配列に格納されているものとして扱います。 ic の値はメッセージ長を表します。
この場合には、API 関数側でステータス・バイトを見て、CN/CIN バイトをエンコーディングし、4 バイトの MIDI パケットを完成させます。
ic の値が 4 以上の場合は、midiMsg
配列に入っているのはシステム・エクスクルーシブ・メッセージであると解釈し、必要なエンコーディングを行なって、(複数の) MIDI パケットを生成します。
この場合、エクスクルーシブ・メッセージの長さは、一回のバルク IN 転送で可能な範囲に限られます。
デフォルトでは、USBMIDI IN エンドポイント長は 32 となっているので、最大メッセージ長は 24 バイトになります。
それを超える長さのエクスクルーシブ・メッセージを送りたい場合には、次に示すように、ユーザ・プログラムで分割して、バルク転送を複数回に分ける必要があります。
ic の値が、SOX, EOX ステータス・バイトである場合には、midiMsg 配列に格納されているのは、ユーザ・プログラム側で、すでに 3 バイト単位に分割されたエクスクルーシブ・メッセージ (の断片) であると解釈し、CN/CIN バイトのみをエンコーディングし、4 バイトの MIDI パケットを完成させます。
ic の値がシステム・リアルタイム・メッセージのステータス・バイトである場合には、「シングル・バイト・メッセージ」として CN/CIN バイトをエンコーディングします。 このとき、メッセージ・データとして ic の値そのものが使われ、midiMsg
配列の中身は見ません。