はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】OpenJTalkのC#ラッパーであるSharpOpenJTalkを利用して音声合成を行う

はじめに

前回OpenJTalkForUnityというUnityで簡単にOpenJTalkを利用できるようにしたパッケージを紹介しました。
www.hanachiru-blog.com

ただこのパッケージはWindowsでしか動作しないという欠点があり、どうしてもクロスプラットフォームで動作させたかったので自作C#ラッパーを作成しようと思っていたのですが、SharpOpenJTalkなるものを発見しました。
github.com

実際にどれくらいのプラットフォームで動作するかは検証する必要がありますが、ひとまず動かすところまでやってみようと思います。

環境

Unity2020.3.5f1
SharpOpenJTalk v1.4.0

VisualStudioのNuGetからインポート&ビルド

NuGetForUnityというUnity用のNuGetクライアントがあるのですが、経験上うまくいったことが少ないのでVisual StudioNuGetから取ってきてUnityに入れます。
github.com


Visual Studioを立ち上げ、TargetPlatform.NET Framework4.72のコンソールアプリケーションを作成します。

// OpenJTalkSample.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <Platforms>AnyCPU;x64;x86</Platforms>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="SharpOpenJTalk" Version="1.4.0" />
  </ItemGroup>

</Project>

あとはNuGetよりSharpOpenJTalkをインポートし、ビルドを行います。

一つ注意点なのですが、x64x86で別々でビルドしないと私の場合はUnity側でエラーを吐いてしまいました。

SharpOpenJTalkをインポート
出力されたdll

以下の3つのdllをUnityのPluginsフォルダに入れます。

  • openjtalk
  • SharpOpenJTalk
  • System.Diagnostics.DiagnosticSource

またx64x86の2種類あると思うので、以下の画像のように配置・設定を行ってください。

Unityにインポートした様子

サンプルを実行してみる

公式サンプルを参考にしながら、StreamingAssets内に以下の2つのデータを配置した場合のサンプルを記述します。(各々の手法で以下の2つのファイルを取得してください)

  • 辞書
  • 音響モデル

SharpOpenJTalk/Program.cs at master · yamachu/SharpOpenJTalk · GitHub

using System;
using System.IO;
using UnityEngine;
using SharpOpenJTalk;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        var instance = new OpenJTalkAPI();

        var dictPath = Path.Combine(Application.streamingAssetsPath, "dic_utf_8");
        var hmmModelPath = Path.Combine(Application.streamingAssetsPath,
            "voice/hts_voice_nitech_jp_atr503_m001-1.05/nitech_jp_atr503_m001.htsvoice");
        if (!instance.Initialize(dictPath, hmmModelPath))
        {
            Debug.LogError("Initialize failed");
            return;
        }

        var labels = instance.GetLabels("これはテストです");
        foreach (var label in labels)
        {
            Debug.Log(label);
        }

        var buffer = instance.SynthesisBuffer("これはテストです");
        if (buffer == null)
        {
            Debug.LogError("Synthesis failed");
            return;
        }
        
        Debug.Log(buffer.Length);
        
        SaveToFile(buffer, "Assets/test.wav");
    }
    
    private static void SaveToFile(short[] buffer, string path)
    {
        var byteWidth = 16 / 8;
        var freq = 48000;
            
        using (var fs = new FileStream(path, FileMode.OpenOrCreate))
        using (var bw = new BinaryWriter(fs))
        {
            bw.Write("RIFF".ToCharArray());
            bw.Write((UInt32)(36 + buffer.Length * byteWidth));
            bw.Write("WAVE".ToCharArray());
            bw.Write("fmt ".ToCharArray());
            bw.Write((UInt32)16);
            bw.Write((UInt16)1);
            bw.Write((UInt16)1);
            bw.Write((UInt32)freq);
            bw.Write((UInt32)(freq * byteWidth));
            bw.Write((UInt16)(byteWidth));
            bw.Write((UInt16)16);
            bw.Write("data".ToCharArray());
            bw.Write((UInt32)(buffer.Length * byteWidth));

            var maxVal = (short)32767;
            var minVal = (short)-32768;

            foreach (var v in buffer)
            {
                if (v > maxVal)
                    bw.Write(maxVal);
                else if (v < minVal)
                    bw.Write(minVal);
                else
                    bw.Write((short)v);
            }
        }
    }
}

無事に実行ができると、Assets内にtest.wavが出力されているはずです。

test.wavが出力されている様子