フリーソフト9VAeきゅうべえに音声合成機能をつけた

- [9VAeきゅうべえは、無料で動画素材がつくれるアプリ](https://qiita.com/danjiro/items/253e5a33a38599098274)です。
- 文字や矢印が動く [モーショングラフィッ クソアプリ](https://dnjiro.hatenablog.com/entry/kaisetu)です。アフターエフェクトに手が出ない人におすすめ。[Youtube(Linux)](https://youtu.be/Khx31_0IWcE)、[(Windows)](https://youtu.be/0k9bAN4Jge4)

今回、**9VAe Linux版にしゃべる機能を追加**しました。今は[全バージョンの9VAe](https://9vae.blogspot.com/p/9vae-download.html)がしゃべります

[作成例＞クリックすると Youtubeが開きます](https://youtu.be/XBuHvhu7Eao)
[![0voice-kof.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/06069b17-2e14-eb1c-141a-4ef4684c37b2.gif)](https://youtu.be/XBuHvhu7Eao)


|ダウンロード|[32bit<br>Linux-x86](https://drive.google.com/file/d/0B_jLDEPlHKVjTmU1M05GanZLWjg/view?usp=sharing)|[64bit<br>Linux-x64](https://drive.google.com/file/d/1iHRmcqLRBtw646jCXM2veK08hLJ4dBK1/view?usp=sharing)|試し方|
|:--:|:--:|:--:|:--:|
|Linux Mint<br>Ubuntu <br>Intel(x86/x64)|[![32bit9va.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/79486048-9441-a72e-850e-f06dae3bc43f.png)](https://drive.google.com/file/d/0B_jLDEPlHKVjTmU1M05GanZLWjg/view?usp=sharing)|[![64bit9va.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/3fff56ea-0b54-fa6d-4882-acb1116d6aff.png)](https://drive.google.com/file/d/1iHRmcqLRBtw646jCXM2veK08hLJ4dBK1/view?usp=sharing)|[Windows用<br> Linux起動USBの作り方](https://dnjiro.hatenablog.com/entry/linuxboot)|
|ラズベリーパイ|[![qva64x64.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/45d9d016-e864-7e08-670b-0d23de6f4c85.png)](https://drive.google.com/file/d/0B_jLDEPlHKVjd0RPOFVWbWxwZkU/view?usp=sharing)||[他のOS版<br>9VAe](https://qiita.com/danjiro/items/253e5a33a38599098274)|

1. ZIPファイルをダウンロード（上をクリックしてから、[↓]ダウンロードボタン）
1. Linux上で解凍
1. フォルダの中の「9va-pi」を実行。9VAeが起動します。（Ubuntu、Mintでは動くはず。他のLinuxでは、9view(プレーヤー)しか起動しないものがあるようです）
![000-qva.svg.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/fe60e0e0-dfb7-5231-6146-ac8ff3a9a13d.gif)

- [9VAeきゅうべえの使い方はこちら](https://dnjiro.hatenablog.com/entry/9vae-open-svg)。この記事の アニメGIF、動画はすべて 9VAe Linux版で作成しました。
- Ubuntu 64bitで OpenJtalk が apt-get できませんでした。Linux Mint は64bit でもOK。[Ubuntu 64bit に OpenJTalkを入れる方法はこちら(Smile Engineering blog)](https://blog.goo.ne.jp/jsp_job/e/78ce2862a5a634520dc1442b25ba0be5)

# 開発方針

- 9VAeは、１枚の絵に文字やイラストをいれて「ひとコマ」と設定するだけで、解説動画がつくれるのが特長です。
- ひとコマアニメは、１枚絵を書いただけで、書いた順番に文字や、図形が再生されます。文字が１文字ずつ順番にでてくる動画がすぐ作れます。
- この文字に「しゃべる」属性をつけるだけで、ゆっくり解説動画がつくれるようにします。
- しゃべる声は、男声と女声の２種類とし、２人が会話しながら解説する動画がつくれること。
- しゃべる声はユーザーがカスタマイズできるようにします。

## わりきり仕様

- 9VAe は、どんな環境（Win / Mac / iOS / iPad / Android / Linux / Amazon Fire / ラズベリーパイ）でも同じように動きます。異なる OS 間で 互換性があります。これはすごいことですが、これを実現するために、移植が難しい仕様はいれないのがポイント。しゃべる機能は以下のようにしました。
- しゃべる声は、男声と女声の２種類
- 同時にしゃべることはない。
- 一度、しゃべりはじめると、最後まで、しゃべってから、次の発話がはじまる。
- しゃべる速さ、ピッチ、音量はエンジンの設定にまかせる。これらの調整は、今後の課題とする。

この割り切り仕様で、無料で使えるものをさがし、Linux では、音声合成エンジン Open JTalk を使うことにしました。

## 実装に必要な機能

- テキストをしゃべる
- 今しゃべっているかどうか調べる
- しゃべる音声をWAV ファイルに出力
- 発声の中断

各OSで上の機能をみたす関数を用意すれば移植できそうです。

現在、Linux、Windows、Android/Chromebook、Macintosh に移植できています。上の機能を実現方法を表にまとめました。

|OS|音声合成|WAV作成|中断|発声中チェック|
|:--:|:--:|:--|:--|:--|
|Linux（日本語）|OpenJTalk<br>(外部ツール)|-ow ファイルパス|kill(pid, SIGKILL); |waitpid(pid, &status, WNOHANG);|
|Linux（英語）|eSpeak|--stdout > 音声ファイル|kill(pid, SIGKILL); |waitpid(pid, &status, WNOHANG);|
|Windows（日本語）|[SofTalk](https://dnjiro.hatenablog.com/entry/9vawin-talk)<br>(外部ツール)|/PS:True /R:ファイルパス /W:しゃべる言葉|mciSendString("stop",,,)|mciSendString("status",,,)|
|Windows（英語）|[eSpeak](https://dnjiro.hatenablog.com/entry/9vawin-talk)<br>(外部ツール)|-w 音声ファイル |mciSendString("stop",,,)|mciSendString("status",,,)|
|Android / Chromebook / Amazon Fire|tts = new TextToSpeech(this, this);|tts.synthesizeToFile();|tts.stop();|tts.speak()で発声追加。発声チェックはしない|
|Macintosh|tts = [[NSSpeechSynthesizer alloc] init];|[tts startSpeakingString:しゃべる言葉 toURL:ファイルパス];|[tts stopSpeaking];|[tts isSpeaking];|
|iOS / iPad|tts = [[AVSpeechSynthesizer alloc] init];|[tts [writeUtterance:utterance toBufferCallback:](https://stackoverflow.com/questions/61969696/objc-avspeechutterance-writeutterance-how)]; |[tts stopSpeaking];|[tts isSpeaking];|


# 音声合成 Open JTalk

[参考文献：Raspberry piで日本語音声合成(Open JTalk)を試してみる。](https://qiita.com/lutecia16v/items/8d220885082e40ace252)

## インストール方法（Linux）

```shell-session
$ sudo apt-get install open-jtalk
$ sudo apt-get install open-jtalk-mecab-naist-jdic
$ sudo apt-get install hts-voice-nitech-jp-atr503-m001
```

これで、男声でしゃべれるようになります。
コマンド入力するとき、数文字入力して、TABキーを押せば楽に入力できます。

## 女声のインストール方法

女性の声は Meiちゃんがよく使われているようです。

[参考文献：OpenJTalkでおしゃべりする](https://zenn.dev/kotaproj/books/raspberrypi-tips/viewer/038_kiso_openj#%E5%90%88%E6%88%90%E9%9F%B3%E5%A3%B0%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)

```shell-session
$ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.6/MMDAgent_Example-1.6.zip/download -O MMDAgent_Example-1.6.zip

$ unzip MMDAgent_Example-1.6.zip MMDAgent_Example-1.6/Voice/*
$ sudo cp -r MMDAgent_Example-1.6/Voice/mei/ /usr/share/hts-voice
```



## 音声コマンド

上のインストールを行ったあと、ターミナルで以下の命令をいれればしゃべりました。


- テキストをしゃべる
```shell-session
$ echo 12345 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow test.wav
$ aplay test.wav
```
これで「いちまんにせんさんびゃくよんじゅうご」としゃべりました。12345の部分に UTF8 の文字をいれれば自由にしゃべります。

- -ow 出力ファイル名 で、WAVファイルに出力できるようです。
- aplay が WAVファイルを発声する命令です。-q オプションで表示が消えます。

[参考文献：萌え声を探せ！Open JTalkのパラメータをいろいろ変化させてみた！](http://moblog.absgexp.net/openjtalk/)

## Linuxでの実装

- 発声の中断、しゃべっているかどうかのチェック、しゃべり方の調整が行えるようにするために、「Open JTalk で WAV 作成する男声命令」「女声命令」を用意し、まず、WAVを作成することにしました。

```sh:talkman_jp.sh
#!/bin/sh
echo "$1" | open_jtalk -jm 1.2 -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow  /tmp/#9va_talk.wav && mv /tmp/#9va_talk.wav /tmp/9va_talk.wav

```

```sh:talkwoman_jp.sh
#!/bin/sh
echo "$1" | open_jtalk -m /usr/share/hts-voice/mei/mei_normal.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /tmp/#9va_talk.wav  && mv /tmp/#9va_talk.wav /tmp/9va_talk.wav
```

まず、#9va_talk.wav というファイル名でWAVを作成し、出力後に 9va_talk.wavという名前に変更しています。名前固定がいまいちですが、わりきり仕様ということで・・・

この命令を読みだす部分は以下のようになってます。
作成した WAVファイルの再生は、Linuxでは「aplay」命令をつかっています。

```c:AcGbl
#define TEXT_MAX_BUF 1024
#define aTALK_WAV_FILE "/tmp/9va_talk.wav" //Waveファイル
#define aTALK_WAV_CMD "aplay"  //Waveを再生する命令

typedef struct {
	pid_t  	pidMakeWav; 
	pthread_t thrMakeWav;
	char	prgMakeWav[600]; //jtalk を呼び出す Shell
	char	utf8Talk[TEXT_MAX_BUF]; //しゃべる言葉

	pid_t  	pidTalk; 
	pthread_t thrTalk;
	char	prgTalk[100]; //Wav再生
} AcGbl;



```


Wave 作成
```c:thread_MakeWav
static void  *thread_MakeWav(void *ax){
    AcGbl *axx = (AcGbl *)ax; char *cmd = axx->prgMakeWav;
    pid_t pid = fork(); int status;
    if(pid==0){
        execlp(cmd , cmd, axx->utf8Talk, NULL);
	    exit(0);
    }
    waitpid(pid, &status, 0);
    axx->pidMakeWav =pid;
}
```

Wave再生
```c:thread_Talk
static void  *thread_Talk(void *ax){
    AcGbl *axx = (AcGbl *)ax; char *cmd = aTALK_WAV_CMD;
    pid_t pid = fork();
    if(pid==0){
        execlp(cmd , cmd, "-q", aTALK_WAV_FILE, NULL);
	    exit(0);
    }
    axx->pidTalk =pid;
}
```

- fork することで、バックグランドで音を鳴らしつづけます。再生が終わるとプロセスが終了するので、それを検出することで、しゃべり終わったかどうかをチェックします。

しゃべっているかどうかチェック

```c:isTalking
int  isTalking(AcGbl *axx) 
{
    pid_t pid = axx->pidTalk; int status;
    pid = waitpid(pid, &status, WNOHANG);
    return pid==0; //再生中
}
```

発声の中断

```c:stopTalk
void  stopTalk(AcGbl *axx) 
{
	int status;
	if(isTalking(axx)){
		kill(axx->pidTalk, SIGKILL); wait(&status);
		pthread_cancel(axx->thrTalk);
		pthread_join(axx->thrTalk, NULL);
	}
}
```

しゃべるテスト

```c:test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <stddef.h> //NULL
#include <sys/types.h> //fork
#include <sys/unistd.h> //fork
#include <sys/wait.h>
#include <sys/signal.h>


void main(int argc, char *argv[])
{
    AcGbl ag; memset(&ag,0,sizeof(&ag));

    puts("Start");
    strncpy(ag.prgMakeWav, "/sh命令を置いた場所/talkman_jp.sh",sizeof(ag.prgMakeWav));//男声 
    //strncpy(ag.prgMakeWav, "/sh命令を置いた場所/talkwoman_jp.sh",sizeof(ag.prgMakeWav));//女声 

    //しゃべらせたい言葉、UTF8 でいれる
    strncpy(ag.utf8Talk, "1234567",sizeof(ag.utf8Talk));

    puts("Make wave");
	thread_MakeWav(&ag); //WAVの作成

    puts("Talk 1");
	thread_Talk(&ag);
    for(;isTalking(&ag);) ;//しゃべり終わるのを待つ

    puts("Talk 2");
	thread_Talk(&ag);
    sleep(2); //2秒後に終了

    puts("Break");
    stopTalk(&ag); 
}

```

テストプログラムのコンパイルと実行

```shell-session
$ gcc -pthread test.c
$ ./a.out
```
1. まず「ひゃくにじゅうさんまんよんせんごひゃくろくじゅうなな」としゃべります。
1. 喋り終わるのをまってから、もう一度しゃべります。こんどは２秒あとに中断して終了します

# 9VAeでの文字入力としゃべる設定

- Open JTalk がインストールされていなければ、しゃべりません。9VAe Linux版でしゃべる機能を試すには、Open JTalk をインストールしてください。
- Linux Mint. Ubuntu32bitは上の方法でインストールできました。[Ubuntu 64bit の場合はこちら(Smile Engineering blog)](https://blog.goo.ne.jp/jsp_job/e/78ce2862a5a634520dc1442b25ba0be5)


## 文字入力としゃべる設定

9VAeで文字を入力したあと、文字の選択枠の中心の「＋」をクリックすると表示されるメニューに「しゃべる」「コメント」の２項目を追加しました。「コメント」文字は表示されない文字で、画面にないせりふをしゃべるときに使います。
しゃべる属性をつけると、男声でしゃべるようになります。

![001c-inp.svg.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/ac2446a4-35a1-f084-f4d1-83f794aad16d.gif)

しゃべるを解除するには、文字パレット（次の項目）の右端のボタンをつかいます。次の項目参照


## 女性の設定

右側の文字パレットの配置属性メニューに「男がしゃべる」「女がしゃべる」を追加しました。文字を選んで（複数も可能）「女がしゃべる」にすると女声になります。ただし女声をインストールしていなければ男声でしゃべります。

![002-wmn.svg.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/9ed30808-a082-5d04-3898-dd8669dc87f1.gif)

- もう一度同じ設定をクリックすると「しゃべる」属性が解除されます。

## しゃべる順序、しゃべる時間の調整

ページの時間を「ひとコマ」に設定すると、しゃべる音声の長さを計測し、ページの時間を自動設定します。

 ![003-hit.svg.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/cb3193a4-4a43-5748-0920-c72d78e27acd.gif)

しゃべる順番は、つぎのようになります。

1. しゃべる設定されたコメント文字があれば、重なり順序の下からしゃべります。
1. しゃべる設定された文字を重なり順序の下からしゃべります。
1. 前の声がしゃべり終わるまで、つぎの文字をしゃべりません。
1. 文字はページが切り替わっても最後までしゃべります。しゃべりはじめていない文字は、次のページにうつると読み飛ばされます。
1. 再生中止ボタンを押すと、しゃべるのをやめます。




# 動画作成

- ファイルメニュー＞「動画出力」で、上のしゃべるルールに従って、しゃべる動画が出力されます。
- [動画出力するには ffmpeg が必要です。インストールはこちら](https://dnjiro.hatenablog.com/entry/2022/01/12/081725)。[Youtube解説](https://youtu.be/Lc5-ac01n6w)
![004-mp4.svg.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/02285b73-3ffe-744a-a92f-508e6735ac99.gif)

以下の動画は、この方法で作成しました。


[作成例＞クリックすると Youtubeが開きます](https://youtu.be/XBuHvhu7Eao)
[![0voice-kof.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/06069b17-2e14-eb1c-141a-4ef4684c37b2.gif)](https://youtu.be/XBuHvhu7Eao)

# マルチOS対応の仕組み

- 9VAeきゅうべえは、Win/Mac/Android/iOS で動きます。ソースは９割が C言語で共通化されています。しかし、文字入力、描画、しゃべるなどは、OSごとに処理が異なり、各OS用のルーチンに差し替えています。
- アニメ再生などの時間管理は、C の共通ソースにあり、CからOSルーチンをどう呼び出すかが問題となります。各OS処理を C から呼び出せるか、以下のようになっています。

|OS|OS側|C から直接呼べるか|
|:--:|:--:|:--:|
|Windows|Win32 C++|〇|
|Linux|GTX C++|〇|
|Android|Java|〇(JNI)|
|Mac|Objective-C|×|
|iOS/iPad|Objective-C|×|

- C から直接呼び出せない場合は、非同期処理になります。例えば、文字をしゃべらせる時間がすぐにはわからないので、結果がわかってから処理を続ける仕組みが必要になります。
- C からOSの関数を呼び出せても、処理結果がすぐには出ない処理もあります（文字入力など）。

OSによって非同期処理が異なるため、しゃべる処理の移植はかなり複雑になりました。