Arduino USBMIDIライブラリ

Arduino IDEのライブラリマネージャで、MIDIUSBで検索をすると、3つのライブラリが見つかります。

似たような名前のライブラリですが、それぞれの特色を記載します。

MIDIUSB

MIDIUSBはarduino.ccが提供するatmega32u4またはARMボード用のライブラリです。USBの一般的なArduinoでは一般的なライブラリPluggableUSBをベースに作られています。

MIDIの受信 MidiUSB.read()
MIDIの送信 MidiUSB.send(midiEventPacket_t event)

上記のようなシンプルな関数を持っています。こちらのGithubで公開されています。

USB-MIDI

USB-MIDIはUSBとMIDIの間に"-"が入っています。こちらのGithubで公開されています。

このライブラリは、FortySevenEffects Arduino MIDIライブラリのトランスポート層を使っています。また、基盤となるArduino MIDIUSBライブラリ(上述しています)を使用します。(従って、MIDIUSBで動作するデバイスのみがここで動作します)。

ライブラリをインストールしようとすると、以下のように2つのライブラリ(MIDI LibraryとUSBMIDI)をインストールするように促されます。

こちらのライブラリでは、MIDIイベントにそれぞれにコールバック関数を設定することができます。

void setHandleNoteOn(NoteOnCallback fptr)
void setHandleNoteOff(NoteOffCallback fptr)
etc.

詳細は、Arduino\libraries\MIDI_Library\src\MIDI.hに記載があります。このコールバック関数があるおかげで、MIDIを受信した際、それぞれのメッセージが何であるかを自分のプログラムで判断する必要がありません。
また、USB MIDIだけでなくSerial MIDIに対しても同様の設定が可能です。

USBMIDI

USBMIDIこちらのGithubで公開されています。arduino.cc のページはこちらです。 BlokasLabが開発しているライブラリです。

PluggableUSBライブラリをサポートするデバイスで使えます。 その他のAVR8デバイスは、V-USBライブラリを使用してサポートされます。バージョン1.1.2以降、Digisparkボードが追加されました。

V-USBはこのライブラリをインストールすると、自動的にライブラリに追加されます。AVR8が搭載されたボードを使用する際には、こちらのライブラリがつかるはずです。(未確認)です。ATmega328pで使用するにはハードウェアの追加が必要です。MIDIUSBよりも対応するArduinoボード範囲は広くなります。こちらのページを参照して下さい。関数は以下のように、MIDI受信、MIDI送信が準備されています。

// Stream interface.
virtual int available();
virtual int read();
virtual int peek();
virtual void flush();

// Print interface.
virtual size_t write(uint8_t c);
*
Arduino NanoにUSBMIDIを搭載

ATmega328p搭載のArduino Nanoに回路を追加し、USBMIDIを使えるようにします。回路図は次の通りです。USBのコネクタを追加し、Nanoに接続します。

usbconfig.hをハードウェアに合わせて修正します。usbconfig.hは、Arduinoのスケッチを保存しているディレクトリ、私の場合はDocuments\Arduino\libraries\USBMIDI\srcにあります。メモ帳で開いて以下の3つの行を確認します。もち違った値が記載されていたら修正して下さい。

/* ---------------------------- Hardware Config ---------------------------- */

#ifndef USB_CFG_IOPORTNAME
#define USB_CFG_IOPORTNAME      D
#endif
/* This is the port where the USB bus is connected. When you configure it to
 * "B", the registers PORTB, PINB and DDRB will be used.
これは、USBバスが接続されているポートです。
「B」に設定すると、レジスタPORTB、PINB、DDRBが使用されます。
 */
#ifndef USB_CFG_DMINUS_BIT
#define USB_CFG_DMINUS_BIT      3
#endif
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
 * This may be any bit in the port.
これは、USB D-ラインが接続されているUSB_CFG_IOPORTのビット番号です。
これは、ポートの任意のビットである可能性があります。
 */
