N.Yamazaki's blog

主に音声合成について思ったことを書いてみようと思います。
「AquesTalk-ESP32 Ver.2.0」を使う

keywords:音声合成, 組み込み, 日本語, ESP32, M5Stack

 

 

 

■概要


AquesTalk-ESP32は、ESP32向けの音声合成ライブラリです。
これが今回バージョンアップしました。
また、Arduino-ESP32も大幅に変更されました。
そこで、新しくなったAquesTalk-ESP32の使い方を紹介します。

 

 

■これまでとの違い


AquesTalk-ESP32 Ver.2.0  ダウンロード

漢字が読めるようになりました。これまでは音声記号列からの音声合成でしたが、小型軽量になった言語処理エンジンAqKanji2Romanが組み込まれました。
数MBの言語辞書データは、SDメモリカードやシリアルフラッシュメモリなど、比較的低速な外部メモリに配置しながらも、高速に処理ができます。

 

Arduino-ESP32

2018/06/28に大規模な変更がありました。
インストール方法がArduino IDE のボードマネージャを使う方法に変わりました。
インストールされる場所も変更されています。
これに伴い、AquesTalk-ESP32ライブラリのインストール場所も変更が必要です。
また、I2S関連の関数も変更されていて、従来のコードでは2倍のサンプリング周波数で再生される不具合が生じています。

 


■ビルド準備 ArduinoIDEの場合


ArduinoIDEによる開発環境上に、AquesTalk-ESP32をセットアップする手順を以下に示します。
ArduinoIDEや、ArduinoIDEでESP32をビルドするための「Arduino core for the ESP32」は、すでにインストールされているものとします。


1.  次の場所にライブラリとヘッダをコピーします。

    {PATH_Arduino_ESP32}¥tools¥sdk¥lib¥libaquestalk.a
    {PATH_Arduino_ESP32}¥tools¥sdk¥include¥aquestalk¥aquestalk.h

ここで、{PATH_Arduino_ESP32}は、「Arduino core for the ESP32」がインストールされた場所です。 私の環境では次のとおりでした。

    C:¥Users¥{ユーザ名}¥AppData¥Local¥Arduino15¥packages¥esp32¥hardware¥esp32¥1.0.0

aquestalk¥aquestalk.hのaquestalkフォルダは作成してください。


2.  次の場所にplatform.local.txtを作成します。同じ場所にplatform.txtがあるはずです。

    {PATH_Arduino_ESP32}¥platform.local.txt

platform.local.txtには、以下を記述します。これは、コンパイル時のインクルードパスにaquestalkを追加し、リンク時にlibaquestalk.aを加える指定です。

compiler.c.extra_flags="-I{compiler.sdk.path}/include/aquestalk"
compiler.cpp.extra_flags="-I{compiler.sdk.path}/include/aquestalk"
compiler.c.elf.libs=-lgcc -lopenssl -lbtdm_app -lfatfs -lwps -lcoexist -lwear_levelling -lesp_http_client -lhal -lnewlib -ldriver -lbootloader_support -lpp -lmesh -lsmartconfig -ljsmn -lwpa -lethernet -lphy -lapp_trace -lconsole -lulp -lwpa_supplicant -lfreertos -lbt -lmicro-ecc -lcxx -lxtensa-debug-module -lmdns -lvfs -lsoc -lcore -lsdmmc -lcoap -ltcpip_adapter -lc_nano -lesp-tls -lrtc -lspi_flash -lwpa2 -lesp32 -lapp_update -lnghttp -lspiffs -lespnow -lnvs_flash -lesp_adc_cal -llog -lsmartconfig_ack -lexpat -lm -lc -lheap -lmbedtls -llwip -lnet80211 -lpthread -ljson  -lstdc++ -laquestalk

compiler.c.elf.libsの部分は、Arduino core for the ESP32のバージョンによって異なります。必ずplatform.txtからcompiler.c.elf.libsの部分をコピペして、最後に -laquestalk を加えます。

 

