本稿は、Arduino Advent Calender 2017の21日目の記事として、18日目の記事である
Arduino MIDI Library の使い方の補強・実践編の役割を担う記事です。
通常スタンドアローンで使用する電子楽器に対し、Arduino MIDIを用いて外部からアクセス・連携することによって何が実現できるのか、どういった面白い世界が広がるのかをまとめます。加えて、こういった楽器固有の機能を持つ楽器に対する、MIDIでの機能ハックのためにArduino MIDI Libraryはどうやって使われるかを実例とともに紹介します。
About Electone
Electoneとは
図はヤマハ株式会社の紹介ページより。
Electoneは、ヤマハ株式会社が製造発売する電子オルガンのことです。上下鍵盤に加え、足で演奏するペダル鍵盤のついた、合計3段の鍵盤を持つ電子楽器です。現行の最新機種であるELS-02シリーズでは、
AWM音源は986種類もの音色を内蔵。管楽器や弦楽器だけでなく、民族楽器やコーラスボイスなど、高品質で多彩な音色が充実しています。(公式ページ)
といった形で、ジャンルを問わない音色を発生できるタイプの楽器で、発生する音色の組み合わせを予めレジストレーションしておくことができます。また、予め作っておいたリズムパターンを再生することも可能です。従って、100万円もかかるような高価な点を除けばオールラウンドなジャンルが演奏できるなかなか便利なやつです。
機能全てを説明しきることは難しいので、この辺で。
Electone × MIDI
図は『 ELS-02/02C/02X 取扱説明書』を改変して作成。
エレクトーンには、外部の機器を接続するための端子類がついており、その一覧は上図に示したとおりです。特に9, 10はElectoneにMIDI接続するための端子類であり、ここを通してElectoneをMIDIデバイスとして使用することが出来ます。
エレクトーンユーザのための実験
エレクトーンには詳しいが技術には詳しくないタイプの人は、このMIDI端子からどれほどの信号が出ているか、受け取ることが出来るかを次のような実験から確かめることが出来るでしょう。面倒なので以前部内誌に投稿したものをコピペするので、しばらく文体が変わります。なお、エレクトーンをやってない人にはあまり関係ないので読み飛ばしてください。
まずは、エレクトーンについているMIDI端子を使ってみることからはじめ、MIDIによってどのようなことができるのか体感してみよう。ここでは、エレクトーン2台を連携させてみる、ということをまずやってみる。エレクトーンが自宅に2台も無いという場合は、例えばレッスン室レンタルなどを利用するか、場所を適当に探すかするのが良いが、こういうことが出来るかと読むだけでも良いかと思う。
まず準備として、MIDIケーブルを適当なところから入手し(例えばAmazonで「MIDIケーブル」と検索をかければいくらでも出てくる)、MIDIケーブルを、片方はエレクトーンAのMIDI OUT端子、もう片方はエレクトーンBのMIDI IN端子につなぐ。これで準備は終わりである。
では、MIDI OUT側のエレクトーンAで、適当な演奏をしてみよう。すると、もう一台のエレクトーンBでも全く同じ音階で演奏されるはずである。エレクトーンBのレジストデータを変えれば、様子がよくわかるはずである。
演奏する以外にも、例えばエレクトーンAでリズムをスタートさせればエレクトーンBからもリズムがスタートするし、エレクトーンA側でメモリを変えれば同じ番号のメモリにエレクトーンBも設定されるし、エレクトーンAのレジストをいじれば同じようにエレクトーンBのレジストも変わるはずである。このように、エレクトーンのMIDI OUT端子からは、いまエレクトーンで行っているほとんど全ての操作・演奏情報が出力されている。上鍵盤ボイス1の音色のエフェクト・パラメータをいじってやるという細かい作業でさえ、エレクトーンBでも全く同じように再現しているはずである。
また、MIDI IN側に対して適当なメッセージを送ってやることでエレクトーン上で行う操作を外部から行うこともできる。本稿で取り扱う技術は、これらの出力される情報をうまく使ったり、うまいメッセージをエレクトーンに送ってやることで全て実現しており、これによってエレクトーン1台だけでは難しい表現を容易に行えるようにしている。
###エレクトーン中のMIDI関係のコンフィグ
ところで、MIDI OUTからエレクトーンの操作の全てが出力されるのはいいが、これでは使い勝手が悪い。例えば、リズムスタートなどの操作だけを送信して、それ以外は送らないように出来ないのであろうか。この出力内容の操作は、エレクトーンの設定を変えることによってある程度可能である。
図は『 ELS-02/02C/02X 取扱説明書』より引用
エレクトーンのUTILITYボタンから、右上のメニューより「MIDI」を選んだ時、上図のようなMIDIページが表示される。ここで、上/下/ペダル鍵盤の出力チャンネルや、一部のMIDIメッセージの出力の有無の変更をすることが可能である。
まずは上部の「アウトプット」欄から説明する。MIDIメッセージには16の別個の出力先(チャンネル)があり、それぞれに対応した音が出力されるようになっている。エレクトーンでは、MIDI INに入ってきたメッセージについて、チャンネル1は上鍵盤の演奏、チャンネル2は下鍵盤、チャンネル3はペダル鍵盤として解釈する。一方で、MIDI OUTから出て行くメッセージは、標準的にはMIDI INの時と同じチャンネルから出力されるが、このページから出力先を変更することで、違うチャンネルのメッセージとして音を出すことができる。ここがもっとも重要な選択項目なので、後で実践を交えて再度解説する。
「MIDIアウトフィルター」では、ホリゾンタルタッチ、アフタータッチ、セカンドエクスプレッション、リズムの再生についてMIDI OUTから演奏情報を出力するかしないかを選択できる。ここの項目でONに選択された演奏情報は出力されない(したがって、繋がれているエレクトーンなどでもその演奏情報は反映されない)。「インターナル/エクスターナル」では、本来エレクトーンで演奏操作を行う内容について、自分の今の演奏操作をそのまま用いる「インターナル」で行うか、外部から入ってきた演奏情報を用いる「エクスターナル」にするかを選べる。エクスプレッションはEL時代を知る人間なら馴染みがある項目だと思われる。エクスターナルにしていると、自分の右足の状態にかかわらず音が出るようになり、特に外からMIDIで演奏情報を入れている場合は、そこで設定された音量に合わせられる。リード1は、実はここでエクスターナルを選択することで、外部から入ってくるMIDI演奏情報のうち、4チャンネルに入ってくるものをリード1の音で演奏できるようにする。この時は、設定したエレクトーンの上鍵盤で演奏している内容でリード1の音は流れない。「同期」については難しいため省略。
では、先ほど宣言したとおり「アウトプット」欄について実践し、その動作を確かめてみよう。たとえば、エレクトーンAの上鍵盤の出力先をチャンネル3にして演奏してみよう。すると、エレクトーンBでは3ch = ペダル鍵盤の音として演奏が流れるはずである。下鍵盤、ペダル鍵盤でも同様のことが可能である。これはたとえば部内コンなどで、ELS-01CやD-DeckからELS-02Cにコードをつなぎ、出力先をELS-02Cで演奏しない鍵盤に割り当てることで、01シリーズの音しか出せないエレクトーンからでも02シリーズの音が演奏できるようになる、という使い道がある。ついでに、上鍵盤の出力先をチャンネル4にし、MIDIメッセージの受信側でリード1のインターナル/エクスターナルをエクスターナルにして演奏してみよう。演奏がそのまま受信側のリード1から再生されるはずだ(なお、この時エレクトーンBの上鍵盤を演奏してもリード1の音は流れない)。
これでも十分新しい使い道が開拓できるのだが、エレクトーン演奏者にとってさらに便利な応用がある。今度は上鍵盤の出力先をチャンネル8にしてみよう。今度は、エレクトーンAのみからしか音が出なくなったはずである。しかし、この状況でもエレクトーンAでリズムスタートを押せばエレクトーンBでもリズムが始まるはずである。このように、出力先のチャンネルとして1, 2, 3(, 4)以外を選ぶことで、他のエレクトーンから鳴らしたくない鍵盤の演奏のみを除外してやることができる。これを使うと、より柔軟性の高い演奏応用が可能である。通常アンサンブルをするときは、シーケンスリズムを再生することができるのは1人だけで、他の演奏者からは今の拍も見えないし何より音変えを自分でしなければならない。MIDIケーブルでつなぎ、この方法で出力先から音が出ない状況を作ってやると、リズムの再生だけが同期してできるようになるため、2台のエレクトーンで音変えが必要なくなる。また、通常リズム作成時はメインドラムとアドドラムの2キットしか使用できないが、この方法で2台からリズムを鳴らすことでそれぞれ2キットの合計4キットのリズムキットによるリズムが作成・再生可能になる。
具体的なMIDIメッセージ
さて、エレクトーンからどういったMIDIメッセージが出てくるのか/入れられるのかは『 ELS-02/02C/02X MIDIリファレンス』から参照することが可能です。(エレクトーンに馴染みのない方にはあまりわからないかもしれませんが)、以下に代表的な機能群についてメッセージの対応を示します。なお、数字の末尾のHは、数字が16進数で表現されていることを明示するものであり、以後も同様とします。逆に、Hをつけないものは10進数表現です。
- ノートオン・ノートオフ 9$n$Hから始まるノートオンによって行う。ノートオフも9$n$Hから始まる。
- アフタータッチ D$n$Hから始まるチャンネルプレッシャーによって行う。つまり、実はエレクトーンの鍵盤のアフタータッチは各鍵盤ごとではなく、上鍵盤/下鍵盤/ペダル鍵盤のそれぞれで共通になっている。なお、この事実は次のような実験によって明らかになる: 例えば上鍵盤で、アフタータッチがよく効くように設定した上で、低い音の鍵盤を非常に小さく押したまま、高い音の鍵盤のアフタータッチを大きく変化させてみると良い。低い音も高い音と同じように音が大小するのがわかる。
- レジストチェンジ エレクトーンが送信側となるときは、CFHから始まるプログラムチェンジ(つまり16ch用のメッセージである。コントロールは一般に16chのメッセージとして出力されている)によって行われる。続くデータバイトに、レジスト番号が入っている。
- ホリゾンタルタッチ 上鍵盤/下鍵盤のみ、E$n$Hから始まるピッチベンドとして出力される。
- エクスプレッションペダル BFH, 0BH, $nn$Hによるコントロールチェンジによって行う。
- セカンドエクスプレッションペダル 通常BFH, 04H, $nn$Hによるコントロールチェンジによって行う。MIDIコントロールの設定によって、E$n$Hから始まるピッチベンドとして出力するように変更することも可能。
- ライトフットスイッチ これに限っては、対応するメッセージはないようである。
- レフトフットスイッチ システムエクスクルーシブメッセージ F0H, 43H, 70H, 70H, 40H, 45H, 7FH(00H), F7H でON(OFF)である。
- ニーレバー システムエクスクルーシブメッセージ F0H, 43H, 70H, 70H, 40H, 47H, 7FH(00H), F7H でON(OFF)である。
- テンポ変更 システムエクスクルーシブメッセージ F0H, 43H, 70H, 70H, 40H, 50H, $nn$H, $mm$H, F7H で変更。ここで、実際に設定されるテンポは$4 \times mm + nn$
- リズムスタート・ストップ システムリアルタイムメッセージ FAH(スタート)/FCH(ストップ) によって通常行われる。システムエクスクルーシブメッセージF0H, 43H, 60H, 7AH, F7H(スタート)/F0H, 43H, 60H, 7DH, F7H(ストップ)もあるが、これは「リズムスタート/ストップボタンを押した」というメッセージで直接リズムスタート/ストップをするものではない(結果的に同じ動作はする)。
- SEQボタン SEQ $N$ ボタンについて、システムエクスクルーシブメッセージ F0H, 43H, 70H, 78H, 41H, 6$N$H, 01H(00H), F7H でON(OFF)である
これらからわかる通り、
- それなりの機能については、MIDIの標準的なもので対応可能
- エレクトーン独自の機能については、システムエクスクルーシブで対応
という形です。エレクトーンと外部機器の連携を試みた場合、特に後者の部分をよく使うことが多いため、MIDI システムエクスクルーシブをいかに扱うかについて勉強する必要があります。
Electone × Arduino MIDI
Arduino MIDIを用いて、エレクトーンと連携するというのは、いくつかのケースに場合分けが出来ます。
- 他の(本来MIDI接続がすぐに出来る状態ではない)機器・センサ類をエレクトーンに繋いで使いたい。
- つなぐ相手もエレクトーンやMIDIデバイスなんだけど、エレクトーン用にチューニングしたプロトコル変換を噛ませたい
- (かなり特殊)PC <- Arduino MIDI -> エレクトーンをArduino Leonardoを使って楽にやる
といった形になってきます。これらの試みの中で、特に興味深いものを次の実例集に挙げます。具体的には、
- YAMAHA KX5 のエレクトーン対応化
- PlayStation 2コントローラのMIDIデバイス化・エレクトーン接続
- エレクトーン + Arduino Leonardoによる USB HID化応用
- eVY1 モジュールを用いたエレクトーンの演奏によるボーカロイド歌唱
という例を取り扱い、先日投稿したArduino MIDI Libraryの実例集を兼ねたい思います。
実例集
以下、近いうちにきちんとした解説を加筆します。
外部機器 -> エレクトーン
YAMAHA KX5 のエレクトーン対応化
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
void handleNoteOn(byte channel, byte pitch, byte velocity){
MIDI.sendNoteOn(pitch, velocity, 2);
}
void handlePC(byte ch, byte no){
MIDI.sendProgramChange(no, ch);
}
void handleNoteOff(byte channel, byte pitch, byte velocity){
MIDI.sendNoteOff(pitch, velocity, 2);
}
void handlePitchBend(byte channel, int bend){
MIDI.sendPitchBend(-bend, 2);
}
void handleCC(byte channel, byte number, byte value){
static byte data[] = {0xf0, 0x43, 0x70, 0x70, 0x40, 0x45, 0x7f, 0xf7};
if( number == 0x40 ){
data[6] = value; //switch on/off state values happen to equal to the "value" variable data.
MIDI.sendSysEx(sizeof(data)/sizeof(data[0]) ,data, true);
}
}
void setup()
{
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandlePitchBend(handlePitchBend);
MIDI.setHandleControlChange(handleCC);
MIDI.setHandleProgramChange(handlePC);
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI.turnThruOff();
}
void loop(){
MIDI.read();
}
PlayStation 2コントローラのMIDIデバイス化・エレクトーン接続
#include <GPSXClass.h>
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
Serial.begin(38400);
PSX.mode(PSX_PAD1, MODE_ANALOG, MODE_LOCK);
PSX.motorEnable(PSX_PAD1, MOTOR1_ENABLE, MOTOR2_ENABLE);
// Poll current state once.
PSX.updateState(PSX_PAD1);
MIDI.begin();
}
void loop()
{
PSX.updateState(PSX_PAD1);
if (PRESSED_CIRCLE(PSX_PAD1) || PRESSED_LEFT(PSX_PAD1)) {
MIDI.sendNoteOn(42,127,1);
} else if(PRESSED_R1(PSX_PAD1) || PRESSED_L1(PSX_PAD1)){
MIDI.sendNoteOn(43,127,1);
} else{
MIDI.sendNoteOff(42, 1);
MIDI.sendNoteOff(43, 1);
}
}
エレクトーン -> 外部機器
エレクトーン + Arduino Leonardoによる USB HID化応用
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
const byte LeftFootSwitch[] = {0xf0, 0x43, 0x70, 0x70, 0x40, 0x45, 0x7f, 0xf7};
void handleNoteOn(byte channel, byte pitch, byte velocity){
MIDI.sendNoteOn(channel, pitch, velocity);
}
void handleSysEx(byte data[], unsigned int n){
int i;
if(n != sizeof(LeftFootSwitch) / sizeof(LeftFootSwitch[0]) )
return;
for(i = 0; i < n; i++)
if(data[i] != LeftFootSwitch[i])
return;
Keyboard.print("\n");
}
void handleStart(){
Keyboard.print("\n");
}
void setup(){
Keyboard.begin();
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleSystemExclusive(handleSysEx);
MIDI.setHandleStart(handleStart);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop(){
MIDI.read();
}
eVY1 モジュールを用いたエレクトーンの演奏によるボーカロイド歌唱
#include <avr/pgmspace.h>
#include <MIDI.h>
#include <SPI.h>
#include <SD.h>
#include <SoftwareSerial.h>
SoftwareSerial softSerial(2,3);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);
MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiOut);
File myFile;
int notes;
const int led = 9;
boolean led_state, musicStart, reset;
byte correctPitch[32], lastNote, dist;
void handleNoteOn(byte channel, byte pitch, byte velocity){
if(pitch < 48){
handleStart();
} else if(musicStart){
if(pitch == correctPitch[dist]){
MIDI.sendNoteOn(pitch, velocity, channel);
lastNote = pitch;
dist++;
}
} else {
MIDI.sendNoteOn(pitch, velocity, channel);
lastNote = pitch;
}
}
void handleNoteOff(byte channel, byte pitch, byte velocity){
MIDI.sendNoteOff(pitch, velocity, channel);
}
void handleStart(void){
if(!musicStart){
musicStart = true;
midiOut.sendRealTime(midi::Start);
}
MIDI.sendNoteOff(lastNote, 0, 1);
callNextLyrics();
}
void handleStop(void){
midiOut.sendRealTime(midi::Stop);
setup();
}
void setup() {
const byte rstNSX1[] = {0xf0, 0x43, 0x79, 0x09, 0x01, 0x01, 0x00, 0xf7};
led_state = true, musicStart = false; lastNote = 0, reset = true;
// initialize the digital pin as an output.
pinMode(led, OUTPUT); //for Debug
pinMode(10, OUTPUT); //for SD Card
digitalWrite(led, HIGH);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleStart(handleStart);
MIDI.setHandleStop(handleStop);
MIDI.begin(1);
MIDI.turnThruOff();
midiOut.begin(1);
midiOut.turnThruOff();
//eVY1 RESET
MIDI.sendSysEx(sizeof(rstNSX1)/sizeof(rstNSX1[0]), rstNSX1, true);
delay(5000);
SD.begin(4);
myFile.close();
while(!(myFile = SD.open("SAYONARA.TXT")));
callNextLyrics();
reset = false;
}
void loop() {
MIDI.read();
digitalWrite(led, musicStart);
}
void callNextLyrics(){
static byte buf[136] = {0xf0, 0x43, 0x79, 0x09, 0x00, 0x50, 0x10};
int i, c;
dist = 0;
if(musicStart == true || reset == true){
for(i = 7; (c = myFile.read()) != -1; i++){
if(c == '\n')
break;
else
buf[i] = c;
}
if(i != 7){
buf[i++] = 0x00;
buf[i] = 0xf7;//footer
MIDI.sendSysEx(sizeof(buf)/sizeof(buf[0]), buf, true);
}
correctPitch[0] = 0;
for(i = 0; (c = myFile.read()) != -1; i++){
if(c == '\n')
break;
else
correctPitch[i] = c;
}
}
}