Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
eVY1シールド

概要

ヤマハのNSX-1にeVY1を搭載した、eVY1シールドの実験です。

音を出したり声を出したりしていきます。MIDI素人だし、いろいろ機能があるので少しずつ追加していきます。MIDIについては勉強中なので、MIDIについてもメモを残していくことになると思います。

目的

eVY1シールドをArduinoから制御するためのプログラムを少しずつ作っていきます。

いちばんの問題は、歌詞や楽譜データをどのように作成するのかということのような気がしてきました。このあたりを整備しないとエンジンが立派でもさみしいアプリケーションしかできません…逆に、このあたりを解決するすごいアイデアがあるととても面白いものができるのではと思います。

あまりいいアイデアがないので、アイデアがある人が使えるように下回りを整備していきます。

お断り

MIDIの世界では、16進数は数字の後ろにHをつけて表すようです。このページでは、C/C++言語の表記に従い、数字の前に0xをつける記法を用います。

USB-MIDI音源

Arduinoに接続する前に、USB-MIDI音源として直接PC(Windows7)に接続してみました。ドライバがインストールされて、

eVY1 MIDIデバイスとして認識されています。

Windows7のWindows Media Playerでは、MIDI音源を選択することはできないようなので(このあたりは自信なしです)、MIDISelectorなどのツールを使ってMIDI音源をeVY1に変更すると、MIDI音源として利用することができるようになります。

Arduinoから使う

eVY1シールドは、シリアル接続されたMIDIデバイスです。31250bpsで接続してしまえば、あとは、Arduinoのシリアルライブラリを利用してメッセージを送信すれば、音を鳴らしたり歌を歌わせたりすることができます。

シリアル通信で一つずつメッセージを送信していくので、厳密に同時に音を鳴らし始めることはできません。

Arduinoの電源オンもしくはリセットを行った後、eVY1シールドのリセットが行われます。リセットには数秒かかるため、setup()の中などでeVY1シールドに対して何らかの初期化を行いたい場合は、delay()を使うなどして、リセットが終わるのを待たないと、必要な設定が行われないので注意してください。

チャネル2(MIDIメッセージ上は0x01)番から16(同0x0f)番でGeneral Midi音源が利用できるようです。チャネル1は、eVocaloid専用のようで、チャネル1にNote Onイベントを送ると声が出ます。デフォルトは「ア」のようです。なので、何も設定しないでチャネル1を利用すると、「アアアアアアー」となります。

データシート

NSX-1のデータシートには、MIDIのコマンドが掲載されています。基本的には一般のMIDIと同じです(一部用語が異なるようです)。eVocaloid用の発音記号も掲載されているので参考にしてください。株式会社アイデステクノロジ社のページにも情報が載っています。こちらも参考になります。MIDIの仕様書はこちらのようです。

MIDIメッセージは、1個のステータスバイトとその後に複数のデータバイトが続くバイト列です。ステータスバイトは最上位ビットが1、データバイトは最上位ビットが0です。つまり、ステータスバイトは0x80から0xff、データバイトは0x00から0x7fまでとなります。

ステータスバイトがコマンドで、データバイトがサブコマンドあるいはパラメータのようなイメージです。

0x80から0xefまでのメッセージは、「チャネルメッセージ」と呼ばれるメッセージで、ステータスバイトの下位4ビットにチャネル番号を設定します。例えば、Note Onのステータスバイトは0x8nと書かれています。このとき、nはチャネル番号を表します。例えば、チャネル1を指定する場合は、nに0を指定します。MIDIのチャネル番号は通常1から16と表記しますが、MIDIメッセージ上は0x0から0xfのため、1を引いた数を設定します。

MIDI/eVY1シールドでは、以下のメッセージが規定されています(作業中なのですべてを網羅しているわけではありません)。

