努力したWiki

推敲の足りないメモ書き多数

ユーザ用ツール

サイト用ツール


documents:voiceroid:voiceroid-003

VOICEROID+EX/CeVIOをリモートからしゃべらせる

2018/04/14

  • 新版DLLへ対応。そのためSAPI、CeVIOも利用可能になりました。

2017/09/11

  • 「音街ウナTalk Ex」サポートの為DLL入れ替え。

2017/08/15

  • HTTP経由でVOICEROID+/VOICEROID+EXを操作するやっつけ実装の作成。

概要

HTTPで発声用テキストを待ち受けし、VOICEROID/CeVIOに発声させるサーバプログラム。HTTPを使ったブリッジみたいなものです。
Windows版接続クライアントrechoseika.exeも同梱しています。

ダウンロード

seikaserver20180414c.zip 2018/04/14公開。新版DLLへ対応と色々書き換え。SAPI、CeVIOも利用可能になりました。JS,CSS分離バージョン。

アーカイブには以下が含まれています。

  • seikaServer.exe - サーバプログラム(サービスプログラム)です。
  • rechoseika.exe - Windows用クライアントプログラムです。
  • echoSeikAPI.dll - seikaServer.exeで使用するDLLです。
  • index.html - seikaServer.exe で使うHTML
  • index.css - seikaServer.exe で使うcss
  • index.js - seikaServer.exe で使うJavaScript
  • sample.sh - curlコマンドを使うサンプル

注意 2018/04/14

この版は、過去の物と全く互換性がありません。

注意

このプログラムで公開するサービスは不特定多数に提供するものではありません。個人が利用する範囲内にとどめてください。
VOICEROID/CeVIO製品を持っていない人がこのプログラムで公開するサービスを通じてVOICEROID/CeVIOを使用するのはおそらく問題になるでしょう。
※そのため、Webサーバのように複数リクエストを並列して受付できません。VOIVEROID/CeVIO自体同じ製品を複数インスタンス実行できないし。

また、エラーに関しての考慮が決定的に欠けています。ご注意ください。

対応製品の説明

以下の起動例は、rechoseika.exeでseikaServerが起動しているIPアドレス 192.168.1.100 のホストに接続し、“おはようございます。涼しい朝ですね。”の音声データを取得、再生しています。

VOICEROID 起動方法(起動例)
VOICEROID+ 京町セイカ EX rechoseika.exe -s 192.68.1.100 おはようございます。涼しい朝ですね。
VOICEROID+ 東北ずん子 rechoseika.exe -s 192.68.1.100 -cv ZUNKO おはようございます。涼しい朝ですね。
VOICEROID+ 東北ずん子 EX rechoseika.exe -s 192.68.1.100 -cv ZUNKO_EX おはようございます。涼しい朝ですね。
VOICEROID+ 民安ともえ rechoseika.exe -s 192.68.1.100 -cv TAMMY おはようございます。涼しい朝ですね。
VOICEROID+ 民安ともえ EX rechoseika.exe -s 192.68.1.100 -cv TAMMY_EX おはようございます。涼しい朝ですね。
VOICEROID+ 結月ゆかり rechoseika.exe -s 192.68.1.100 -cv YUKARI おはようございます。涼しい朝ですね。
VOICEROID+ 結月ゆかり EX rechoseika.exe -s 192.68.1.100 -cv YUKARI_EX おはようございます。涼しい朝ですね。
VOICEROID+ 鷹の爪吉田くん rechoseika.exe -s 192.68.1.100 -cv YOSHIDA おはようございます。涼しい朝ですね。
VOICEROID+ 琴葉 茜・葵 rechoseika.exe -s 192.68.1.100 -cv AKANE おはようございます。涼しい朝ですね。
rechoseika.exe -s 192.68.1.100 -cv AOI おはようございます。涼しい朝ですね。
VOICEROID+ 東北きりたん EX rechoseika.exe -s 192.68.1.100 -cv KIRITAN おはようございます。涼しい朝ですね。
音街ウナTalk Ex rechoseika.exe -s 192.68.1.100 -cv UNA おはようございます。涼しい朝ですね。
VOICEROID+ 鷹の爪 吉田くん EX rechoseika.exe -s 192.68.1.100 -cv YOSHIDA_EX おはようございます。涼しい朝ですね。
VOICEROID+ 月読アイ EX rechoseika.exe -s 192.68.1.100 -cv AI_EX おはようございます。涼しい朝ですね。
VOICEROID+ 月読ショウタ EX rechoseika.exe -s 192.68.1.100 -cv SHOUTA_EX おはようございます。涼しい朝ですね。
VOICEROID+ 水奈瀬コウ EX rechoseika.exe -s 192.68.1.100 -cv MINASE おはようございます。涼しい朝ですね。
CeVIO 起動方法(起動例)
CeVIO さとうささら rechoseika.exe -s 192.68.1.100 -cv SASARA おはようございます。涼しい朝ですね。
CeVIO すずきつづみ rechoseika.exe -s 192.68.1.100 -cv TSUZUMI おはようございます。涼しい朝ですね。
CeVIO タカハシ rechoseika.exe -s 192.68.1.100 -cv TAKAHASHI おはようございます。涼しい朝ですね。
CeVIO IA rechoseika.exe -s 192.68.1.100 -cv IA おはようございます。涼しい朝ですね。
CeVIO ONE rechoseika.exe -s 192.68.1.100 -cv ONE おはようございます。涼しい朝ですね。
SAPI 起動方法(起動例)
SAPI話者(最初に見つかった使用可能話者) rechoseika.exe -s 192.68.1.100 -cv SAPI おはようございます。涼しい朝ですね。

