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; } }