目次

Open JTalk

Open JTalkgalateatalk の開発グループが新たに開発したオープンソース日本語音声合成エンジン。 コンパイルにはhts_engine APIが必要。

以下、ubuntu および cygwin での動作について記述する。

履歴

リンク

Linux での実行

方針:ubuntu 10.04 でcheckinstallを使い deb パッケージを作りながらインストールしてみた。

現状:実行コマンドは動いた。音声合成の動作確認ができた。

$ 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

実行コマンドのオプション確認

$ /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 は Windows で再生できる。

個人的な評価

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

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]);