ステータスバイト 第1データバイト 第2データバイト
Channel Message
Channel Voice Message
Note Off 音を止める 0x8n Note No. Velocity
Note On 音を出す 0x9n Note No. Velocity
Polyphonic Key Pressure キーをさらに押す 0xan Note No. Pressure
Control Change
Bank Select MSB/Bank Select LSB 音源を切り替える 0xbn 0x00/0x20 MSBの値
0x00: Normal
0x7f: Drumkit
Modulation Depth ビブラートの深さを変える 0xbn 0x01 Depth
Portamento Time ポルタメントが有効なときのピッチの変化速度 0xbn 0x05 Time
Main Volume 音量調整 0xbn 0x07 Volume
Portamento ポルタメントの有効・無効を切り替える 0xbn 0x41 0x00-0x3f: Off
0x40-0x7f: On
Program Change 音色を変える 0xcn Voice No. 0x00
Channel Pressure キーをさらに押す 0xdn Pressure 0x00
Pitch Bend ピッチを変える 0xen Pitch Bend MSB Pitch Bend LSB
Channel Mode Message
All Sound Off チャネルの音を消す 0xbn 0x78 0x00
Reset All Controller 初期化する 0xbn 0x79 0x00
All Note Off チャネルに対してNote Offを出す 0xbn 0x7b 0x00
Omni Off チャネルを区別しない 0xbn 0x7c 0x00
Omni On チャネルを区別する 0xbn 0x7d 0x00
Mono 単音 0xbn 0x7e 0x00-0x10
Poly 和音 0xbn 0x7f 0x00
System Message
System Exclusive Message
System Common Message
System Realtime Message

Note Off/Note On

MIDI音源から音を出したり、止めたりするメッセージです。Note Onで音を出します。これは、鍵盤を押すのと同じです。その後、Note Offで鍵盤を離します。

メッセージ ステータスバイト データバイト(1バイト目) データバイト(2バイト目) 説明
Key Off 0x8n ノート番号 ベロシティ チャネルnの「ノート番号」のキーを、速さ「ベロシティ」で離します。おそらくベロシティは関係ありません。
Key On 0x9n ノート番号 ベロシティ チャネルnの「ノート番号」のキーを、速さ「ベロシティ」で押します。ベロシティを0にすると、Key Offと同じ効果が出ます。

声を出す

シールドから声を出すには、以下の2つのステップが必要なようです。

  1. 歌詞を登録する
  2. チャネル1にnote onイベントを送信する。

歌詞を登録するのは以下のSysExコマンドを利用します。

F0 43 79 09 00 50 1m dd ... F7

mは0のとき歌詞の置き換え、1のとき歌詞の追加です。ddがデータの並びで、eVocaloid用のフォネティックコードです。詳細はデータシートを参照してください。複数のフォネティックコードを同時に登録することができます。この場合、1音ごとに「,」で区切る必要があります。

音を出す

チャネル1以外(チャネル2から16)のチャネルに対して、Note Onメッセージを送信すると、そのチャネルに割り当てられた音色(楽器)で音が鳴ります。音色は後述するProgram Changeメッセージで変更することができます。

音階

Note Off/Note Onメッセージのノート番号が音階です。0x00が最も低く、0x7fが最も高い音です。0x3cが真ん中の「ド」に相当します。値が1増えると半音上がります。

Control Change

MIDIのさまざまな機能を切り替えるためのメッセージです。

メッセージ ステータスバイト データバイト(1バイト目) データバイト(2バイト目) 説明
Control Change 0xBn Control Number Data データバイト(1バイト目)で機能(Control Number)を指定し、データバイト(2バイト目)にパラメータを設定する。

Bank Select MSB/Bank Select LSB

Bank Select MSB/Bank Select LSBを使って、音源を音源を切り替えることができます。データシートによると、Normalに加えてReal Acoustic Sounds(RAS)とDrumkitも利用できるように書いてありますが、RASを選択することはできませんでした。これは、ヤマハのニュースリリースに書いてある、"「Real Acoustic Sound」と「eVocaloid™」を同時に使用することはできません。事前にどちらの音源をプリインストールするか選択する必要があります。"と関連があると思います。

Bank selectを行った後は、Program Changeを利用して音源を設定する必要があるようです。

Modulation Depth

Modulation Depthを使うと、ビブラートの深さを変えることができます。

Portament/Portament Time