プログラムの使い方と説明

先にVOICEROID/CeVIOを起動しておきます。

seikaServerの起動

次に、ダウンロードしたアーカイブにある、

  • echoSeikAPI.dll
  • seikaServer.exe
  • index.html
  • index.css
  • index.js

を同じフォルダに配置ます。例えば E:\seikaserver にコピーします。
次に、ワークディレクトリを作成してください。自動作成はしません。例えば E:\SEIKASERVERWORK を作成します。
終わったら以下のコマンドを実行します。

E:\seikaserver>start seikaserver E:\SEIKASERVERWORK

コマンドプロンプトの画面が出て、“seikaserver listen start”のメッセージが出たら準備完了です。

Webブラウザから使う

Webブラウザをseikaserverに接続してVOICEROID/CeVIOを発声させる方法です。

seikaServer.exe は簡易な操作UIを提供しており、Webブラウザから “http://localhost:7180” にアクセスする事で表示させることができます。 もし seikaserver.exe を実行しているPCと異なるPCでWebブラウザ表示する場合は、“http://localhost:7180” のlocalhost を seikaserver.exe を実行しているPCのIPアドレスに書き換えてください。

WebブラウザにUIを表示させたら、使用製品を選択し、TalkTextに発声させたいテキストを入力して送信ボタンを押します。対応した製品なら、抑揚、高さ、話速、音量、声質、を指定する事もできます。
CeVIOの感情パラメタはサポートしていません。ごめんなさい。

ACTIONを“発声”から“音声取得”に切り替えると、発声せず発声結果の音声ファイルのダウンロードが始まります。
抑揚、高さ、話速、音量、声質、が適用されないようであれば、製品再設定のため、ACTIONを“リセット”に切り替え初期化を実行してみてください。

音声が再生されるのは製品がインストールされているPCです。Webブラウザを実行しているPCではありません。ACTIONを“音声取得”にしてWAVファイルをダウンロードすることができるので、ダウンロード後にメディアプレイヤー等で再生してください。
WebブラウザでUI表示

curlコマンド、perlスクリプトで使う

WebAPIのテストなんかでも使われたりする、curlコマンドでも操作できます。Perlでアクセスする事もできます。

  • seikaserver.exe を実行しているPCのIPアドレスが 192.168.1.200 で、Unixを実行しているサーバのIPアドレスが 192.168.1.20 だった場合の例です。
  • この例の環境では文字コードにEUC-JPを使っているのでその旨をcurlコマンドやPerlスクリプトへ伝えます。
  • 指定するURLのパス最初が“/PLAY”の時は、発声させます。 “/SAVE”の時は、音声ファイルのダウンロードを実行します。

IPアドレスや製品の指定は適宜書き換えしてください。

ACTION URL例
琴葉茜で発声 http://localhost:7180/PLAY/AKANE
琴葉葵の音声取得 http://localhost:7180/SAVE/AOI
京町セイカのリセット http://localhost:7180/RESET/SEIKA

curlコマンドの例

Unixコンソールから

sample.sh を実行すると、seikaserver.exe に接続して製品に発声させられることがわかります。 Webブラウザを使った時と同様、音声ファイルの取得が可能な事もわかります。

Perlスクリプトの例

発声させた音声データを保存するなら以下の様なコードになるかもしれません。