#ifndef USB_CFG_DPLUS_BIT
#define USB_CFG_DPLUS_BIT       2
#endif
/* This is the bit number in USB_CFG_IOPORT where the USB D+ line is connected.
 * This may be any bit in the port. Please note that D+ must also be connected
 * to interrupt pin INT0! [You can also use other interrupts, see section
 * "Optional MCU Description" below, or you can connect D- to the interrupt, as
 * it is required if you use the USB_COUNT_SOF feature. If you use D- for the
 * interrupt, the USB interrupt will also be triggered at Start-Of-Frame
 * markers every millisecond.]
これは、USB D +ラインが接続されているUSB_CFG_IOPORTのビット番号です。 
これは、ポートの任意のビットである可能性があります。 
D +も割り込みピンINT0に接続する必要があることに注意してください!
 [他の割り込みを使用することもできます。
以下の「オプションのMCUの説明」のセクションを参照してください。
または、USB_COUNT_SOF機能を使用する場合に必要なため、D-を割り込みに接続できます。
割り込みにD-を使用すると、USB割り込みもミリ秒ごとにフレーム開始マーカーで
トリガーされます。
 */

*
USBMIDIの安定化

USBMIDIのデバイスをPCにつないだ時、USBのデバイスとして認識できる場合とできない場合があります。あまり安定しているとは言い難い状況です。おまじないかもしれませんが、二つの事をやってみました。USBデバイスとして認識できるためには、usbmidi_vsb.cppのDescriptorを返す関数がきち呼ばれているかどうかを確認する必要があります。以下のようにSerial.printを入れてみました。

usbMsgLen_t usbFunctionDescriptor(usbRequest_t * rq)
{
//	Serial.println("usbFunctionDescriptor");

	if (rq->wValue.bytes[1] == USBDESCR_DEVICE)
	{
		Serial.println("USBDESCR_DEVICE");
		usbMsgPtr = (usbMsgPtr_t)deviceDescrMIDI;
		return sizeof(deviceDescrMIDI);
	}
	else if (rq->wValue.bytes[1] == USBDESCR_CONFIG)
	{
		Serial.println("USBDESCR_CONFIG");
		usbMsgPtr = (usbMsgPtr_t)configDescrMIDI;
		return sizeof(configDescrMIDI);
	}
	else if (rq->wValue.bytes[1] == USBDESCR_STRING)
	{
		Serial.println("USBDESCR_STRING");
		if (rq->wValue.bytes[0] == 1)
		{
			const uint8_t *data;
			usbMsgLen_t n = _usbmidi_get_vendor_string(data);
			usbMsgPtr = (usbMsgPtr_t)data;
			return n;
		}
		else if (rq->wValue.bytes[0] == 2)
		{
			const uint8_t *data;
			usbMsgLen_t n = _usbmidi_get_product_string(data);
			usbMsgPtr = (usbMsgPtr_t)data;
			return n;
		}
	}
	return 0;
}

PCがデバイスを認識するためには、usbFunctionDescriptorが複数回呼ばれる必要があります。Serial.Monitorで見ると以下のように表示されます。

USBDESCR_DEVICE
USBDESCR_DEVICE
USBDESCR_COFG
USBDESCR_STRING
USBDESCR_DEVICE
USBDESCR_CONFIG
USBDESCR_CONFIG
USBDESCR_CONFIG
USBDESCR_DEVICE
USBDESCR_DEVICE
USBDESCR_DEVICE
USBDESCR_DEVICE
SDESCR_DEVICE
USBDESCR_DEVICE
USBDESCR_DEVICE
USBDESCR_DEVICE
USBDEC_EIE
USBDESCR_DEVIE
USBDESCR_DEVICE
USBDESCR_DEVICE

ところどころシリアルが文字化けをしています。この情報が得られない場合は接続ができていません。

もう一つおまじないで、割り込みのポーリング間隔を短くします。解説を見ると低スペックのCPUの時は10msecより短くしてはいけない、と書かれています。とすると高スペックであれば短くすることができます。

#define USB_CFG_INTR_POLL_INTERVAL      8

エンドポイント 1 (割り込み入力) でバージョンをコンパイルすると、これがポーリング間隔になります。 値はミリ秒単位であり、低速デバイスの場合は 10 ミリ秒未満であってはなりません。