以上で準備は終わりです。

 


■AquesTalk-ESP32のラッパークラス「AqeusTalkTTS」


AquesTalk-ESP32は、辞書データのアクセスやD/A出力部分は、動作環境(ハードウェア)に応じて実装する必要があります。
そこで、AquesTalk-ESP32のインターフェースをラップしたプログラムAqeusTalkTTSを作りました。これで、アプリケーションから簡単にAquesTalk-ESP32を使うことができます。

また、AqeusTalkTTSは、表示やその他の処理と並行して動作できるように、非同期(バックグラウンド)で音声出力します。

このAqeusTalkTTSとAquesTalk-ESP32の関係を下図に示します。

 

図. AqeusTalkTTSとAquesTalk-ESP32の関係

 

アプリ側からは、AquesTalk-ESP32が隠ぺいされ、AqeusTalkTTSの関数だけを使います。


関数aqdic_XXX()は、辞書データをアクセスする関数群で、言語処理エンジンAqKanji2Romanから呼ばれます。今回は、辞書データをSDメモリカードのファイルとして書き込んで使用します。SDのアクセスには、SD.hを用いているのので、ArduinoIDEに依存しています。ESP32-IDF環境で使う場合は書き換えが必要です。他にも、辞書データをSPIシリアルフラッシュメモリに保存したり、初期化時にRAMに読み込んで使用するなどの方法もあるでしょう。その場合も、このaqdic_XXX()をハードウェア環境に応じて実装します。

 

関数DAC_XXX()は、D/A出力部分で、ここでは、I2Sを経由して内蔵の8bitDACから音声出力しています。外部I2S-DACを使ったり、PDM出力をする場合などは、ここを書き換えます。

 

AqResampleは、リサンプリングのライブラリです。AquesTalk picoの出力は8KHzのサンプリング周波数です。これを、折り返し雑音の影響を削減するため、24KHzにアップサンプリングしています。

 

このAqeusTalkTTSのソースとヘッダファイルは、次のSampelTTS.zipに含まれています。改変・配布は自由です。

 


■サンプルプログラム


 

ダウンロード:SampleTTS.zip

 

サンプルスケッチ SampleTTS は、AquesTalk-ESP32、AquesTalkTTSを使ったHelloWorldのようなものです。ArduinoIDE用です。以下の機能が有ります。


シリアル経由で漢字仮名交じりのテキストを受信し、それを音声合成出力します。
ArduinoIDEのシリアルモニタは日本語テキストを送信できないので、Tera TermなどUTF8が送信できる通信アプリを使用してください。

 

左ボタンの押下で、固定メッセージをplayK()関数で再生します。
ここで、発声中に、playK()やplay()関数を呼び出すと、発声中のメッセージは中断され、新たなメッセージが発声されます。

 

中ボタンの押下で、日付を1日から31日まで順に読み上げます。
ここでは、play()関数で音声記号列からの音声出力しています。
また、isPlay()関数で発声の終了を調べ、発声の終了後に、次の日付を読み上げています。

 

右ボタンの押下は、stop()関数で発声を止めます。

 

なお、事前に、上記「ビルド準備」に従って、AquesTalk-ESP32をインストールしておきます。
また、SDメモリカードへ辞書データを書き込んでおく必要もあります。AquesTalk-ESP32のパッケージに辞書データaqdic_m.binがありますので、aq_dicフォルダを作成して、その下にaqdic_m.binを書き込みます。辞書のサイズは約9MBです。書き込む方法は何でも良く、SDカードの刺さるPC等でコピーするのが手っ取り早いでしょう。辞書をコピーしたら、そのSDメモリカードをM5Stackに刺しておくことを忘れずに。

 


■AqeusTalkTTSクラスのメンバ関数