Portamentを使うと、前の音と次の音の間を滑らかに変化させることができます。

Main Volume

ボリュームを調整します。音を出しているときにこのメッセージを送信すると、音が途切れるようです。

Panpot

ステレオの左右のバランスを調整します。2バイト目のデータバイトが0のとき左、64のとき中央、127のとき右となるようです。

Program Change

音色を切り替えるには、program changeイベントを送信します。デフォルトはピアノです。

ソース

MIDIやeVocaloidを扱うライブラリとそのライブラリを試すためのサンプルプログラムです。順序が逆の気もしますが、サンプルプログラム・ライブラリの順に記載しています。

サンプルプログラムを動作させるには、ライブラリをサンプルプログラムと同じディレクトリに配置します。そのうちきちんとライブラリ化します。

スケッチをアップロードした後は、変な状態に陥るようなので、リセットボタンを押す必要があるみたいです。

GitHubのリポジトリはこちら

サンプルプログラム

あいうえおを繰り返す

#include "eVY1.h"
#include "eVY1Data.h"
#include "midiData.h"

eVY1 evy1(&Serial);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(31250);
  delay(5000); // wait for the shield to wake up
}

void loop () {
  int channel = 0;
  int delay1 = 400, delay2 = 100;
  
  evy1.eVocaloid(0, "a,i,M,e,o");
  
  evy1.noteOn(channel, 0x3c, 0x3f);
  delay(delay1);
  evy1.noteOff(channel, 0x3c, 0x7f);  
  delay(delay2);
  evy1.noteOn(channel, 0x3e, 0x3f);
  delay(delay1);
  evy1.noteOff(channel, 0x3e, 0x7f);  
  delay(delay2);
  evy1.noteOn(channel, 0x40, 0x3f);
  delay(delay1);
  evy1.noteOff(channel, 0x40, 0x7f);  
  delay(delay2);
  evy1.noteOn(channel, 0x41, 0x3f);
  delay(delay1);
  evy1.noteOff(channel, 0x41, 0x7f);  
  delay(delay2);
  evy1.noteOn(channel, 0x43, 0x3f);
  delay(delay1);
  evy1.noteOff(channel, 0x43, 0x7f);  
  delay(delay2);
}

MIDIの音色をすべて鳴らす

#include "eVY1.h"
#include "eVY1Data.h"
#include "midiData.h"

eVY1 evy1(&Serial);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(31250);
  delay(5000); // wait for the shield to wake up
}

void loop () {
  int channel1 = 1;
  int channel2 = 2;

  evy1.controlChange(channel2, MIDI_CC_BANK_SELECT_MSB, BANK_SELECT_DRUMKIT);
  evy1.controlChange(channel2, MIDI_CC_BANK_SELECT_LSB, 0);
  evy1.programChange(channel2, 0);
  
  for (int i = 0; i < 0x7f; i++) {
    evy1.programChange(channel1, i);
    
    for (int j = 0; j < 0x7f; j++) {
      evy1.noteOn(channel1, j, 0x3f);
      evy1.noteOn(channel2, j, 0x3f);
      delay(150);
      evy1.noteOff(channel1, j, 0x7f);
      evy1.noteOff(channel2, j, 0x7f);
    }
  }
  
}

ビブラートを変える

#include "eVY1.h"
#include "eVY1Data.h"
#include "midiData.h"

eVY1 evy1(&Serial);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(31250);
  delay(5000); // wait for the shield to wake up
}

void loop () {
  int channel = 1;
  
  for (int i = 0; i < 0x7f; i += 8) {
    evy1.controlChange(channel, MIDI_CC_MODULATION_DEPTH, i);
    evy1.noteOn(channel, 0x3c, 0x3f);
    delay(1000);
    evy1.noteOff(channel, 0x3c, 0x7f);
    delay(300);
  }
}

ポルタメントを使う

#include "eVY1.h"
#include "eVY1Data.h"
#include "midiData.h"

eVY1 evy1(&Serial);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(31250);
  delay(5000); // wait for the shield to wake up
}