sample1.pl
#!/usr/local/bin/perl
 
use Encode;
use HTTP::Request::Common;
use LWP;
 
my $uri = "http://192.168.1.200:7180/SAVE/SEIKA";
my $params = [ SPEED => 1.00, PITCH=> 1.00, VOLUME => 1.00, INTONATION => 1.00, TALKTEXT => decode('euc-jp',$ARGV[0]) ];
my $ua = LWP::UserAgent->new();
my $res = $ua->request( POST($uri, $params) );
 
  print $res->content; # Wavファイルの中身が入っています

引数に発声させたいテキストを指定します。※手抜きです。必ず指定が要ります。

$ perl sample1.pl "駄目駄目な香りがします" > damedame.wav

“/SAVE”でリクエストを行ったので、レスポンスにはWav音声データが含まれています。必要なら引数で保存先ファイル名を指定し、そこに保存できるようにしてください。

すぐローカルで再生したいならこんなコードになるかもしれません。

sample2.pl
#!/usr/local/bin/perl
 
use Encode;
use HTTP::Request::Common;
use LWP;
use Audio::Wav;
 
my $uri = "http://192.168.1.200:7180/SAVE/SEIKA";
my $params = [ SPEED => 1.00, PITCH=> 1.00, VOLUME => 1.00, INTONATION => 1.00, TALKTEXT => decode('euc-jp',$ARGV[0]) ];
my $ua = LWP::UserAgent->new();
my $res = $ua->request( POST($uri, $params) );
 
  open(SNDDEV, ">/dev/dsp");   # Linux/Unix の環境で異なる
  print SNDDEV $res->content;
  close(SNDDEV);

引数に発声させたいテキストを指定します。※手抜きです。必ず指定が要ります。

$ perl sample2.pl "とっても駄目駄目な気がします"

直接再生デバイスファイルへWavファイルの内容を流し込んでいます。 Wavファイルのヘッダ部分は雑音になるかもしれません。必要ならヘッダ部分を取り除くコードを追加してください。 またWavファイルの形式に対応していないデバイスなら音声的に悲惨な事になるでしょう。適当な再生ツールにリダイレクトさせるのも方法です。

rechoseika.exe を使う

『Windows版クライアントが無いとねぇ』と煽られたので、.NET Framework 4.5.2でコンパイルしたクライアントプログラムを同梱しました。

このクライアントを使えば、ネットワーク経由で別のPCからseikaServer.exe を実行しているPCへ接続し、音声データを転送・再生することができます。
使い方はechoseika.exeとだいたい同じです。

E:\seikaserver>rechoseika -h
usage: echoseika [-s host] [[[option1] option2] ... optionN] TalkText

options:
-s host         : Hostname or IP address of seikaserver. default is localhost
-p place        : place to play. place: remote or local. default is local
-wav wavefile   : save voice to wavefile.wav
-cv voiceroid   : use product name. default is SEIKA
-volume P       : set volume parameter P
-speed P        : set speed parameter P
-pitch P        : set pitch parameter P
-intonation P   : set intonation parameter P
-alpha P        : set alpha parameter P
TalkText        : text to vocalize

E:\seikaserver>

echoseika.exeと異なるオプションの説明は以下になります。

オプション 説明
-s ホスト名 seikaserverを実行しているPCのホスト名もしくはIPアドレスを指定します。未指定の場合は localhost を仮定します。
-p 再生場所 音声の再生場所を指定します。“remote”はseikaserverを実行しているPCでの再生、“local”はrechoseika.exeを実行しているPCでの再生です。未指定の場合はlocalを仮定します。
-wav ファイル名 -p オプションでlocalが指定されている時に有効となります。

以下は seikaserver.exe を実行しているPCのIPアドレスが 192.168.1.200 で、rechoseika.exe を実行したPCのIPアドレスが 192.168.1.208 だった場合の例です。

rechoseika.exeから

技術的な話

  • seikaserver.exe はポート 7180 でHTTPのPOSTメソッドによるリクエストを待ちます。
    HTTPを話せればよいので、Webブラウザや、CURLコマンドで操作ができます。
    HTTPを扱えるライブラリがあればPerlでもRubyでもPythonでもC#でもVBでも利用が可能でしょう。
    発声させる以外に、音声ファイル取得が可能ですので、Linuxでcurlコマンドを使い音声ファイルをダウンロードして再生する事もできます。
  • URLで動作/話者を指定します。旧版はやっつけ実装のためごちゃごちゃになっていましたので、元々の設計であったURLでの指示に統一させました。
  • 音声効果のパラメタ、発声させるテキストはPOSTメソッドのBODYで指定します。“Content-Type: application/x-www-form-urlencoded”でエンコードされている必要があります。
    ※送られてきたデータ、パラメタの解析が面倒なので…
  • Firewallでポート7180番にガードがかかっていたりする場合もあるので注意してください。