漢字のテキストからの音声合成と、音声記号列からだけの音声合成の場合で、使用する初期化関数が異なります。前者はcreateK()を、後者はcreate()を用います。


ちなみに、AquesTalk-ESP32はヒープメモリを一切使用せず、処理で使用するメモリ(ワークバッファ)をアプリ側で確保して、初期化時にその領域を指定する方法を用いています。AqeusTalkTTSは、初期化時にワークバッファをヒープ上に確保し、AquesTalk-ESP32に指定しています。ヒープの使用量は、createK()では21KB、create()では400Bです。
コードサイズも、createK()とcreate()では大きく異なり、create()だと170KBほど削減できます。AquesTalk-ESP32の言語処理エンジンAqKanji2Romanは、高速化のために、辞書データの一部をROM領域に持たせているためです。

 

int createK(const char *licencekey);
AqeusTalkTTSを初期化します。
最初に一度だけ呼び出します。
漢字を含む日本語テキストからの音声合成の場合、こちらを使用します。
引数には、AquesTalk-ESP32のライセンスキーを指定します。
これにより、ナ行、マ行がすべてヌと発声される制限が無くなります。

 

int create(const char *licencekey);
AqeusTalkTTSを初期化します。
音声記号列からの音声合成だけの場合、こちらを使用します。


void release();
AqeusTalkTTSの使用を終了するときに呼び出します。
初期化時にヒープに確保した、ワークバッファを解放します。

 

int playK(const char *kanji, int speed);
指定のテキストメッセージを音声出力します。
非同期関数で、発声はバックグラウンドで行われ、関数はすぐに戻ります。
引数kanjiには、漢字仮名混じりの文字列(UTF8)を指定します。
引数speedは話速で、50-300の範囲で指定します。デフォルトは100です。

 

int play(const char *koe, int speed);
playK()の漢字仮名交じりのテキストの代わりに、ローマ字音声記号列を指定します。

 

void stop();
発声を中止します。

 

bool isPlay();
発声中の場合はtrueを返します。

 

int getLevel();
発声中の波形の大きさを返します。リップシンクで用います。

 


■ライセンスキーの入手


AquesTalk-ESP32の評価版はナ行とマ行がすべてヌと発声される制限があります。ライセンスキーを購入することでこの制限が解除されます。

ライセンスキーの入手は、アクエストOnline Storeで「AquesTalk ESP32 使用ライセンス」を購入します。このとき備考欄に使用するESP32モジュールのMACアドレスを記載する必要があります。その後に郵送されるライセンス証にライセンスキーが記載されており、このライセンスキーをcreate()/createK()の引数に指定します。


・アクエストOnline Store「AquesTalk ESP32 使用ライセンス
 

 

■今後の予定


AquesTalk-ESP32に付属の辞書データですが、まだチューニングしていません。おかしな読み方も多々あるかと思います。
今後、辞書の整備を行って、品質を向上させる予定です。

 

 

■リンク


過去記事


ESP32で音声合成(AquesTalk pico for ESP32)


AquesTalk-ESPを簡単に使うクラス(プログラム)


省RAM版の言語処理エンジンのプロトタイプ開発
 

| AquesTalk | 19:26 | - | - |
省RAM版の言語処理エンジンのプロトタイプ開発

- M5Stackで漢字テキストからの音声合成 -

 

Keywords: AqKanji2Koe, AquesTalk, 言語処理, M5Stack, ESP32, 組み込みシステム

 

■漢字の読み上げ



日本語テキストから音声合成をするには、言語処理エンジンが必要です。
言語処理エンジンAqKanji2Koeは、漢字仮名混じりの文字列を、AquesTalk用の音声記号列に変換するライブラリです。

この言語処理には5MB〜12MBの辞書データが必要で、これがネックになってハードウェア規模を小さくできませんでした。そのため、現在の最小動作環境は、Raspberry Pi程度となっています。
*「AquesTalk Pi」はRAM256MB〜のRaspberry Pi上で動作

 

