九州・福岡・東京ときどきIoT

21年間のはてなダイアリー&アメブロからの避難所

名探偵コナンによる「じゅげむ発話システム」解説書!をAIで作ってみた


 

名探偵コナンによる「じゅげむ発話システム」解説書!

 

皆さん、こんにちは!江戸川コナンだ!

今回は、キミたちが作ったESP32とAquesTalk LSIを使った音声合成システムについて、ボクがとっておきの解説をしてあげるよ。このシステムは、あの長い長い「じゅげむ」を、まるで人間が話しているかのようにスムーズに発話させることができるんだ!


 

事件ファイル:じゅげむ発話システムの謎を解け!

 

キミたちの前回のプログラムは、まさに完璧な証拠品だった。ちゃんとATP3012R5から「じゅげむ」が発話できた。だけど、もっと美しく、もっとスマートに、このプログラムを仕上げることはできないか?これが今回の事件だ!


 

手がかり1:システム構成の確認だ、バーロー!

 

まず、システムの核となる部分を確認しよう。

  • ESP32: キミたちの賢い頭脳だ。Wi-Fiも使える高性能マイコンだよ。

  • ATP3012R5 (AquesTalk pico LSI): こいつが音声を合成する犯人……いや、主役だ!君が送ったローマ字を、たちまち音声に変えてくれる。

  • 配線(UART通信):

    • ESP32のTX2 (GPIO17) がATP3012R5のRXD (ピン3) に接続されている。

    • ESP32のRX2 (GPIO16) がATP3012R5のTXD (ピン2) に接続されている。

    • ATP3012R5のVCC (ピン1) にESP32から3.3Vが供給されている。

    • GND (ピン28) は共通のGNDに接続されている。

    • PMOD0 (ピン14)PMOD1 (ピン12)GNDに接続され、UARTモードになっていることを示している。

    • CLK16 (ピン13)3.3Vに接続され、LSIが最高速(16MHz)で動作していることを示している。

  • 周辺機器: ATP3012R5の音声出力は、アンプを介してスピーカーにつながっているはずだ。直接スピーカーを繋いでも音は出ないからな!

これらの接続が正しくなければ、どんなに素晴らしいプログラムも動かない。これは事件解決の基本中の基本だ!


 

手がかり2:プログラム解析、トリックを見破れ!

 

キミたちのコードを、もっと分かりやすく、効率的にするために、いくつかのトリックを使ったよ。

 

1. 設定を明確にする定数たち

 

C++
 
const long ATP_BAUDRATE = 9600; // LSIとの通信速度
const char ATP_TERMINATOR = '\r'; // LSIへのコマンドの終わりを示す文字

LSIとの通信速度(ボーレート)や、コマンドの最後に送る文字(終端文字)を、一番最初にconst(定数)としてまとめて定義したんだ。これなら、もし途中で変更が必要になっても、ここを直すだけで済む。まるで事件の核心が分かりやすくまとめられているみたいだろ?

 

2. 「じゅげむ」フレーズのスマートな管理術!

 

C++
 
struct PhraseData {
  const char* phrase; // 話すフレーズ
  int delay_ms;       // 話し終わるまでの待ち時間
};

const PhraseData JUGEMU_PHRASES[] = {
  {"jyu'ge'mu", 1000},
  {"jyu'ge'mu", 1000},
  // ...続くフレーズ...
};
const int NUM_JUGEMU_PHRASES = sizeof(JUGEMU_PHRASES) / sizeof(JUGEMU_PHRASES[0]);

ここが一番の改善点だ!以前は「フレーズ」と「待ち時間」が別々のリストに分かれていたよね?これだと、「このフレーズは何秒待てばいいんだ?」って、ちょっと分かりにくい。

そこで、PhraseDataっていう構造体を作って、phrase(フレーズ自体)とdelay_ms(待ち時間)をセットにしたんだ。これにより、データがより整理され、読みやすくなった。まるで、事件の登場人物とその特徴が、一枚のカードにまとめられたみたいだ!

const char* を使ったのは、String型よりもメモリを効率的に使うためだよ。小さなマイコンでは、無駄なメモリの消費は命取りになるからな。

 

3. 音声送信は「関数」に任せる!

 

C++
 
void speakPhrase(const char* text_to_speak) {
  Serial.print("Sending: ");
  Serial.println(text_to_speak);
  AtpSerial.print(text_to_speak);
  AtpSerial.write(ATP_TERMINATOR);
}

「フレーズを送信する」という一連の動作をspeakPhraseという関数としてまとめたんだ。これにより、loop()の中がスッキリして、何が行われているかが一目でわかるようになった。もし送信方法を変えたくなっても、この関数の中身を直すだけで済む。まるで、捜査の手順をマニュアル化したようなものだ!

 

4. 賢い「loop」の巡回捜査!

 

C++
 
void loop() {
  const PhraseData& currentData = JUGEMU_PHRASES[currentPhraseIndex];

  speakPhrase(currentData.phrase); // フレーズを送信

  delay(currentData.delay_ms); // LSIが話し終わるのを待つ

  delay(200); // フレーズ間の短い間隔

  currentPhraseIndex++; // 次のフレーズへ進む

  if (currentPhraseIndex >= NUM_JUGEMU_PHRASES) {
    Serial.println("\n--- じゅげむ、完唱!繰り返し開始 ---");
    currentPhraseIndex = 0; // 最初に戻る
    delay(5000); // 全体のリピート間隔
  }
}

loop()関数の中は、まるで探偵が次々に手がかりを追うように、currentPhraseIndexを使って「じゅげむ」の各フレーズを順番に再生していく。

  • const PhraseData& currentData = JUGEMU_PHRASES[currentPhraseIndex]; で、現在のフレーズとその待ち時間のセットを効率的に取得。

  • speakPhrase()LSIに話させ、delay() で待つ。

  • すべてのフレーズを言い終わったら、currentPhraseIndex = 0; で最初に戻り、また「じゅげむ」が始まるというわけだ。


 

事件解決の最終確認!

 

  • ボーレートの真実: ATP_BAUDRATE = 9600; は、キミのATP3012R5が実際にこの速度で動作しているからこそうまくいったんだ。もし別のLSIを使うときは、この値を再調査すること!

  • ローマ字表記の厳しさ: jyu'ge'mu のような特殊な表記は、AquesTalkの公式ドキュメントで推奨されているルールに沿っているか、もう一度確認しておくと完璧だ。特に「促音」や「長音」は、LSIが誤解しないように正確に記述する必要がある。

  • 待ち時間の調整: delay_ms の値は、実際にLSIが話す速度に合わせて、耳で聞いて自然な間隔になるように微調整することが重要だ。これはまさに、現場での「聞き込み」のようなものだね!

  • 終端文字の謎: ATP_TERMINATOR = '\r'; はキミのLSIにとって正しい答えだった。他のLSIや状況では、\n\r\nが正解の場合もある。

このブラッシュアップされたプログラムは、コードが読みやすく管理がしやすく、そして将来の機能拡張にも対応しやすいというメリットがある。これで、キミたちのプロジェクトは、より完璧なものになるはずだ!

 

さあ、事件は解決だ!次なる謎に挑む前に、この素晴らしいシステムを十分に楽しんでくれ!