目次
Open JTalk
Open JTalkはgalateatalk の開発グループが新たに開発したオープンソース日本語音声合成エンジン。 コンパイルにはhts_engine APIが必要。
- New and Simplified BSD ライセンス
- Open JTalk
- 2011-12-25 にバージョン 1.05 リリース。
- 2011-07-07 にバージョン 1.04 リリース。
- hts_engine API 最新版は 2011-07-07 にリリースされたバージョン 1.05 である。
- Ubuntu 12.04 / Debian wheezy 公式パッケージに Open JTalk が入るという話 http://bit.ly/yl0ABs
以下、ubuntu および cygwin での動作について記述する。
履歴
- 2010-05-27: Ubuntu 10.04 および Open-JTalk 1.01 に対応して更新した。EUC-JP ではなく UTF-8 でビルドすることにした。
- 2010-07-01: libopenjtalk を作りかけている。
- 2010-10-30: nvdajp から 2010.2j がリリースされる。
- 2011-03-08: Open JTalk の音素継続長の不具合を回避する
リンク
- Open JTalkをVS2008で (2010-02-19)
- SEMSのいろいろ (VoIP関連のシステム記事。2010-07-01)
- GNOME Orca で Open JTalk も 使えるようにしました。 (2010-08-28)
- Ubuntuで音声合成 open jtalkを試す (2010-10-27)
Linux での実行
方針:ubuntu 10.04 でcheckinstallを使い deb パッケージを作りながらインストールしてみた。
現状:実行コマンドは動いた。音声合成の動作確認ができた。
- hts-engine-api_1.03-1_i386.deb
- open-jtalk_1.01-1_i386.deb
- 2010-02-19 公開を終了しました。後日更新版を公開したいと思います。
$ sudo dpkg -i hts-engine-api_1.03-1_i386.deb $ sudo dpkg -i open-jtalk_1.01-1_i386.deb
mecab が配布パッケージに含まれていて open_jtalk バイナリの一部として使われている。
naist_jdic を open_jtalk 仕様に変換したものが(文字コードごとに)ソースコードと別に配布されている。
実際には naist_jdic および mecab は open_jtalk ソースパッケージの一部として同梱されており、 open_jtalk ソースパッケージから生成された辞書のバイナリが配布元に置かれているとのこと。
この他に話者モデルのファイルが配布されている。現時点では話者 m001 のモデルのみが公開されているようだ。
パッケージ作成の作業記録
tar.gz のダウンロード : http://sourceforge.net/projects/hts-engine/
$ tar xvfz hts_engine_API-1.03.tar.gz $ cd hts_engine_API-1.03 $ sh configure $ make $ sudo checkinstall make install // オプションの設定 $ ls *.deb hts-engine-api_1.03-1_i386.deb
tar.gz のダウンロード : http://sourceforge.net/projects/open-jtalk/
$ tar xvfz open_jtalk-1.01.tar.gz $ cd open_jtalk-1.01 $ sh configure --with-charset=UTF-8 $ make $ sudo checkinstall make install // オプションの設定 $ ls *.deb open-jtalk_1.01-1_i386.deb
- 1.00 でデフォルトのビルドをした場合:config.log によると CHARSET='-D CHARSET_EUC_JP' と判定されている。
- 1.01 では configure –with-charset=UTF-8 で encoding の設定ができる。詳しくは configure –help で。
実行コマンドのオプション確認
$ /usr/local/bin/open_jtalk The HMM-based speech synthesis system (HTS) Open JTalk version 1.01 (http://open-jtalk.sourceforge.net/) Copyright (C) 2008-2010 Nagoya Institute of Technology All rights reserved. The HMM-based speech synthesis system (HTS) hts_engine API version 1.03 (http://hts-engine.sourceforge.net/) Copyright (C) 2001-2010 Nagoya Institute of Technology 2001-2008 Tokyo Institute of Technology All rights reserved. Yet Another Part-of-Speech and Morphological Analyzer (Mecab) mecab version 0.98 (http://mecab.sourceforge.net/) Copyright (C) 2001-2008 Taku Kudo 2004-2008 Nippon Telegraph and Telephone Corporation All rights reserved. NAIST Japanese dictionary mecab-naist-jdic version 0.6.1-20090630 (http://naist-jdic.sourceforge.jp/) Copyright (C) 2009 Nara Institute of Science and Technology All rights reserved. open_jtalk - An HMM-based text to speech system usage: open_jtalk [ options ] [ infile ] options: [ def][ min--max] -x dir : dictionary directory [ N/A] -td tree : decision trees file for state duration [ N/A] -tf tree : decision trees file for Log F0 [ N/A] -tm tree : decision trees file for spectrum [ N/A] -md pdf : model file for state duration [ N/A] -mf pdf : model file for Log F0 [ N/A] -mm pdf : model file for spectrum [ N/A] -df win : window files for calculation delta of Log F0 [ N/A] -dm win : window files for calculation delta of spectrum [ N/A] -ow s : filename of output wav audio (generated speech) [ N/A] -ot s : filename of output trace information [ N/A] -s i : sampling frequency [16000][ 1--48000] -p i : frame period (point) [ 80][ 1--] -a f : all-pass constant [ 0.42][ 0.0--1.0] -g i : gamma = -1 / i (if i=0 then gamma=0) [ 0][ 0-- ] -b f : postfiltering coefficient [ 0.0][-0.8--0.8] -l : regard input as log gain and output linear one (LSP) [ N/A] -u f : voiced/unvoiced threshold [ 0.5][ 0.0--1.0] -ef tree : decision tree file for GV of Log F0 [ N/A] -em tree : decision tree file for GV of spectrum [ N/A] -cf pdf : filename of GV for Log F0 [ N/A] -cm pdf : filename of GV for spectrum [ N/A] -jf f : weight of GV for Log F0 [ 0.7][ 0.0--2.0] -jm f : weight of GV for spectrum [ 1.0][ 0.0--2.0] -k tree : use GV switch [ N/A] -z i : audio buffer size [ 1600][ 0--48000] infile: text file [stdin] note: option '-d' may be repeated to use multiple delta parameters. generated spectrum and log F0 sequences are saved in natural endian, binary (float) format.
1.00 では入力は「ラベルファイル」となっていたが、読み上げたいテキストファイルを与える仕様である。 1.01 で infile は "text file" と修正された。
動かしてみる
sourceforge.net で配布されている hts_voice_nitech_jp_atr503_m001-1.01 と open_jtalk_dic_utf_8-1.00 をカレントディレクトリに展開。 こんなスクリプトを書いた。
[run_jtalk.sh]
#!/bin/bash VOICE=hts_voice_nitech_jp_atr503_m001-1.01 DIC=open_jtalk_dic_utf_8-1.00 open_jtalk \ -td $VOICE/tree-dur.inf \ -tf $VOICE/tree-lf0.inf \ -tm $VOICE/tree-mgc.inf \ -md $VOICE/dur.pdf \ -mf $VOICE/lf0.pdf \ -mm $VOICE/mgc.pdf \ -df $VOICE/lf0.win1 \ -df $VOICE/lf0.win2 \ -df $VOICE/lf0.win3 \ -dm $VOICE/mgc.win1 \ -dm $VOICE/mgc.win2 \ -dm $VOICE/mgc.win3 \ -ef $VOICE/tree-gv-lf0.inf \ -em $VOICE/tree-gv-mgc.inf \ -cf $VOICE/gv-lf0.pdf \ -cm $VOICE/gv-mgc.pdf \ -k $VOICE/gv-switch.inf \ -x $DIC \ -ow _hoge.wav \ -ot _log.txt \ test.txt
[test.txt] (UTF-8)
今日はいい天気です。
上記のオプションはファイル名から推測して見つけたものである。
ちなみにオプションで指定するファイルが不足していると Floating Point Error などのエラーになる。
スクリプトを実行すると以下の2つが出力された。zip したものを下記に添付する。
- _hoge.wav : open_jtalk_101_hoge_wav.zip
- _log.txt : open_jtalk_101_log_txt.zip
_hoge.wav は Windows で再生できる。
個人的な評価
- unidic を使っていないのに(アクセント辞書を使っていないはずなのに)韻律は比較的自然であると感じる。
- 音声デバイスを叩く機能は持っていないようであり、また1つテキストを合成するごとにプロセスを起動する仕様になっている。
- _log.txt には継続長情報らしきものがある。galatea でリップシンクできるかも。。
Cygwin での実行
以下は 1.00 版に関する記述。
環境は Windows XP 32bit である。
作業に先立って cygwin を setup.exe でアップデートした。gcc 4.3.4 を使用。
hts_engine を configure; make; make install する。
続いて open_jalk を configure; make する。
make が途中で止まる。mecab/src/common.h を下記のように直す(include3つを後ろにずらす)
#include <cstring> #include <string> #include <iostream> // #include <algorithm> // #include <cmath> // #include <sstream> #ifdef __CYGWIN__ #define _GLIBCXX_EXPORT_TEMPLATE #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif // tricky macro for MSVC #if defined(_MSC_VER) || defined(__CYGWIN__) #define for if (0); else for /* why windows.h define such a generic macro */ #undef max #undef min #endif /* for Open JTalk */ #if defined (_MSC_VER) /* for Open JTalk */ #define snprintf _snprintf #endif #include <algorithm> #include <cmath> #include <sstream>
これでコンパイルが通った。make install する。
CHARSET は config.log によると euc-jp になっていた。
上記と同じように実行。すると、wav ファイルも生成されるけど、デバイスから音声も出力される。
オーディオデバイスを無効化する
オーディオ出力を自前で制御したい場合にはどうするのか?
ソースを追ってみたら、HTS_engine が勝手に Win32 のオーディオ API を有効化していることが分かった。
HTS_Engine_create_gstream() の手前で無効化する必要がある。
ぜんぜん綺麗でないが open_jtalk.c に手を加えた。
OpenJTalk_synthesis() がある bin/open_jtalk.c:144 に下記のように追加。
HTS_Engine_create_sstream(&open_jtalk->engine); HTS_Engine_create_pstream(&open_jtalk->engine); open_jtalk->engine.global.audio_buff_size = 0; // nishimotz HTS_Engine_create_gstream(&open_jtalk->engine);
これで、音声ファイルを出力するだけのコマンドラインプログラムが完成。
音声データ出力部を理解する
http://github.com/nishimotz/htsengineapi/blob/master/lib/HTS_engine.c
void HTS_Engine_save_generated_speech(HTS_Engine * engine, FILE * fp)
および
void HTS_Engine_save_riff(HTS_Engine * engine, FILE * fp)
にて合成結果をファイルに書き出している。
後者の重要な部分は
HTS_GStreamSet *gss = &engine->gss;
for (i = 0; i < HTS_GStreamSet_get_total_nsample(gss); i++) { temp = HTS_GStreamSet_get_speech(gss, i); HTS_fwrite_little_endian(&temp, sizeof(short), 1, fp); }
である。
HTS_GStreamSet_** 系は hts_engine_API の中で使われているのだが、これらは /usr/local/include/HTS_engine.h にて定義されている。
エラーメッセージの出力
since 2011-03-04
HTS_engine_API の lib/HTS_misc.c にて下記の関数が定義されている:
void HTS_error(const int error, char *message, …)
出力先は stderr 固定。第一引数が 0 の場合はワーニング出力となり、exit() を実行しない。
音素継続長の処理
since 2011-03-04
- HTS_engine.c: HTS_Engine_create_sstream()
- HTS_sstream.c: HTS_SStreamSet_create()
- HTS_sstream.c: HTS_set_duration()
JTalk m001 話者モデルでアクセント句の数が一定値を超えたときに状態ごとのフレーム数割り当てが不適切になる現象と推測。mei_normal では起きない。
HTS_Question_match() ですべての質問とのマッチに失敗して一番グローバルな継続長モデルが選択される。無音も含めたグローバルモデルなので継続長の平均が不適切になる。という仮説。
いろいろ調べてわかったのだが jpcommon/jpcommon_label.c を以下のようにいじると、コンテクストが見つからない現象が回避されて継続長がまともになる。。
static int index_accent_phrase_in_breath_group(JPCommonLabelAccentPhrase * a) { // omitted if (i > 3) i = 3; // 3の値は適当 return i; }
static int count_mora_in_utterance(JPCommonLabelMora * m) { // omitted if (i > 10) i = 10; // 10の値は適当 return index_mora_in_utterance(m) + i; }
Open JTalk 1.03 の更新
2011-05-08 Open JTalk 1.02 から 1.03 へのバージョンアップにおいて jpcommon/jpcommon_label.c の更新が確認された。 効果は評価中。
diff --git jpcommon/jpcommon_label.c jpcommon/jpcommon_label.c index 0e9a61a..6ee6a51 100644 --- jpcommon/jpcommon_label.c +++ jpcommon/jpcommon_label.c @@ -4,7 +4,7 @@ /* http://open-jtalk.sourceforge.net/ */ /* ----------------------------------------------------------------- */ /* */ -/* Copyright (c) 2008-2010 Nagoya Institute of Technology */ +/* Copyright (c) 2008-2011 Nagoya Institute of Technology */ /* Department of Computer Science */ /* */ /* All rights reserved. */ @@ -652,8 +652,11 @@ void JPCommonLabel_make(JPCommonLabel * label) sprintf(label->feature[i], "%s/A:xx+xx+xx", label->feature[i]); else { tmp1 = index_mora_in_accent_phrase(p->up); - sprintf(label->feature[i], "%s/A:%d+%d+%d", label->feature[i], - tmp1 - p->up->up->up->accent, tmp1, count_mora_in_accent_phrase(p->up) - tmp1 + 1); + tmp2 = + p->up->up->up->accent == + 0 ? count_mora_in_accent_phrase(p->up) : p->up->up->up->accent; + sprintf(label->feature[i], "%s/A:%d+%d+%d", label->feature[i], tmp1 - tmp2, tmp1, + count_mora_in_accent_phrase(p->up) - tmp1 + 1); } /* for B: */ if (short_pause_flag == 1) @@ -698,7 +701,8 @@ void JPCommonLabel_make(JPCommonLabel * label) sprintf(label->feature[i], "%s/E:xx_xx!xx_xx", label->feature[i]); else sprintf(label->feature[i], "%s/E:%d_%d!%s_xx", label->feature[i], - count_mora_in_accent_phrase(a->head->head), a->accent, + count_mora_in_accent_phrase(a->head->head), + a->accent == 0 ? count_mora_in_accent_phrase(a->head->head) : a->accent, a->emotion == NULL ? "xx" : a->emotion); if (i == 0 || i == label->size - 1 || short_pause_flag == 1 || a == NULL) sprintf(label->feature[i], "%s-xx", label->feature[i]); @@ -717,7 +721,8 @@ void JPCommonLabel_make(JPCommonLabel * label) tmp1 = index_accent_phrase_in_breath_group(a); tmp2 = index_mora_in_breath_group(a->head->head); sprintf(label->feature[i], "%s/F:%d_%d#%s_xx@%d_%d|%d_%d", label->feature[i], - count_mora_in_accent_phrase(a->head->head), a->accent, + count_mora_in_accent_phrase(a->head->head), + a->accent == 0 ? count_mora_in_accent_phrase(a->head->head) : a->accent, a->emotion == NULL ? "xx" : a->emotion, tmp1, count_accent_phrase_in_breath_group(a) - tmp1 + 1, tmp2, count_mora_in_breath_group(a->head->head) - tmp2 + 1); @@ -733,7 +738,8 @@ void JPCommonLabel_make(JPCommonLabel * label) sprintf(label->feature[i], "%s/G:xx_xx%%xx_xx", label->feature[i]); else sprintf(label->feature[i], "%s/G:%d_%d%%%s_xx", label->feature[i], - count_mora_in_accent_phrase(a->head->head), a->accent, + count_mora_in_accent_phrase(a->head->head), + a->accent == 0 ? count_mora_in_accent_phrase(a->head->head) : a->accent, a->emotion == NULL ? "xx" : a->emotion); if (i == 0 || i == label->size - 1 || short_pause_flag == 1 || a == NULL) sprintf(label->feature[i], "%s-xx", label->feature[i]);