現在、この言語処理エンジンを、1ランク小さい規模のハードウェアで動作をすることを目指して開発を進めています。具体的には、RAMは256KB以下、CPUはCortex-Mをターゲットにしています。

辞書データは削減できないので、現状、ワンチップマイコンの中に入れることはできません。
そこで、これはSDメモリカードやSPIフラッシュメモリなどの、外部メモリに配置して使うことを考えています。

 

target

 


■プロトタイピング



今回、M5Stackを使ってプロトタイプを作成しました。
M5Stackは、ハードウェア規模がターゲットに近く、SDスロットやオーディオ出力が付いているので今回のプロトタイピングにはぴったりです。

 

M5Stack
SoC ESP32 
CPU Xtensa LX6(Tensilica製),CLK:240MHz
RAM 520KiB
ROM 4MB(flash)
その他

320 x 240 TFTカラーディスプレイ、

microSDカードスロット、スピーカー

 

このプロトタイプでは、シリアルで漢字を含むテキスト(UTF8)を受信し、今回開発中の言語処理エンジンで音声記号列に変換して、それをAquesTalk pico for ESP32で音声データを生成して出力しています。
顔表示(+リップシンク)の部分は、「M5Stack-Avatar」を使わせていただきました。

 

<プロトタイプのデモ動画>

 


■アルゴリズムとデータ構造の全見直し



言語処理では、辞書データへの大量のアクセスが必要です。
一方で、SDメモリカードからのデータの読み出しは、メモリからのアクセスとは比較できないほど遅くなります。この点が大きな開発課題となります。
最初のプロトタイプは、1文を処理するのに1分ほどかかってしまい、とても実用になりませんでした。

 

辞書データには、LOUDSなどの簡潔データ構造が使われます。
これはデータサイズが小さく、高速な辞書引きができるメリットがありますが、データが分散しているという特徴があります。
RAM上に辞書データが展開されている場合は何の問題もないのですが、転送速度が遅く、ランダムアクセスが不得意なSDメモリカードに配置されている場合は、大きな問題となります。

 

今回、データ構造やアルゴリズムを全面的に見直すことにし、読み込み回数の削減や、なるべく連続的なアドレスでアクセスするようにして、なんとか実用的なレベルまでたどり着きました。

現プロトタイプでのコードサイズは100KB以下(ただし、C++の標準ライブラリは含まず)です。
RAMサイズは未だ調べていませんが、あれこれ含めて520KiBで動いている事実から目標に近くなっていると思っています。


今後はライブラリ製品としての開発フェーズに入ります。

 

■Link


 

次記事 「AquesTalk-ESP32 Ver.2.0」を使う

| AquesTalk | 12:08 | - | - |
M5Stackの音量を抵抗1つで調節する

keywords: M5Stack, Volume, 音量、スピーカー


 

■概要



M5Stackは、内蔵のオーディオアンプへの入力信号のレベルが大きすぎるため音が割れます。
一方、内蔵DACは量子化精度が8bitしかないので、ソフト的に音量を下げることは厳しいです。
今回は、1つの抵抗でM5Stackの音量を調整してみます。

 

 

■しくみ


 

DACの出力をGPIO26から出力し、抵抗を経て、GPIO25につながっているアンプに送る。

 

ESP32には2つのDACがあり、GPIO25とGPIO26にそれぞれつながっています。
M5Stackでは、DAC1(GPIO25)がアンプに接続されています。


ここで、DAC1を止めて、DAC2の出力をGPIO26から出し、抵抗で減衰させてGPIO25に戻せば、音量が調整できます。このときESP32のGPIO25はフローティング状態(電気的に絶縁)しておく必要があります。

 

上の動画では、10kΩと22KΩで切り替えています。10KΩの場合は、そのまま出力した場合とほとんど音量が変わらず、22KΩの場合は、約16dB(1/6)小さくなりました。

 

