主にandroidとかのメモ

eVY1 shieldでシリアル通信で歌詞指定&MML再生(その2)

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

}