クムクムロボット Qumcum PRO、SEにはメインプロセッサーにESP32が使われています。このプロセッサーは最大内部240MHzのクロックで動作していて、音声データなどはソフトウエアで処理することが可能な速さです。ベーシックQumcumでは音声合成ICを使って発話させていましたが、PRO、SEではソフトウエアライブラリを使用して音声合成しています。

このライブラリは株式会社アクエスト  AquesTalk ESP32です。ここではこれを使った音声合成のサンプルを作成しますので、Arduino IDEにこのライブラリをインストールする方法はこちらのAquesTalk ESP32のWEBサイトを参照してください。

クムクムの音声合成概要

クムクムロボットのソフトウエアで音声合成ライブラリを使ってみます。基板上にはアンプのICとボリューム、そしてスピーカーを接続するコネクタがあります。

音声合成とBEEP音を出す機能は同じアンプとスピーカーを使っていますが、ESP32のポートは異なります。それぞれのピンアサインは以下の通りです。ここではBEEPではなく音声合成を対象にします。

機能ESP32 IO
音声合成IO25
BEEPIO4

ESP32には内部にi2S(Inter-IC Sound)バスと内蔵DAC(digital-analog converter)が周辺機能として内蔵されています。このライブラリもそれらを使うことができるので、上記ポートIO25から出てくる信号はアナログ信号です。この信号はボリュームを通り、オペアンプLM386Mで電力増幅されスピーカーを駆動しています。

AVRなどの一般的なマイコンではDACが内蔵されていることは少ないので、外付け回路が必要になりますが、このESP32はとても便利なマイコンです。ただ内蔵DACの分解能は8ビットしかありません。音楽CDなどは16bitなので256分の1しかありませんが、実際に耳で聞いてみれば十分に実用的なことがわかります。なんら問題ないです。

 

音声合成 Aques Talkで喋らせてみる

では実際にクムクムPro、SEで音声合成でしゃべらせてみましょう。

注意      この実験はスケッチを間違えると基板が故障する可能性がありますので、最後まで一読してから行って下さい。

 

AquesTak ESP32のライブラリをインストールしているので、Arduino IDEの [ファイル]-[スケッチ例] から以下の [AquesTalk ESP32]-[hello_aquestalk] を開きます。

 

Arduino IDEのエディタにサンプルスケッチが開きます。少々複雑に見えますが、行っていることは

I2Sの設定

DACの設定

AuesTalkライブラリで文字列データのPCM変換

PCMデータをI2Sへ送り出し

という一連の流れになっています。

以下、サンプルスケッチをArduino IDE 2.xxを使ってクムクムPRO、SEで動作するように修正したスケッチです。

// hello_aquestalk.ino - AquesTalk ESP32 サンプルプログラム
#include "driver/i2s.h"
#include "aquestalk.h"

#define LEN_FRAME 32
uint32_t workbuf[AQ_SIZE_WORKBUF];


void setup() {
  int iret;
  Serial.begin(115200);
  Serial.println("Initialize AquesTalk");
  iret = CAqTkPicoF_Init(workbuf, LEN_FRAME, "XXX-XXX-XXX");
  if(iret){
    Serial.println("ERR:CAqTkPicoF_Init");
  }

  DAC_Create();
  Serial.println("D/A start");
 
  playAquesTalk("konnnichiwa.");
  playAquesTalk("korewa;te'_sutode_su.");
  playAquesTalk("sa'nngatsu/<NUMK VAL=17 COUNTER=nichi> <NUMK VAL=12 COUNTER=ji>/<NUMK VAL=23 COUNTER=funn>.");
  playAquesTalk("yukkuri_siteittene?");

  DAC_Release();
  Serial.println("D/A stop");
}


void loop() {
}


// 一文の音声出力(同期型)
void playAquesTalk(const char *koe)
{
  Serial.print("Play:");
  Serial.println(koe);

  int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 100, 0xffffU);
  if(iret)  Serial.println("ERR:CAqTkPicoF_SetKoe");

  for(;;){
    int16_t wav[LEN_FRAME];
    uint16_t len;
    iret = CAqTkPicoF_SyntheFrame(wav, &len);
    if(iret) break; // EOD
   
    DAC_Write((int)len, wav);
  }
}


////////////////////////////////
//i2s configuration
static const int i2s_num = 0; // i2s port number
i2s_config_t i2s_config = {
     .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
     .sample_rate = 24000,
     .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
     .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
     //.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB,
     .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,// ※修正行1
     .intr_alloc_flags = 0,
     .dma_buf_count = 4,
     .dma_buf_len = 384,
     .use_apll = 0
};


void DAC_Create()
{
  AqResample_Reset();

  i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);

  //i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);  // 25, 26 ※修正行2  コメントアウトする
  i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); // 25  ※修正行2   コメントを外す
  //i2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN ); // 26
}


void DAC_Release()
{
  i2s_driver_uninstall((i2s_port_t)i2s_num); //stop & destroy i2s driver
}