■プログラム



ベースはM5Stackのspeakerサンプルプログラムです。関数playMusicVolume()は、M5StackのライブラリのSpeaker.cから起動音の再生ルーチンを抜き出し、以下を修正したものです。


・最初にGPIO25を入力に設定する関数を呼び出す
・DACへの出力はDAC1からDAC2(GPIO26)に変更

 

// M5_volume.ino - M5Stack volume control
#include <M5Stack.h>
void setup() {
// Initialize the M5Stack object
M5.begin();
M5.Lcd.printf("M5Stack Speaker test:¥r¥n");
M5.Speaker.setVolume(8);
M5.Speaker.playMusic(m5stack_startup_music, 25000);
}
void loop() {
if(M5.BtnA.wasPressed()) {
M5.Lcd.printf("wasPressed A ¥r¥n");
playMusicVolume(m5stack_startup_music, 25000); //play the M5Stack startup sound
}
if(M5.BtnB.wasPressed())
{
M5.Lcd.printf("wasPressed B ¥r¥n");
M5.Speaker.tone(3000, 200); //frequency 3000, with a duration of 200ms
}
if(M5.BtnC.wasPressed())
{
M5.Lcd.printf("wasPressed C ¥r¥n");
M5.Speaker.setVolume(10);	// max
M5.Speaker.playMusic(m5stack_startup_music, 25000); //play the M5Stack startup sound
}
M5.update();
}
void playMusicVolume(const uint8_t* music_data, uint16_t sample_rate) 
{
// disconnect PWM & input on GPIO25
ledcDetachPin(SPEAKER_PIN);
pinMode(SPEAKER_PIN, INPUT);
uint32_t length = strlen((char*)music_data);
uint16_t delay_interval = ((uint32_t)1000000/sample_rate);
for(int i=0; i<length; i++) {
dacWrite(GPIO_NUM_26, music_data[i]);
delayMicroseconds(delay_interval);
}
for(int t=music_data[length-1]; t>=0; t--) {
dacWrite(GPIO_NUM_26, t);
delay(2);
}
ledcAttachPin(SPEAKER_PIN, TONE_PIN_CHANNEL);
}

 

■GPIO25がOFFにならない!?



アンプの入力インピーダンスが28KΩ(typ)なので、ESP32側のGPIO25が完全に切り離されているなら、22KΩでも半分程度の減衰になるはずです。しかし、実際は1/6。


ESP32側のGPIO25のGND間の内部抵抗が、なぜか10KΩ程度とかなり低くなっていました。オープンドレイン出力なども試したのですが、フローティング状態にできませんでした。なにか方法をご存知でしたらコメントください。

 


M5Stackはアンプとスピーカーを内蔵していますが、今のままではあまりにもったいない。
音が歪まないように改良して欲しいところです。
 

| 電子工作 | 18:05 | comments(2) | - |
ESP32のPDMでサウンド出力+ノイズ対策

keywords: ESP32、PDM、オーディオ、音声

 

■概要


 

ESP32は、PDM(Pulse-density modulation)の変調器を内蔵しています。
前の記事では内蔵DACでしたが、今回はこのPDMで音を出してみます。

PDMは1bitのデジタル信号ですが、パルスの密度がアナログ信号の振幅に直接対応しているので、アナログアンプに直に与えても鳴ります(アンプ自体がLPFの役割を担っているため)。

今回の実験は、ESP32-DevCとM5Stackで行ないました。


 

 

■崩れたPDM波形!?


 

最初のPDM出力波形はこんなでした。

 

 

波形がひどく崩れています。
立ち上がりは急峻で良いのですが、立下りが指数的に下がっていて、これでは、まともな音は出ません。

ネットでも同様の現象が報告されていたので、そんなものなのかと一時は諦めましたが・・・

 