メソッド URL(のパス) 説明
GET 全パターン / どの様なパスが指定されてもWebUIのindex.htmlを返す。
POST /PLAY/話者 /PLAY/SEIKA “/PLAY”でサーバ上での再生を指定。“/話者”で実行する製品の話者を指定。
POST /SAVE/話者 /SAVE/TAMMY_EX “/SAVE”で音声データをレスポンスに含める。“/話者”で音声保存する製品の話者を指定。

ソース

seikaserver.exeのソースはこの通りです。毎度のことですが、エラー関係はだいぶ無視しています。

seikaserver.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Threading;
using System.Reflection;
using echoSeikaAPI;
 
namespace seikaServer
{
    class seikaserver
    {
        static AvatorController control = new AvatorController();
        static string seikaServerWork = @".\";
 
        static void Main(string[] args)
        {
 
            if (args.Length != 1)
            {
                Console.WriteLine("usage: seikaserver WorkDir ");
                return;
            }
            else
            {
                if (!Directory.Exists(args[0]))
                {
                    Console.WriteLine("WorkDir {0} not found.",args[0]);
                    return;
                }
                seikaServerWork = args[0] + @"\";
                Console.WriteLine("use {0} directory.",seikaServerWork);
            }
 
            Console.WriteLine("seikaserver Initialized...");
 
            HttpListener listener = new HttpListener();
 
            listener.Prefixes.Add("http://*:7180/");
            listener.Start();
 
            Console.WriteLine("seikaserver listen start");
 
            // Request accept loop
            while (true)
            {
                HttpListenerContext context = listener.GetContext();
                HttpListenerRequest req = context.Request;
                HttpListenerResponse res = context.Response;
 
                try
                {
                    Dictionary<string, object> apiParameters = ApiParser(HttpDataParser(req));
 
                    Console.WriteLine(string.Format("{0}, {1} Request {2} from {3}",
                                                        DateTime.Now,
                                                        req.HttpMethod,
                                                        req.RawUrl.ToString(),
                                                        req.RemoteEndPoint.ToString()));
 
                    switch (req.HttpMethod)
                    {
                        case "POST":
                            PostMethodProcess(req, res, apiParameters);
                            break;
 
                        case "GET":
                            GetMethodProcess(req, res, apiParameters);
                            break;
 
                        default:
                            break;
                    }
                }
                catch (Exception ex)
                {
                    res.StatusCode = 500;
                    byte[] resStr = req.ContentEncoding.GetBytes(ex.Message);
                    res.OutputStream.Write(resStr, 0, resStr.Length);
                    Console.WriteLine("Error: " + ex.Message);
                    Console.WriteLine("Error: " + ex.StackTrace);
                }
 
                res.Close();
            }
        }
 
        private static Dictionary<string, string> HttpDataParser(HttpListenerRequest req)
        {
            char[] sep1 = { '/' };
            char[] sep2 = { '&' };
            char[] sep3 = { '=' };
 
            Dictionary<string, string> ans = new Dictionary<string, string>();
 
            ans.Clear();
 
            string[] pair1 = req.RawUrl.Split(sep1);
 
            if (pair1.Length >= 3)
            {
                ans["DO"] = pair1[1].ToUpper();
                ans["AVATOR"] = pair1[2].ToUpper();
            }
            else
            {
                ans["DO"] = null;
            }
 
            using (StreamReader sr = new StreamReader(req.InputStream, req.ContentEncoding))
            {
                string data = sr.ReadToEnd();
                foreach (string param in data.Split(sep2))
                {
                    string[] pair2 = param.Split(sep3);
                    if (pair2.Length == 2)
                    {
                        ans[Uri.UnescapeDataString(pair2[0]).ToUpper()] = Uri.UnescapeDataString(pair2[1].Replace('+', ' '));
                    }
                }
            }
 
            return ans;
        }
        private static Dictionary<string, object> ApiParser(Dictionary<string, string> parameters)
        {
            Dictionary<string, object> ans = new Dictionary<string, object>();
 
            foreach (KeyValuePair<string, string> pair in parameters)
            {
                switch (pair.Key)
                {
                    case "CV":
                        break;
 
                    case "AVATOR":
                        ans[pair.Key] = pair.Value;
                        try
                        {
                            ans["CV"] = (Avator)Enum.Parse(typeof(Avator), pair.Value);
                        }
                        catch
                        {
                            ans[pair.Key] = null;
                        }
                        break;
 
                    case "DO":
                    case "TALKTEXT":
                        ans[pair.Key] = pair.Value;
                        break;
 
                    default:
                        try
                        {
                            ans[pair.Key] = decimal.Parse(pair.Value);
                        }
                        catch
                        {
                            ans[pair.Key] = pair.Value;
                        }
                        break;
                }
            }
 
            return ans;
        }
 
        private static void GetMethodProcess(HttpListenerRequest req, HttpListenerResponse res, Dictionary<string, object> apiParameters)
        {
            string htmlpath = Directory.GetParent(Assembly.GetExecutingAssembly().Location).ToString() + @"\index.html";
            byte[] resContent;
 
            res.StatusCode = 200;
            res.ContentType = "text/html; charset=utf-8";
            resContent = File.ReadAllBytes(htmlpath);
            res.OutputStream.Write(resContent, 0, resContent.Length);
        }
        private static void PostMethodProcess(HttpListenerRequest req, HttpListenerResponse res, Dictionary<string, object> apiParameters)
        {
            Avator cv;
            string api = apiParameters["DO"] as string;
            string avator = apiParameters["AVATOR"] as string;
 
            byte[] resContent;
 
            if (apiParameters["AVATOR"] == null)
            {
                res.StatusCode = 404;
                resContent = req.ContentEncoding.GetBytes(string.Format("product {0} was not specified.", avator));
                res.OutputStream.Write(resContent, 0, resContent.Length);
                return;
            }
 
            cv = (Avator)apiParameters["CV"];
 
            // サポート外製品だったら失敗
            switch (cv.TTS_Products())
            {
                case TTSProduct.VOICEROID2:
                case TTSProduct.MS_SPF:
                    res.StatusCode = 404;
                    resContent = req.ContentEncoding.GetBytes(string.Format("sorry. {0} was not supported.", cv.ProdName()));
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    return;
 
                default:
                    break;
            }
 
            // TTS製品の登録ができなければ失敗
            if (!control.isRegisted(cv))
            {
                if (!control.Register(cv,false))
                {
                    res.StatusCode = 500;
                    resContent = req.ContentEncoding.GetBytes(string.Format("product {0} was not running on this server.", cv.ProdName()));
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    return;
                }
            }
 
            // 発声テキストが無ければ失敗
            if ((!apiParameters.ContainsKey("TALKTEXT")) || ("" == (string)apiParameters["TALKTEXT"]))
            {
                res.StatusCode = 400;
                resContent = req.ContentEncoding.GetBytes("Talk text was not specified.");
                res.OutputStream.Write(resContent, 0, resContent.Length);
                return;
            }
 
            // 音声再生もしくは音声データ返送
 
            SetVoiceEffectParameters(apiParameters);
 
            switch (api)
            {
                case "PLAY":
                    res.StatusCode = 200;
                    control.PlaySync(cv, (string)apiParameters["TALKTEXT"]);
 
                    resContent = req.ContentEncoding.GetBytes(string.Format("product {0} has talked.", cv));
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    break;
 
                case "SAVE":
                    FileRemover(cv, seikaServerWork);
 
                    res.StatusCode = 200;
                    control.Save(cv, (string)apiParameters["TALKTEXT"], seikaServerWork + cv.ToString());
 
                    if (WaitFileCreation(cv, seikaServerWork))
                    {
                        res.StatusCode = 200;
                        res.ContentType = "audio/wav";
                        res.Headers.Add("Content-Disposition", string.Format("attachment;filename=\"{0}.wav\"", cv));
                        resContent = File.ReadAllBytes(seikaServerWork + cv.ToString() + ".wav");
                    }
                    else
                    {
                        res.StatusCode = 408;
                        res.ContentType = "text/plain";
                        resContent = req.ContentEncoding.GetBytes("Sorry. Request was timed out.");
                    }
 
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    break;
 
                case "RESET":
                    control.Remove(cv);
 
                    if (control.Register(cv))
                    {
                        res.StatusCode = 200;
                        resContent = req.ContentEncoding.GetBytes(string.Format("product {0} has reseted.", cv));
                    }
                    else
                    {
                        res.StatusCode = 500;
                        resContent = req.ContentEncoding.GetBytes(string.Format("product {0} has not reseted.", cv));
                    }
 
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    break;
 
                default:
                    res.StatusCode = 400;
                    resContent = req.ContentEncoding.GetBytes("Unknown request.");
                    res.OutputStream.Write(resContent, 0, resContent.Length);
                    break;
            }
 
            if (apiParameters.ContainsKey("DO")        ) Console.WriteLine(string.Format("  API         : {0}", (string)apiParameters["DO"]));
            if (apiParameters.ContainsKey("AVATOR")    ) Console.WriteLine(string.Format("    avator    : {0}", (string)apiParameters["AVATOR"]));
            if (apiParameters.ContainsKey("CV")        ) Console.WriteLine(string.Format("    product   : {0}", ((Avator)apiParameters["CV"]).ProdName()));
            if (apiParameters.ContainsKey("TALKTEXT")  ) Console.WriteLine(string.Format("    text      : {0}", (string)apiParameters["TALKTEXT"]));
            if (apiParameters.ContainsKey("VOLUME")    ) Console.WriteLine(string.Format("    volume    : {0}", (decimal)apiParameters["VOLUME"]));
            if (apiParameters.ContainsKey("SPEED")     ) Console.WriteLine(string.Format("    speed     : {0}", (decimal)apiParameters["SPEED"]));
            if (apiParameters.ContainsKey("PITCH")     ) Console.WriteLine(string.Format("    pitch     : {0}", (decimal)apiParameters["PITCH"]));
            if (apiParameters.ContainsKey("INTONATION")) Console.WriteLine(string.Format("    intonation: {0}", (decimal)apiParameters["INTONATION"]));
            if (apiParameters.ContainsKey("ALPHA")     ) Console.WriteLine(string.Format("    alpha     : {0}", (decimal)apiParameters["ALPHA"]));
        }
        private static void FileRemover(Avator cv, string WorkDir)
        {
            bool exists1 = File.Exists(WorkDir + cv.ToString() + ".wav");
            bool exists2 = File.Exists(WorkDir + cv.ToString() + ".txt");
 
            if (exists1) File.Delete(WorkDir + cv.ToString() + ".wav");
            if (exists1) File.Delete(WorkDir + cv.ToString() + ".txt");
        }
        private static bool WaitFileCreation(Avator cv, string WorkDir)
        {
            bool ans = false;
            bool exists1 = false;
            bool exists2 = (cv.TTS_Products() == TTSProduct.VOICEROID) ? false : true;
 
            for (int i = 0; i < 60; i++)
            {
                exists1 = File.Exists(WorkDir + cv.ToString() + ".wav");
 
                if (cv.TTS_Products() == TTSProduct.VOICEROID) exists2 = File.Exists(WorkDir + cv.ToString() + ".txt");
 
                if (exists1 & exists2) break;
 
                Thread.Sleep(500);
            }
 
            if (exists1 && exists2) ans = true;
 
            return ans;
        }
        private static void SetVoiceEffectParameters(Dictionary<string, object> apiParameters)
        {
            if (apiParameters["CV"] == null) return;
 
            Avator cv = (Avator)apiParameters["CV"];
 
            if (apiParameters.ContainsKey("INTONATION")) control.SetVoiceEffectParam(cv, VoiceEffectParam.intonation, (decimal)apiParameters["INTONATION"]);
            if (apiParameters.ContainsKey("PITCH")     ) control.SetVoiceEffectParam(cv, VoiceEffectParam.pitch, (decimal)apiParameters["PITCH"]);
            if (apiParameters.ContainsKey("SPEED")     ) control.SetVoiceEffectParam(cv, VoiceEffectParam.speed, (decimal)apiParameters["SPEED"]);
            if (apiParameters.ContainsKey("VOLUME")    ) control.SetVoiceEffectParam(cv, VoiceEffectParam.volume, (decimal)apiParameters["VOLUME"]);
            if (apiParameters.ContainsKey("ALPHA")     ) control.SetVoiceEffectParam(cv, VoiceEffectParam.alpha, (decimal)apiParameters["ALPHA"]);
        }
    }
 
}

コメント

コメントを入力. Wiki文法が有効です:
画像の文字が読めなければ、文字を読んだ.wavファイルをダウンロードして下さい。
 
documents/voiceroid/voiceroid-003.txt · 最終更新: 2018/04/14 23:02 by k896951

ページ用ツール