MML再生をパワーアップさせてみました。
androidの会横浜支部で発表・実演してきました。
■発表資料です
■使用の前に
・arduinoとeVY1shieldソフトウェアシリアルを使用します。
eYV1shieldのJP2を2-3に変更してください。
・(ハードウェアの)シリアル通信でコマンド入力します。本サンプルでは9600bpsで改行コードは[LFのみ]です
arduinoコンソールから演奏できます。
・MMLの書き方、歌詞の書き方、サンプルデータは資料をご覧ください。
・さらにBluetoothモジュール(手持ちのRBT-001)をつけて、Android(BluetoothchatをSPP化したもの)から演奏できることは確認済みです。(iOSは開発できないので未確認)
■スケッチ(コード汚くてすみません・・・)
#include <avr/pgmspace.h>
#include <SoftwareSerial.h>
#define CHANNEL 0 //(0:eVocaloid 1-15:MIDI(9:drum) )
#define BUF_SIZE 512
typedef struct {
//midi event
int type;
int channel;
int note;
int velocity;
int length;
int param1;
int time;
} midi_message_t;
//func
void send_midi_message(midi_message_t midi_message);
/*
* 注意:
* SoftwareSerial使用の為eYV1shieldのJP2を2-3に変更する事
*/
int led = 13;
//SW-Serial: eVY1 shield
SoftwareSerial swSerial(2, 3);
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
// Set MIDI baud rate:
swSerial.begin(31250);
// HW-Serial: console
Serial.begin(9600);
delay(1000);
}
char recv_buf[BUF_SIZE]; // 受信用バッファ
char *p_buf=recv_buf;//受信バッファポインタ(初期値recv_buf先頭アドレス)
void loop(){
//シリアル受信処理
while(Serial.available()>0){
*p_buf = Serial.read();//1byte読み込みバッファに書き込み
if(*p_buf=='\n' /*|| *p_buf=='\''*/){
*p_buf='\0';// \0:null(文字列終端)で改行を上書き
receive_data(recv_buf);//受信データ処理
p_buf=recv_buf;//受信バッファポインタ初期値化
}else{
p_buf++;
}
}
}
//serialコマンドデータ受信
void receive_data(char *recvdata) {
if(strlen(recvdata)!=0){
if(*recvdata=='#'){
//歌詞設定
lylic_send(recvdata);
Serial.println("OK");
}else{
//mml再生
int ret=play_mml2(recvdata);
if(ret==0){
Serial.println("OK");
}else{
Serial.print("ERR:");
Serial.println(ret);
}
}
}
}
//lylic send
void lylic_send(char *str){
//Phonetic symbols hedaer f04379090050は固定
swSerial.write((byte)0xF0);
swSerial.write((byte)0x43);
swSerial.write((byte)0x79);
swSerial.write((byte)0x09);
swSerial.write((byte)0x00);
swSerial.write((byte)0x50);
swSerial.write((byte)0x10);//10:replace 11:append
Serial.print("lylic:[");
for(int i=1;i<strlen(str)+1;i++){
swSerial.write(*(str+i));
Serial.print(*(str+i));
}
Serial.println("]");
//footer
swSerial.write((byte)0x00);//end of list
swSerial.write((byte)0xF7);
}
//----------------------------
#define MAX_CHANNEL 15
#define RESOLUTION 480
#define MML_CHAR "CDEFGABR#+-1234567890.&O><LV@TI "
#define MML_CMD_SOUND "CDEFGABR" //音符・休符
#define MML_CMD_ETC "O><LV@T&I " //その他制御コマンド TODO: PNQ,は非優先で実装
//#define MML_CMD MML_CMD_SOUND+MML_CMD_ETC; //全コマンド
#define MML_OPT_SOUND "#+-" //CMDの後ろに付くオプション
#define MML_OPT_LENGTH "." //CMDの後ろに付くオプション TODO:&は別途実装
#define MML_NUM "1234567890" //数字
//音色(MIDIノートナンバー)
#define NOTE_C 0
#define NOTE_D 2
#define NOTE_E 4
#define NOTE_F 5
#define NOTE_G 7
#define NOTE_A 9
#define NOTE_B 11
//イベントタイプ
#define TYPE_ETC 0
#define TYPE_NOTE 1
#define TYPE_REST 2
#define TYPE_INSTRUMENT 3
#define TYPE_TEMPO 4
int play_mml2(char *mmlstr){
int pos=0;
int mml_L=4;//Length(1-64?)デフォルト4(4分音符)
int mml_O=4;//Octave(0-9)デフォルト4
int mml_T=120;//Tempo(1-255)デフォルト120
int mml_V=15;//volume(0-15)デフォルト15?
int mml_AT=0;//音色(GM:0-127)デフォルト0
char command_buf[16];
midi_message_t midimessage;
Serial.println("mml:");
Serial.print(mmlstr);
Serial.println("");
while (pos < strlen(mmlstr)) {
*(mmlstr+pos)=toupper(*(mmlstr+pos));//小文字→大文字
sprintf(command_buf,"%c",*(mmlstr+pos));
// ①コマンド文字チェック
if (strstr((char *)MML_CMD_SOUND,command_buf) == NULL
&& strstr((char *)MML_CMD_ETC,command_buf) == NULL) {
// コマンド以外文字
return -1;
}
// ②タイプ判別確定
if(strstr((char *)MML_CMD_SOUND,command_buf) != NULL){ // 音符・休符
midimessage.type = TYPE_NOTE;
midimessage.channel=CHANNEL; //暫定channel=0固定
midimessage.velocity=(127*mml_V/15); //ボリューム設定
Serial.print("*");
switch (*(mmlstr+pos)) {
case 'C':
midimessage.note = NOTE_C + ((mml_O+1) * 12);
break;
case 'D':
midimessage.note = NOTE_D + ((mml_O+1) * 12);
break;
case 'E':
midimessage.note = NOTE_E + ((mml_O+1) * 12);
break;
case 'F':
midimessage.note = NOTE_F + ((mml_O+1) * 12);
break;
case 'G':
midimessage.note = NOTE_G + ((mml_O+1) * 12);
break;
case 'A':
midimessage.note = NOTE_A + ((mml_O+1) * 12);
break;
case 'B':
midimessage.note = NOTE_B + ((mml_O+1) * 12);
break;
case 'R':
midimessage.type = TYPE_REST;
break;
default:
//error
return -2;
}
// 音長仮決定(明示的指定、付点などで後で変更される可能性有り)
midimessage.length = (RESOLUTION * 4) / mml_L;// 音長=1小節の時間/n分音符
pos++;
} else if(strstr((char *)MML_CMD_ETC,command_buf) != NULL){ // その他
Serial.print("*");
switch (*(mmlstr+pos)) {
case 'O': // オクターブ(MIDIobj無し)
{
midimessage.type = TYPE_ETC;
pos++;
int tmpOct = atoi(mmlstr+pos);
if (tmpOct < 1 || tmpOct > 9) {
// 範囲外
return -3;
}
mml_O = tmpOct;
char buf[16];
pos += strlen(itoa(tmpOct,buf,10));// 桁数分
Serial.print("*");//一桁固定
}
break;
case '>': // オクターブ(MIDIobj無し)
midimessage.type = TYPE_ETC;
if (mml_O < 9)
mml_O++;
pos++;
break;
case '<': // オクターブ(MIDIobj無し)
midimessage.type = TYPE_ETC;
if (mml_O > 0)
mml_O--;
pos++;
break;
case 'L': // 音長(MIDIobj無し)
{
midimessage.type = TYPE_ETC;
pos++;
int tmpLen = atoi(mmlstr+pos);
if (tmpLen < 1 || tmpLen > 128) {
// 範囲外
return -4;
}
mml_L = tmpLen;
char buf[16];
pos += strlen(itoa(tmpLen,buf,10));// 桁数分
for(int i=0;i<strlen(itoa(tmpLen,buf,10));i++){
Serial.print("*");
}
}
break;
case 'V': // 音量(MIDIobj無し)
{
midimessage.type = TYPE_ETC;
pos++;
int tmpVol = atoi(mmlstr+pos);
if (tmpVol < 0 || tmpVol > 15) {
return -5;
}
mml_V = tmpVol;
char buf[16];
pos += strlen(itoa(tmpVol,buf,10));// 桁数分
for(int i=0;i<strlen(itoa(tmpVol,buf,10));i++){
Serial.print("*");
}
}
break;
case '@': // Instrument(楽器)変更
midimessage.type = TYPE_INSTRUMENT;
// 数値は後処理で読み込み
pos++;
break;
case 'T': // TEMPO
midimessage.type = TYPE_TEMPO;
// 数値は後処理で読み込み
pos++;
break;
case '&': // タイ・スラー
//TODO: 実装未定
midimessage.type = TYPE_ETC;
pos++;
break;
case ' ': // 半角スペース
//なにもしないで読み飛ばし
midimessage.type = TYPE_ETC;
pos++;
break;
default:
return -6;
}
}
// ③タイプごとの後処理
/*
* 音符時 オプションで半音、音長指定、付点がある
* Cの例:C C+ C+4 C+. C+4.
*
* 休符時、音長指定、付点がある
* 例:R R4 R4.
*/
if(strlen(mmlstr)>pos){
*(mmlstr+pos)=toupper(*(mmlstr+pos));//小文字→大文字
sprintf(command_buf,"%c",*(mmlstr+pos));
// オプション(#+-)文字チェック(音符の後のみ)
if (midimessage.type == TYPE_NOTE
&& strstr((char *)MML_OPT_SOUND,command_buf) != NULL) {
Serial.print("*");
// コマンドオプション文字
switch (*(mmlstr+pos)) {
case '#': // シャープ
case '+': // シャープ
if (midimessage.note < 127)
midimessage.note++;
break;
case '-': // フラット
if (midimessage.note > 0)
midimessage.note--;
break;
default:
return -7;
}
pos++;
}
// 音長(数値)処理(音符・休符の後のみ)
sprintf(command_buf,"%c",*(mmlstr+pos));
if ((midimessage.type == TYPE_NOTE || midimessage.type == TYPE_REST)
&& strstr((char *)MML_NUM,command_buf) != NULL) {
int tmpLength = atoi(mmlstr+pos);
if (tmpLength < 0 || tmpLength > RESOLUTION) {
// 範囲外
return -8;
}
if(tmpLength!=0){
midimessage.length = (RESOLUTION * 4) / tmpLength;// 音長=1小節の時間/n分音符
}
char buf[16];
pos += strlen(itoa(tmpLength,buf,10));// 桁数分
for(int i=0;i<strlen(itoa(tmpLength,buf,10));i++){
Serial.print("*");
}
}
// 付点(.)処理(音符・休符の後のみ)
sprintf(command_buf,"%c",*(mmlstr+pos));
if ((midimessage.type == TYPE_NOTE || midimessage.type == TYPE_REST)
&& strstr((char *)MML_OPT_LENGTH,command_buf) != NULL) {
midimessage.length *= 1.5;
pos++;
Serial.print("*");//一桁固定
}
// テンポ指定
if(midimessage.type == TYPE_TEMPO
&& strstr((char *)MML_NUM,command_buf) != NULL) {
midimessage.param1 = atoi(mmlstr+pos);
if (midimessage.param1 < 1 || midimessage.param1 > 255) {
// 範囲外
return -9;
}
char buf[16];
pos += strlen(itoa(midimessage.param1,buf,10));// 桁数分
mml_T=midimessage.param1;
for(int i=0;i<strlen(itoa(midimessage.param1,buf,10));i++){
Serial.print("*");
}
}
//音色指定
if (midimessage.type == TYPE_INSTRUMENT ){
midimessage.param1 = atoi(mmlstr+pos);
if (midimessage.param1 < 0 || midimessage.param1 > 127) {
// 範囲外
return -10;
}
char buf[16];
pos += strlen(itoa(midimessage.param1,buf,10));// 桁数分
for(int i=0;i<strlen(itoa(midimessage.param1,buf,10));i++){
Serial.print("*");
}
}
}
//イベント送信
if(midimessage.type==TYPE_NOTE || midimessage.type==TYPE_INSTRUMENT){
send_midi_message(midimessage);
}
//rest(何もしない)
if(midimessage.type==TYPE_NOTE || midimessage.type==TYPE_REST){
delay((60000/mml_T)*midimessage.length/480); //次の音時間までwait
}
//note_off処理
if(midimessage.type==TYPE_NOTE){
midimessage.velocity=0;//Note Off
send_midi_message(midimessage);
}
}
//loop end
Serial.println("");
return 0;
}
void send_midi_message(midi_message_t midi_message) {
switch(midi_message.type){
case TYPE_NOTE:
//NOTE-ON:0x9n
swSerial.write((byte)(0x90|midi_message.channel));
swSerial.write((byte)midi_message.note);
swSerial.write((byte)midi_message.velocity);
break;
case TYPE_INSTRUMENT:
//ProgramChange:0xCn
swSerial.write((byte)(0xc0|midi_message.channel));
swSerial.write((byte)midi_message.param1);
break;
default:
break;
}
}