Node.jsでVOICEROIDをしゃべらせてみる

# 概要
WebフレームワークでVOICEROIDを操作してみたいな～とふと考え付いたので、いずれReactなどで動かすためにまずはNode.jsでVOICEROIDを操作できるかという技術検証です。実際にReactでも動くかはまだ試せていないのでいずれやります。

# Node.jsで動かす方法
といってもVOICEROID製品はほとんどC#で作られていることが多くNode.jsから直接制御するのは大変そうということがわかりました。ただ、調べるとどうやら[AssistantSeika](https://wiki.hgotoh.jp/documents/tools/assistantseika/assistantseika-000)というアプリケーションを使えば行けそうだということがわかりそのラッパーである[Node-Seika](https://github.com/nusu-github/node-seika)というものがあることを知ったのでこれを使ってみようと思います。

# 使うライブラリなど
大前提として、喋らせるVOICEROIDの正規ライセンス、AssistantSeikaが必要になります。なお、AssistantSeikaはVOICEVOX等にも対応しているようなので持っていない方はそちらで代用してもいいかもしれません。
- Node.js(v20.10.0)
- TypeScript
- VOICEROID 製品
- AssistantSeika

# 実装してみる
それでは実装してみます。今回はNode.jsを使うということでせっかくなのでTypeScriptも使います。
今回はパッケージツールとしてnpmを使用していますがお好きなものに変えてください。
```sh
npm i -D @types-node concurrently nodemon ts-node typescript
```
インストール後、`node-seika`を入れます。
```sh
npm i nusu-github/node-seika
```
ディレクトリに`tsconfig.json`を作成し、次に置き換えます。
```json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "es2020",
    "strict": true,
    "sourceMap": true,
    "outDir": "dist",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/**/*"]
}
```
次に、`package.json`を修正します。
```diff_jsonnet
{
  "name": "nodeseika-example",
  "version": "1.0.0",
  "main": "index.js",
+ "type": "module",
+ "scripts": {
+   "start": "node --experimental-specifier-resolution=node dist/index.js",
+   "dev": "tsc && concurrently \"tsc -w\" \"nodemon --experimental-specifier-resolution=node dist/index.js\"",
+   "build": "tsc"
  },
  "keywords": [],
  "author": "Crysta1221",
  "license": "ISC",
...
}
```
緑色になっている場所と対応する行を探し、置き換えてください。(デフォルトではscriptsの中身がtestのみになっているため、testを削除して上記のものに置き換えるほか、プロジェクトをmoduleとして認識させます)
次に、`src/index.ts`を作成し、次のように置き換えます。
```js
import { Wcfclient } from "node-seika";

const client = new Wcfclient();

// NodeSeikaのバージョン取得
console.log("[INFO] NodeSeika Version:");
const version = await client.Version();
console.log(version);

// VOICEROID製品のスキャン
console.log("[INFO] Scanning All VOICEROID Products...");
await client.ProductScan();

// 検出完了したキャラクター一覧を取得
console.log("[INFO] Listing scanned VOICEROID Products...");
const ver = await client.AvatorList();
console.table(ver);

// 最初のキャラの詳細を取得
const firstEntry = ver.entries().next().value?.[0];
if (firstEntry === undefined) {
  throw new Error("No entries found in ver");
}
const logs = await client.GetDefaultParams2(firstEntry);
console.table(logs);

// しゃべらせる
console.log("[INFO] tts running");
await client.Talk(
  firstEntry,
  "こんにちは。きずなあかりです。のーどじぇいえすから実行しています。",
  { filepath: undefined, emotions: [["喜び", 0.3]] }
);
```
今回は、A.I.VOICEの紲星あかりを使っています。プログラムを実行する前にAssistantSeikaをダウンロードしてインストールして起動していること、使用したいVOICEROIDにチェックをいれておくほか、VOICEROID本体の起動も必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2467860/699eea49-927b-837e-a37d-1157b6e1d713.png)
上記が完了している状態で`npm run dev`を実行すると、製品スキャンを行い、一番最初にリストアップされているキャラクターがしゃべるようになっています。
サンプルでは、ピッチ等も上げていますが、キャラクターによっては`emotions`のテキスト等のパラメータを変更する必要がありそうなので、`client.GetDefaultParams2`で取得したデータと比較しながら書き換えてください。`firstEntry`の部分はCIDが入ります。CIDについては、AssistantSeikaのリファレンスを読むのがいいかと思います。
# 完成版
https://github.com/Crysta1221/nodeseika-example

今回作成したものをリポジトリとしてアップしていますのでお使いください。
# さいごに
今回は、AssistantSeikaのWCFClientを使用して動作させてみましたが、Web APIやSeikaSay2でも行けるようです。ただ、Web APIはAssistantSeika側で設定が必要なことや、SeikaSay2では読み上げに時間がかかったためWCFClientを採用しました。
Node.js側でVOICEROIDを制御できたので次はReact上で動かしてみたいです。