実は、なんてことはない、単純なミスでした。
内蔵DAC用のコードを改良して使っていたため、出力ピンの指定を忘れていました。
i2s_set_pin()でこれを指定すれば、下のきれいな矩形のPDM信号が得られるようになりました。

 

 

 

 

■無音の再生時のノイズ



ゼロの値を連続して出力すると、ピーという周期性のノイズが現れます。
このノイズ、常に同じ高さでなく毎回違った高さの音が出ます。
どうも、前に出力した波形データに影響しているようです。

 

ESP32のデータシートをみると、PDMの処理は次の図で示されています。

これだけでは、実際どんな処理が行われているのか把握できませんが、
このPDM処理の部分で周期性ノイズが発生していることには間違いありません。

 

(ESP32 Technical Reference Manualより)

 

 

このノイズ対策には、ディザを加えれば良いようです。
ゼロの値を連続する代わりに、例えば、0,1,0,1,...とすることで、この周期性ノイズはなくなります。
ホワイトノイズは残りますが、これは信号出力時の背景ノイズと同じ大きさなので、我慢するしかないでしょう。
後述のプログラムでは、再生する信号にもディザを加えています。合成音声や小さい音などで量子化誤差がランダムでなくなる信号に対しても周期的ノイズを削減する効果が得られます。

 

 

■クリックノイズ対策



PDMの停止中の出力は0Vの状態が続くため、PDMの開始と終了時にクリックノイズが発生します。
DAC出力のときに行ったように、開始時には0VからVDD/2まで、終了時はその逆のVDD/2から0Vまでのスロープ状に値を変化させることでクリックノイズを削減できます。

 

ただ、PDMではスロープ状の値をデータとして指定しても、その出力はスロープ状になりません。先に示したようにPDMの変調器にはHPFやLPF、アップサンプリングが使われているので、DACの場合と異なり、入力の値がそのままストレートに出力されません。

 

そこで、PDMでスロープを表現するのはあきらめ、この部分は内蔵DACを用いることにします。
同じピンにDACとPDMの出力を割り当て、開始時には、内蔵DACで0からVDD/2[V]でスロープを出力した後、PDMに切り替えます。終了時はその逆です。

 

 

■サンプルプログラム



上で検討したことを盛り込んだ、PDMでサウンド/オーディオ/音声出力するライブラリ(ソースプログラム)を公開します。サンプルプログラムは、上の動画で使ったものです。

 

ダウンロード    TestPDM.zip (TestPDM.ino, PDMout.c, PDMout.h, magic07.h)

 

関数は以下の通りです。

 

  • PDMOut_create(): DACにより出力を0VからVDD/2までスロープさせる。
  • PDMOut_release(): DACにより出力をVDD/2から0Vまでスロープさせる。
  • PDMOut_start(): PDMの初期化後、出力をDACからPDMに切り替える。
  • PDMOut_stop(): PDMを停止し、DACに切り替える(VDD/2を維持)。
  • PDMOut_write(): 引数に1サンプルのデータを指定し、I2Sに送る。PDM変調器にはI2SのDMAで送られる。
  • PDMOut_clear(): I2SのDMAバッファをすべてクリア

内蔵DACも使っているので、出力ピンはGPIO25かGPIO26のいずれかになります。

 

write()のPCMデータは符号付き16bitです。内蔵DACのときは符号無しなので注意してください。

 

PDM変調器のほかに内蔵DACも使用しているので、本プログラムを動かした後はこれらのペリフェラルの状態が使用前と異なる可能性があります。M5Stackでは、PDM出力の終了後にM5.Speaker.tone()関数で音が出なくなりました。M5.begin()での内部状態が変わるためです。これについては、次の関数を呼び出せば、再び出るようになります。

(M5.begin()でGPIO25にPWMを繋げた状態にしているのは、今後変更されるかもしれません)

   ledcAttachPin(SPEAKER_PIN, TONE_PIN_CHANNEL);

 

 

