Arduino Microはatmega32U4を搭載していて、UnoのようにUSBシリアルのファームを書き換えなくても、MIDIUSBというライブラリを使用することにより、MIDIデバイスとして振る舞う為の方法が用意されています。
このライブラリの使用方法とMIDI信号についてのメモです。
■環境
ホストマシン:iMac (macOS High Sierra 10.13.3)
ボード:Arduino Micro
Arduino IDE:1.8.5
■ライブラリのインストール
メニューから「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理…」を選択します。
ライブラリマネージャが出るので検索窓にMIDIと入力し、候補に出て来る「MIDIUSB」をインストールします。
■ライブラリを使用する
ライブラリを使用するにはヘッダファイルをインクルードするだけでOKです。
1 |
#include "MIDIUSB.h" |
ソースにヘッダファイルをインクルードしてコンパイルでエラーが出なければOKです。
ちなみに、ボードにArduino Unoを選択していると、下記のようなエラーが出ますのでMicroが選択されている事を確認して下さい。
1 2 3 4 5 6 |
/Users/*****/Documents/Arduino/libraries/MIDIUSB/src/MIDIUSB.h:18:2: error: #error MIDIUSB can only be used with an USB MCU. #error MIDIUSB can only be used with an USB MCU. ^ 次のフォルダのライブラリMIDIUSBバージョン1.0.3を使用中:/Users/*****/Documents/Arduino/libraries/MIDIUSB exit status 1 ボードArduino/Genuino Unoに対するコンパイル時にエラーが発生しました。 |
※ソースは、https://github.com/arduino-libraries/MIDIUSB/blob/master/src/MIDIUSB.hをご参照下さい。
■MIDIプロトコルについて
このあたりをざっと読むと大体雰囲気は分かるかと思います。
https://ja.wikipedia.org/wiki/MIDI
https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html
■MIDI信号の送信
「ファイル」→「スケッチ例」→「MIDIUSB」にあるサンプルスケッチを見れば分かりますが、
送信には 構造体midiEventPacket_tにMIDIプロトコルに載せるデータを詰めてsendMIDI( )経由で行います。
Note-Onコマンドの場合は下記の様になります。
1 2 3 |
midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); |
0x90|channel の部分は、上位4bitがNote-Onコマンド、下位4bitがMIDIチャンネルを指定します。
pitchはノート番号(0〜127で半音毎に割り振られています。ピアノの鍵盤だとA0=21〜C8=108。)で、
velocityは強弱の値(0〜127)。
また、Note-Offコマンドの場合は下記の様になります。
1 |
midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity}; |
■MIDI信号の受信
これもサンプルを見れば分かりますが、read( )を使用して受信します。
戻り値の型は送信でも使用したmidiEventPacket_tで、メンバ header がコマンド識別に使えるようになっています。
1 2 3 4 5 6 7 |
midiEventPacket_t rx; do { rx = MidiUSB.read(); if (rx.header != 0) { コマンドをパースして、やりたい処理を行う } } while (rx.header != 0); |
■Timing ClockとSong Position Pointer
MIDIプロトコルで定義されているコマンドにどのようなものがあるかは、上記の「MIDIプロトコルについて」のリンクを参照して頂ければ分かりますが、
その中でもClockとSong Positionはよく扱う(作るものにもよりますが)かと思います。
- 0xF8 Timing Clock (Sys Realtime)
- 0xF2 Song Position Pointer (Sys Common)
●Timing Clockは四分音符1つにつき24回という周期で飛んできます。
Arduinoを接続してDAWを再生すると、どんなテンポにしてもこの周期が変わらない事が確認出来ます。
(DAWのテンポを極端に遅くして(BPM=5とか)、ArduinoにClockイベントが届く度にログを表示するなどしてみると確認し易いかと思います。)
●Song Position Pointerは、例えばDAWで小節や拍を移動したり、ループ再生中にループ先頭に戻った時などに飛んできます。
ポジションを示すデータは、コマンド(0xF2)に続く2バイトのうち7bitずつ使用してトータルで14bitのデータで表されますので、下記のようにパースする必要があります。
1 2 3 4 |
unsigned short ret; ret = (unsigned short)param2; //msb 7 bits ret = ret<<7; ret |= (unsigned short)param1; //lsb 7bits |
※これは pitch wheel(ベンド)(0xE0)も同じ構成になっています。
詳しくは、http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html#Seqに書かれていますが、
24Clocks@4分音符なので、当然ですが16分音符はその1/4=6Clockで、MIDIビートはこの16分音符とされていて、スレーブ機器はこの周期にシンクロすれば良いということになっているようです。そしてSong Positionの値はこのMIDIビート何個分かで表されているようです。
例えば、Song Position= 8 の場合、8 MIDIビート x 6 Clocks = 48 Clocks。
24Clocks@4分音符なので、
(4分音符の)1拍目 = 0 Clock目、
2拍目 = 24 Clock目、
3拍目 = 48Clock目となるので、3拍目の処理準備をすれば良いという事らしいのですが、
結局16分音符で数えて何個目(0オリジン)かで考えた方が分かり易い気がします。。。