// upsampling & write to I2S
int DAC_Write(int len, int16_t *wav)
{
  int i;
  for(i=0;i<len;i++){
    // upsampling 8KHz -> 24KHz
    int16_t wav3[3];
    AqResample_Conv(wav[i], wav3);

    // write to I2S DMA buffer
    for(int k=0;k<3; k++){
      uint16_t us = ((uint16_t)wav3[k])^0x8000U;  // signed -> unsigned data  for M5Stack internal DAC
//      uint16_t us = (uint16_t)wav3[k];          // for external I2S DAC
      uint16_t sample[2];
      size_t transBytes;
      sample[0]=sample[1]=us; // mono -> stereo
      i2s_write((i2s_port_t)i2s_num, (const char*)sample, 4, &transBytes, portMAX_DELAY);
    }
  }
  return i;
}

Arduino IDE1.xx と Arduino Core 1.xxを使っている場合はオリジナルのサンプルスケッチから※修正行2 の修正だけで動作します。修正行1は修正せずにオリジナルそのままで動作します。

Arduino IDEは1.xxでもArduino Coreを2.xxにアップデートしている場合は、※修正行1も修正が必要です。

注意  ※クムクムにプログラムを転送する前に、必ず修正2を行ってください。最悪故障の原因になります。

 

参考

Arduino IDE 2.0 以降インストールした場合は、Arduino Coreも2.xxにアップデートされていてワーニングが出ます。その中で致命的なものがあります。

以下、Arduino Core2.xxを使っている場合のワーニングです。

C:\Users\username\AppData\Local\Temp\.arduinoIDE-unsaved2023119-22740-9iy6gv.pvnvg\hello_aquestalk\hello_aquestalk.ino:60:49: warning: 'I2S_COMM_FORMAT_I2S_MSB' is deprecated [-Wdeprecated-declarations]
.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB,
^~~~~~~~~~~~~~~~~~~~~~~
In file included from C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.14/tools/sdk/esp32/include/driver/include/driver/i2s.h:16,
from C:\Users\username\AppData\Local\Temp\.arduinoIDE-unsaved2023119-22740-9iy6gv.pvnvg\hello_aquestalk\hello_aquestalk.ino:2:
C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.14/tools/sdk/esp32/include/hal/include/hal/i2s_types.h:88:5: note: declared here
I2S_COMM_FORMAT_I2S_MSB __attribute__((deprecated)) = 0x01, /*!< I2S format MSB, (I2S_COMM_FORMAT_I2S |I2S_COMM_FORMAT_I2S_MSB) correspond to `I2S_COMM_FORMAT_STAND_I2S`*/

warning: 'I2S_COMM_FORMAT_I2S_MSB' is deprecated

つまりは使えないかも、というワーニングで、実際にこのコードのままでは動作しません。

そこで、I2S_COMM_FORMAT_I2S_MSB

を  I2S_COMM_FORMAT_STAND_MSB

に変更してください。これが修正行1です。

詳しくは、Arduino Coreのファイル

C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.14\tools\sdk\esp32\include\hal\include\hal\i2s_types.h

を参照してください。

修正行2は、内蔵DACをステレオで使うか、モノラルで使うかになりますが、クムクムではIO25だけにアンプが接続されています。IO26 にはMEMSマイクのICが接続されているので、入力ポートとして使う必要があります。そこで i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN);としてモノラル右チャンネルだけ有効にしています。

 

 

繰り返してしゃべらせる改造

サンプルそのものは一回だけおしゃべりして終わりですので、簡単な改造で繰り返してお喋りさせるようにしてみます。

複雑に見えるサンプルスケッチですが、発話をすることだけを考えるとほとんどの部分は変更する必要がありません。I2Sの設定、DACの設定などは定型文のおまじないとしてそのまま使えば大丈夫です。

サンプルスケッチでは setup() の中でplayAquesTalk() が使われていますので、一度だけしか発話しません。

そこで loop() に playAquesTalk()  を追加します。

void loop() {
  DAC_Create();
  playAquesTalk("konnnichiwa.");
  DAC_Release();
  delay(2000);
}

このloop()のサンプルでは2秒に一度、”こんにちは” と発話を繰り返します。 ここではいちいち DAC_Create() と DAC_Release() のペアでDACの制御を行っていますが、組み込み機器で止める必要が無い場合、setup() で一度 DAC_Create()  を実行した後に DAC_Release() は必要ありません。

 

もっと上手にお喋りさせたい

発話させる文字列を与える関数は、 playAquesTalk() ですので、その引数にローマ字表記の小文字アルファベット文字列を与えればそのまま発話してくれます。

またサンプルにもありますが、時間の発話で、分を”ふん”、”ぷん”、単位で”本”を”ほん”、”ぼん”、”ぽん”を自動的に使い分けたりすることもできます。

ここでは詳しく説明しませんが、アクセントを付けたり、間を開けたりできますので、AquesTalkの説明書を参照してください。

音声記号列仕様書(ローマ字)  PDF