■PDMでステレオ



ここで扱っているのはモノラルの信号ですが、pin_configの指定でPDMのクロックを出力し、i2s_push_sample()でLR別々の値を指定すればステレオのPDMを出力できます。
ただ、PDMのフォーマットでは1つのデータラインで左右の信号が交互に入るので、今回のようにPDM出力を直にアナログ信号として利用する方法ではステレオ化できません。
PDMのデジタルデータに対応したアンプ(MAX98356やSSM2537など)を使う必要があります。

 

 

■PDMは内蔵の8bitDACより量子化精度が高い?



信号を1/2づつ小さくしながら、どこまで聞こえるか試してみました。その結果、PDMの量子化は10bit以上あるようです。
ただ、S/N比(ピーク信号対雑音比)を測定してみると(FS:24KHz, 1023Hzのsin波、最大振幅)、49dBとなり、8bitDACと同等となりました。

 


■M5Stackで使う場合



現在のM5Stackは、アンプに入力する信号レベルが大きすぎて音割れが酷いです。PDMの場合も同じように音が割れます。
PDMの出力にLPFを通した波形のpeak-to-peakの最大振幅は約3.3Vで、DACとほとんど変わりません。

M5Stackで使用しているアンプ(NS4148)にPDMのデジタル信号を直接与えた場合、アンプ自体のLPF特性を利用して鳴るのですが、振幅が3.3Vのパルス信号を与えるのはかなり無理があるようです。特に音量が小さくなると、ノイズが絶対的に大きくなるようです。


試しに、PDM出力を1次のLPFを通してアナログ値にし、抵抗の分圧でゲインを下げててからアンプに入れたら、ずいぶん良くなりましたが・・・

 

 

■さいごに



量子化ビット数は10bit程度ありますが、S/N比が8bitDACと同じのため、音質の差はほどんどありません。
外付けLPFが必要なことを考えると、DACの代わりにPDMを使用するメリットは無さそうです。とほほ・・・

 

 

■リンク



・  前の記事「ESP32でサウンド出力時のクリックノイズ対策(I2S+内蔵DAC)

・  ESP32のデータシート
・  M5Stackのオーディオアンプ「NS4148
 

 

| 電子工作 | 15:04 | - | - |
AquesTalk-ESPを簡単に使うクラス(プログラム)

keywords: ESP32, 音声合成, マルチタスク, M5Stack
 

2018/08/09 AquesTalk-ESP32とArduino-ESP32のVer.upに伴い、ソースコードやライブラリのセットアップ方法は、次の記事を参照してください。「AquesTalk-ESP32 Ver.2.0」を使う

 

■概要


 

前記事では、ESP32用の音声合成ライブラリ AquesTalk pico for ESP32(AquesTalk-ESP)を紹介しました。
今回は、これを用いてバックグラウンドで内蔵DACから音声を出力するプログラム "AquesTalkTTS" を紹介します。

 

このAquesTalkTTSは、AquesTalk-ESPで音声を生成してI2S経由で内蔵DACから出力するまでを一つにまとめたC++のクラスです。初期化と発声の最短2ステップで簡単に音声合成できます。

 

当初はAquesTalk-ESPを非同期処理で使うテクニックを書こうと思っていたのですが、
そもそもESP32はFreeRTOS上で動いているので、素直にこのマルチタスク機能を使います。


 

 


■サンプルプログラム


 

M5Stackで動作します。

 

機能:

  • 時刻を表示
  • 左ボタン:年月日を読み上げ
  • 中ボタン:時分秒を読み上げ
  • 右ボタン:発声を中止

 ※ 読み上げ中も、時刻表示は毎秒変化します。
   ※ 発声中に別のボタンを押すと、即座に発声が切り替わります。

 

ダウンロード

 サンプルプログラム「M5_TalkingClock.zip」(AquesTalkTTSのソースコードも入っています)

 

 

■コード説明