void loop () {
  int channel = 1;
  
  evy1.controlChange(channel, MIDI_CC_PORTAMENTO, 0x7f); // Portamento On
  
  for (int i = 0; i < 0x7f; i += 4) {
    evy1.controlChange(channel, MIDI_CC_PORTAMENTO_TIME, i); // Portament Time
    evy1.noteOn(channel, 0x3c, 0x3f);
    delay(500);
    evy1.noteOff(channel, 0x3c, 0x7f);
    evy1.noteOn(channel, 0x43, 0x3f);
    delay(500);
    evy1.noteOff(channel, 0x43, 0x7f);
  }
}

パンポットを使う

#include "eVY1.h"
#include "eVY1Data.h"
#include "midiData.h"

eVY1 evy1(&Serial);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(31250);
  delay(5000); // wait for the shield to wake up
}

void loop () {
  int channel = 1;
  evy1.programChange(channel, 0x39);
  
  for (int i = 0; i < 0x7f; i++) {
    evy1.controlChange(channel, MIDI_CC_PANPORT, i); // Portament Time
    evy1.noteOn(channel, 0x3c, 0x3f);
    delay(50);
    evy1.noteOff(channel, 0x3c, 0x7f);
    delay(50);
  }
}

ライブラリ

midiClassとeVY1という二つのクラスの作成を開始しました。midiClassは一般的なMIDIコマンドを扱うクラスで、eVY1はeVY1シールドに特有のコマンドを扱うクラスです。eVY1はmidiClassを継承して作成しています。midiData.hとeVY1Data.hには、データバイトの定義を書いています。必要に応じて利用してください。

メソッド

現状実装してあるMIDIメッセージとそれに対するメソッドを以下に示します。

MIDIメッセージ 説明 メソッド(シグネチャ)
Note Off 音を止める。 void midiClass::noteOff(uint8_t channelNo, uint8_t keyNo, uint8_t velocity)
Note On 音を出す。 void midiClass::noteOn(uint8_t channelNo, uint8_t keyNo, uint8_t velocity)
Polyphonic Key Pressure キーをさらに押し込む。 void midiClass::polyphonicKeyPressure(uint8_t channelNo, uint8_t keyNo, uint8_t data)
ControlChange 音質などの要素を制御する。 void midiClass::controlChange(uint8_t channelNo, uint8_t data1, uint8_t data2)
Program Chage 音色を変える void midiClass::programChange(uint8_t channelNo, uint8_t voiceNo)
Channel Pressure キーをさらに押し込む。 void midiClass::channelPressure(uint8_t channelNo, uint8_t data)
Pitch Bend 音のピッチを変更する。 void midiClass::pitchBend(uint8_t channelNo, uint8_t msb, uint8_t lsb)
eVocaloid 発音データを送信する。 void eVY1::eVocaloid(uint8_t mode, char *lyrics)

midiClass.h

#ifndef MIDI_CLASS_H
#define MIDI_CLASS_H

#include <stdint.h>


class Stream;

class midiClass {
  public:
    midiClass(Stream *port);
    void noteOff(uint8_t channelNo, uint8_t keyNo, uint8_t velocity);
    void noteOn(uint8_t channelNo, uint8_t keyNo, uint8_t velocity);
    void polyphonicKeyPressure(uint8_t channelNo, uint8_t keyNo, uint8_t data);
    void controlChange(uint8_t channenNo, uint8_t data1, uint8_t data2);
    void programChange(uint8_t channelNo, uint8_t voiceNo);
    void channelPressure(uint8_t channelNo, uint8_t data);
    void pitchBend(uint8_t channelNo, uint8_t msb, uint8_t lsb);

  protected:
    Stream *serialPort;
};

#endif /* MIDI_CLASS_H */

midiData.h

#define MIDI_CC_BANK_SELECT_MSB     0x00
#define MIDI_CC_MODULATION_DEPTH    0x01
#define MIDI_CC_PORTAMENTO_TIME     0x05
#define MIDI_CC_DATA_ENTRY_MSB      0x06
#define MIDI_CC_CHANNEL_VOLUME      0x07
#define MIDI_CC_PANPORT             0x0a
#define MIDI_CC_EXPRESSION          0x0b
#define MIDI_CC_BANK_SELECT_LSB     0x20
#define MIDI_CC_DATA_ENTRY_LSB      0x26
#define MIDI_CC_SUSTAIN_DAMPER      0x40
#define MIDI_CC_PORTAMENTO          0x41
#define MIDI_CC_SOSTENUTO           0x42
#define MIDI_CC_SOFT_PEDAL          0x43
#define MIDI_CC_HARMONIC_CONTENT    0x47
#define MIDI_CC_RELEASE_TIME        0x48
#define MIDI_CC_ATTACK_TIME         0x49
#define MIDI_CC_BRIGHTNESS          0x4a
#define MIDI_CC_DECAY_TIME          0x4b
#define MIDI_CC_VIBRATO_RATE        0x4c

midiClass.cpp

#include "Arduino.h"
#include "midiClass.h"

/*
 * Constructor
 */
midiClass::midiClass(Stream *port) {
  serialPort = port;
}

/*
 * Public methods
 */
void midiClass::noteOff(uint8_t channelNo, uint8_t keyNo, uint8_t velocity) {
  serialPort->write(0x80 | channelNo);
  serialPort->write(keyNo);
  serialPort->write(velocity);
}

void midiClass::noteOn(uint8_t channelNo, uint8_t keyNo, uint8_t velocity) {
  serialPort->write(0x90 | channelNo);
  serialPort->write(keyNo);
  serialPort->write(velocity);
}

void midiClass::polyphonicKeyPressure(uint8_t channelNo, uint8_t keyNo, uint8_t data) {
  serialPort->write(0xa0 | channelNo);
  serialPort->write(keyNo);
  serialPort->write(data);
}

void midiClass::controlChange(uint8_t channelNo, uint8_t data1, uint8_t data2) {
  serialPort->write(0xb0 | channelNo);
  serialPort->write(data1);
  serialPort->write(data2);
}

void midiClass::programChange(uint8_t channelNo, uint8_t voiceNo) {
  serialPort->write(0xc0 | channelNo);
  serialPort->write(voiceNo);
}

void midiClass::channelPressure(uint8_t channelNo, uint8_t data) {
  serialPort->write(0xd0 | channelNo);
  serialPort->write(data);
}

void midiClass::pitchBend(uint8_t channelNo, uint8_t msb, uint8_t lsb) {
  serialPort->write(0xe0 | channelNo);
  serialPort->write(lsb);
  serialPort->write(msb);
}

eVY1.h

#ifndef eVY1_H
#define eVY1_H

#include "midiClass.h"
#include <stdint.h>

class Stream;

class eVY1 : public midiClass {
  public:
    eVY1(Stream *port);
    void eVocaloid(uint8_t mode, char *lyrics);
  private:
    Stream *serialPort;
};

#endif /* eVY1_H */

eVY1Data.h

#define BANK_SELECT_NORMAL  0x00
#define BANK_SELECT_RAS     0x08
#define BANK_SELECT_DRUMKIT 0x7f

eVY1.cpp

#include "Arduino.h"
#include "eVY1.h"

/*
 * Constructor
 */
eVY1::eVY1(Stream *port) : midiClass(port){
}

void eVY1::eVocaloid(uint8_t mode, char *lyrics) {
  int lyricsLen = strlen(lyrics);
  midiClass::serialPort->write(0xf0);
  midiClass::serialPort->write(0x43);
  midiClass::serialPort->write(0x79);
  midiClass::serialPort->write(0x09);
  midiClass::serialPort->write((uint8_t)0x00);
  midiClass::serialPort->write(0x50);
  midiClass::serialPort->write(0x10 | mode);
  midiClass::serialPort->write(lyrics);
  midiClass::serialPort->write((uint8_t)0x00);
  midiClass::serialPort->write(0xf7);
}

その他

Webブラウザにピアノの鍵盤を表示して、eVY1を演奏するプログラムを書いてみました。こちらから。

バージョン

Arduino 1.5.5/Arduino Uno



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2013 garretlab all rights reserved.
inserted by FC2 system