KebbiのTTSに表情を合わせる

この記事は、公開時点での [NUWA Robotics](https://www.nuwarobotics.com/ja/) 社製のロボット Kebbi Air に表情付きでしゃべってもらう方法の覚書です。

:::note info
[NuwaRobotAPIのJavaDoc](https://developer-docs.nuwarobotics.com/sdk/javadoc/reference/com/nuwarobotics/service/agent/NuwaRobotAPI.html)の内容をもとに作成しています。
:::
:::note info
NUWA Robot SDK の使い方については[こちら](https://qiita.com/kanade-FELT/items/d539cbd7d50d22105491)を参照ください。
:::

# KebbiのTTS機能
TTS とは、Text to Speech の略で、指定した文章を自然に読み上げる機能です。文ごとに以下のパラメータを設定することができます。

- `locale`（読み上げ言語）
- `volume`（声の大きさ）
- `pitch`（声の高さ・抑揚）
- `speed`（話す速さ）
- `gain`（2024年12月現在はサポートされていないパラメータのようです）

Kebbi の TTS は`startTTS`という関数を呼び出すことで実行できます。`locale` は`startTTS`内で指定しますが，他のパラメータ指定は`setSpeakParameter`という別の関数で行います。

## TTS機能の使い方
サンプルコードは`MainActivity`クラスの中で実装することを想定しています。
```MainActivity.java
// APIを使用するための前準備
IClientId clientId = new IClientId(this.getPackageName());
NuwaRobotAPI robotAPI = new NuwaRobotAPI(this,clientId);

// パラメータを設定
robotAPI.setSpeakParameter(NORMAL, "isForced", "true"); // パラメータを反映させるための命令
robotAPI.setSpeakParameter(NORMAL, "speed", "100"); // 50~200の間で、何故か文字列で設定

// 読み上げる言葉を設定
robotAPI.startTTS("初めまして、私はKebbiです");

// 読み上げる文の先頭に"//"を入れることで、文をディスプレイに表示させずにしゃべらせることができます。
robotAPI.startTTS("//" + "今話していることはディスプレイに表示されません");
```
TTSが完了すると、`VoiceEventListener`の`onTTSComplete`がコールバックされます。
```MainActivity.java
// VoiceEventListener
robotAPI.registerVoiceEventListener(new VoiceEventListener() {
    @Override 
    public void onTTSComplete(boolean b) {
        // Kebbiがしゃべり終えたらどうするかを記述
    }
}
```
コールバックを利用して別々のパラメータの言葉を続けてしゃべらせることもできます。次がそのサンプルコードです。
```MainActivity.java
// しゃべる内容とパラメータ
private int step = 0;
ArrayList<String> text = new ArrayList<String>() {{
    add("初めまして");
    add("//わたしはケビーです");
}};
ArrayList<String> speed = new ArrayList<String>() {{
    add("100");
    add("150");
}};

private void tts(String current_text, String current_speed){
    robotAPI.setSpeakParameter(NORMAL, "speed", current_speed);
    robotAPI.startTTS(current_text);
}

// VoiceEventListener
robotAPI.registerVoiceEventListener(new VoiceEventListener() {
    @Override 
    public void onTTSComplete(boolean b) {
        step++;
        if(step<text.size())
            tts(text.get(step), speed.get(step));
    }
}
```

# Kebbiの表情を制御する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2640607/0939d6b4-6ffa-8e19-3221-a61614d76209.png)

Kebbiの表情は、顔全体と口元の2種類を設定することができます。`playFaceAnimation`を使用して顔全体を、`mouthEmotionOn`を使用して口元を設定することができます。（それぞれのプレビューはこちら：[faceAnimation](https://developer-docs.nuwarobotics.com/sdk/kebbi_emoji_preview/showPic.html), [mouthEmotion](https://developer-docs.nuwarobotics.com/sdk/kebbi_mouth_preview/showPic.html)）


## 表情制御の使い方
サンプルコードは`MainActivity`クラスの中で実装することを想定しています。

```MainActivity.java
// APIを使用するための前準備（TTSと同様、プロジェクト中に１つだけ書いてあればOK）
IClientId clientId = new IClientId(this.getPackageName());
NuwaRobotAPI robotAPI = new NuwaRobotAPI(this,clientId);

// 表情制御のための準備
robotAPI.initFaceControl(context, context.getClass().getName(), FaceControlConnect);
robotAPI.UnityFaceManager().showUnity();

// パラメータを設定
robotAPI.UnityFaceManager().playFaceAnimation(faceExpression);
robotAPI.UnityFaceManager().mouthEmotionOn(75, mouthExpression); // 1つ目の引数で口の動きの速さを設定

// 表情をデフォルト状態にリセット
robotAPI.UnityFaceManager().playFaceAnimationDefault();
robotAPI.UnityFaceManager().mouthOff();
```
# Kebbiに表情付きでしゃべらせる
以上を踏まえて、表情付きでしゃべるようにしてみましょう。組み合わせると次のようになります。

```MainActivity.java
// APIを使用するための前準備
IClientId clientId = new IClientId(this.getPackageName());
NuwaRobotAPI robotAPI = new NuwaRobotAPI(this,clientId);

private void speak(){
    // 表情制御のための準備
    robotAPI.initFaceControl(context, context.getClass().getName(), FaceControlConnect);
    robotAPI.UnityFaceManager().showUnity();
    
    // TTSパラメータを設定
    robotAPI.setSpeakParameter(NORMAL, "isForced", "true"); // パラメータを反映させるための命令
    robotAPI.setSpeakParameter(NORMAL, "speed", "100"); // 50~200の間で、何故か文字列で設定
    
    // 表情を設定
    robotAPI.UnityFaceManager().playFaceAnimation(faceExpression);
    robotAPI.UnityFaceManager().mouthEmotionOn(75, mouthExpression); // 1つ目の引数で口の動きの速さを設定
    
    // 読み上げる言葉を設定
    robotAPI.startTTS("初めまして、私はKebbiです");
}

// VoiceEventListener
robotAPI.registerVoiceEventListener(new VoiceEventListener() {
    @Override 
    public void onTTSComplete(boolean b) {
        // 表情をデフォルト状態にリセット
        robotAPI.UnityFaceManager().playFaceAnimationDefault();
        robotAPI.UnityFaceManager().mouthOff();
    }
}
```
このように、しゃべり終えた段階で表情をデフォルトに戻すようにしてやると、TTSの例のように言葉ごとに表情を変えたり、間を作りたいときに口パク状態にならなかったりと利点があります。

# おわりに
以上でNUWA Robot SDKを用いてKebbiに好きな言葉をしゃべらせることができるようになりました。実際に活用するときは他の動作や処理に合わせて使うことになると思うので、handlerで管理すると良いと思います。
記述に不明点、誤り等見つけましたらお知らせください。よろしくお願いいたします。

## 制御しようと思って実現できなかったこと

### しゃべりの抑揚のみの変更
TTSエンジンの都合でパラメータ`pitch`を使用すると声調と抑揚の両方が設定されるらしく、個別の調整はできないとのことでした。

### しゃべっていない状態での表情の変更
[faceAnimation](https://developer-docs.nuwarobotics.com/sdk/kebbi_emoji_preview/showPic.html)のプレビューを見てわかるように、表情は口元のモーションもセットのようです。口元の動きは [mouthEmotion](https://developer-docs.nuwarobotics.com/sdk/kebbi_mouth_preview/showPic.html)で上書きが可能ですが、口元を消すことはできませんでした。よってしゃべっていない状態での表情はデフォルトのものしか設定できません。

## トラブルシューティング

### TTSのパラメータを設定したのに反映されない
TTS機能の使い方セクションのサンプルコードを参考に、APIを使用するための前準備や`setSpeakParameter`の引数が`NORMAL`になっているか、`true`になっているかを確認してください。`isForced`は`false`に戻すまでに１度呼べば大丈夫です。

### 顔が表示されない
表情制御のセクションのサンプルコードを参考に、APIを使用するための前準備や`initFaceControl`、`showUnity`が呼び出されているかを確認してください。これらも１度呼べば大丈夫です。