サンプルプログラムをもとに、AquesTalkTTSクラスの使い方を示します。

 

M5_TalkingClock.ino(抜粋)

14 void setup()
15 {
・・・
21   iret = TTS.create(licencekey);
・・・
30 }
31 
32 void loop()
33 {
・・・
38   if(M5.BtnA.wasPressed()){
39     if(getLocalTime(&timeinfo)){
40       // 年月日の読み上げ
41       sprintf(koe,"<NUMK VAL=%d COUNTER=nenn>/<NUMK VAL=%d COUNTER=gatu>/<NUMK VAL=%d COUNTER=nichi>.",
42         timeinfo.tm_year+1900,timeinfo.tm_mon+1, timeinfo.tm_mday);
43       iret = TTS.play(koe, 100);
・・・
60   else if(M5.BtnC.wasPressed()){
61     TTS.stop();
62   }
63 
64   M5.update();
65 }

setup()の中で、AquesTalkTTSを初期化します(21行目)

 

loop()の中で、左ボタンが押されたら(38行目)、時間を取得し、数値読みタグで年月日の音声記号列を生成します(41行目)。あとは、TTS.play()を呼び出すだけです(43行目)。これで年月日が読み上げられます。音声出力はバックグラウンドで処理するので、TTS.play()はすぐに戻ります。

 

右ボタンが押されたら(49行目)、TTS.stop()を呼び出します。これで発声が停止します。発声中以外は特に何も起こりません。


※ あらかじめArduinoIDE上にAquesTalk-ESPをインストールしておく必要があります。
    インストール方法は、前記事の「ビルド準備 - ArduinoIDEの場合」の項を参照ください。
※ ライセンスキーが未指定の場合は、「ゆっくりしていってぬ」になります。

 


■AquesTalkTTSクラス(AquesTalkTTS.cpp/.h)の動作


 

AquesTalk-ESPとI2S経由で内蔵DACから音声出力するまでを1つのクラスにしています。
メソッドは create(), play(), stop(), release() の4つだけです。
create()で初期化、play()で音声合成開始、stopで発声停止、release()でメモリ解放します。

 

create()

AquesTalk-ESPの初期化を行っています。ワークバッファしてヒープ上に400byte確保されます。I2Sの初期化は行っていません。

 

play():

引数の音声記号列をAquesTalk-ESPにセットしてから、音声出力用のタスク(task_TTS_synthe())を起動します。

 

task_TTS_synthe():  (内部のタスク関数)

I2Sの初期化(DMAバッファが確保される)後、フレーム単位での音声合成を行いI2Sへ書き込みます。
最後のフレームまで生成したら、I2Sを解放します。
I2Sと内蔵DACは発声中だけ使用しますので、それ以外のときにビープなど他の音を出力することもできます。

 

stop():

不正な音声記号("#")を指定して、わざとAquesTalk-ESPがエラーで終了するようにしています。

 

※ 発声中にplay()を行うと、発声中のメッセージはその場で終了し、新たに指定したメッセージが始まります。
※ ArduinoIDE環境で動作確認しましたが、ESP-IDFでも動くと思います。
※ クリックノイズ対策済みです。

 

 

■リンク


「AquesTalk pico for ESP32(AquesTalk-ESP)」

https://www.a-quest.com/products/aquestalk_pico_esp32.html

 

「ESP32で音声合成(AquesTalk pico for ESP32)」
http://blog-yama.a-quest.com/?eid=970188

 

「ESP32でサウンド出力時のクリックノイズ対策(I2S+内蔵DAC)」
http://blog-yama.a-quest.com/?eid=970190

 

| AquesTalk | 22:10 | - | - |
PROFILE
Follow
CATEGORIES
LATEST ENTRIES
SEARCH THIS SITE
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
SONY MDR-CD900ST
SONY MDR-CD900ST (JUGEMレビュー »)

普段これで開発しています。
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND