Google Speech API と XMOS社の VocalFusion スピーカー開発キット、音声合成システム (Open JTalk) を利用して ボイス・チェンジャーを作ってみる

番外編:

(1) XMOS社から発売されている VocalFusion スピーカー開発キット (XVF-3000) と
(2) Google Cloud Platform (GCP) 上で利用できる Speech API、
(3) そして無料で利用できる音声合成システム Open JTalk を利用して、

自分(男性)の声を女性の声に変更する、ボイス・チェンジャーを作ってみましたので、ご紹介します。




VocalFusion スピーカー開発キット (XVF-3000) のビデオは、以下のページからも紹介しています。
https://www.xmos.com/products/voice/vfusion



  1. VocalFusion スピーカー開発キット (XVF-3000) :
    • 4つの Digital MEMS Microphone と、USB-PHY / PDM マイク インターフェース / Voice DSP / スピーカーインターフェース (I2S) を搭載した XVF-3000 ボイス・プロセッサ による、Voice User Interface 評価ボード
  2. Google Cloud Platform (GCP), Speech API
    • Google 社が提供するクラウド・サービス。様々なサービスやAPI が用意されており、素早く開発を行うことができる。
  3. Open JTalk
    • HTS Working Group が開発・提供する Free の Text-to-Speech ソフトウェア。オープンソースライセンスの一つである、Modified BSD ライセンスが設定されている。多謝。


1.全体構成

今回は、MAC OSX を ホストマシンとしています。

VocalFusion スピーカー開発キット (XVF-3000)と MAC OSX を、USB接続します。
また、VocalFusion スピーカー開発キット (XVF-3000)の Audio Out (3.5mm Jack) とスピーカーを接続します。

MAC OSX は、インターネットに接続し、Google Cloud Platform に接続できるようにします。






2. Google Cloud Platform 上の設定


Google Cloud Platform では、「無料トライアル」のサービスを提供しています(2017年6月13日現在)。まずは、この無料トライアルから、始めることをお勧めします。

はじめて使用される方は、アカウント作成含め各種設定が必要になります。はじめるための様々な情報は Web から入手できますので、以下にいくつかURLをご紹介いたします。





  • 第0回 Google Cloud Platformをはじめよう! アカウント登録~画像認識APIを試してみよう



Speech API,  Translate API,  Natural Language API を有効にします。

各APIを有効にする方法は、前述の「第0回 Google Cloud Platformをはじめよう」のページ中ごろに、[1] APIの有効化 の記述がありますので、それを参考にして設定を行います。


次に、認証キーを入手します。

前述の「第0回 Google Cloud Platformをはじめよう」では、「[2] APIキーの発行」の箇所で、APIキーを入手する方法を紹介していますが、ここでは、「サービスアカウントキー」を生成します。


生成の手順は、以下のGoogleのページをご参照ください(まだ日本語のページが無いようです) https://developers.google.com/identity/protocols/application-default-credentials (How the Application Default Credentials workの箇所をご参照ください)


生成した JSON ファイルは、ローカルのファイルとして保存します。インターネットで公開することの無いようにします。他の人からもアクセスできないように管理します。


最後に、環境変数に、Google Cloud Platform 上のProject 名と、保存した JSON のファイル名を設定します。

以下の例は、MAC OSX で、Terminal 上で環境変数を設定した例です。

export GCLOUD_PROJECT=xxx-yyy-zzz
export GOOGLE_APPLICATION_CREDENTIALS="xxxxxxxxx.json"




3.Node のインストール

以下のURLにアクセスして、Node をインストールします。 バージョン v6.* をインストールします(2017年6月13日現在)。
 https://nodejs.org/



4. Homebrew のインストール(MAC OSXのみ)

MAC OSX の Terminal 上で、以下のコマンドを実行します。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"



5. SOX のインストール

SOX は、Sound eXchange という Open Source のソフトウエアです。これをインストールします MAC OSX の Terminal 上で、以下のコマンドを実行します。

brew install sox



6. Open JTalk のインストール

MAC OSX の Terminal 上で、以下のコマンドを実行します。

brew install open-jtalk




7. 実行ファイルの準備

今回の実行プログラムは、Node.js で記載されています。 このプログラムは、Github 上の Example を、参考に(コピーして)作成しています。

Google Could Platform Node.js Examples:
https://github.com/GoogleCloudPlatform/nodejs-docs-samples


(7-1) package.json  (必要となるNode Moduleを記載)
(7-2) voiceChange.js (Node.js プログラム)

実際の記述は、このページの末尾に記載します。




8. 環境設定


前述の 2. の箇所で紹介した環境変数が設定されていることを確認し、前述の package.json ファイルがある directory で、以下のコマンドを実行します。
(MAC OSX の Terminal 上で、以下のコマンドを実行します)

npm  install




9. 実行前の設定(Speech 開始と終了の設定)


VocalFusion スピーカー開発キット (XVF-3000) では、音声を認識する音の閾値を設定することができます。これを、VAD (Voice Activity Detection) と呼んでいますが、この閾値をコントロールすることで、発話完了をより正確に制御することができます。

発話完了が正確に制御できれば、発話開始から音声認識アプリケーションの応答までの時間が素早くなることが期待できます。


以下では、その他の、一般に公開されている情報を使用して、その閾値を変更する方法を紹介します。

前述の「npm install」で、次のファイルがインストールされます。
node_modules/node-record-lpcm16/index.js

このファイルでは、発話開始と終了を、ボイスチェンジャー・ツール側で認識するための閾値(threshold)の値を設定しています。これらは Default で、null に設定されています。

  var defaults = {
    sampleRate: 16000,
    compress: false,
    threshold: 0.5,
    thresholdStart: null,
    thresholdEnd: null,
    silence: '1.0',
    verbose: false,
    recordProgram: 'rec'
  }

前述の、thresholdStart, thresholsEnd の値を、環境に合わせて 0.2 - 0.5 に設定します。

静かな環境であれば、null の設定でも良いですが、多少ノイズのある環境ですと、ツール側が発話完了を認識するのに時間がかかる場合があります。

その場合には、thresholdEnd の値を設定することで、発話終了が確実に認識され、結果的に、ボイスチェンジャー・ツールのレスポンスも早くなります。




10. 実行


MAC OSX の Terminal 上で、以下のコマンドを実行します。
実行後、日本語で発話します(何か喋ります)。

node  voiceChange.js  listen  -l  ja  -t  en


結果として、次のような表示となり、加えて、自分が喋った内容が、女性の声(音声合成)で繰り返されます(再生されます)。以下では、「今日の天気を教えてください」と喋ったケースです。


(ja) 今日の天気を教えてください

(en) Please let me know what's the weather like today.

*** Natural language analysis - parts of speech in Japanese:
NOUN:   今日
PRT:      の
NOUN:   天気
PRT:      を
VERB:    教え
PRT:      て
VERB:    ください


喋った内容が、Speech API により、日本語のテキストに変換されています。
また、そのテキストを、Translate API に入力し、英語の文章に変換しています。
最後に、日本語のテキストを、Natural Language API に入力し、解析しています。

Natural Language API で、日本語の文章を名詞や動詞に分解できますので、その結果を使って、様々な応答を実装することが容易になります。



Google Natural Language API 以外にも、自然言語を解析するフリーのツールがあります。"MeCab" という、Node で使用可能なフリーのツールもあります。"Node Mecab" で検索すると様々なページで、使用方法を紹介しています。



11. VocalFusion スピーカー開発キット (XVF-3000) による、遠くからの音声入力とエコーキャンセル


VocalFusion スピーカー開発キット (XVF-3000)を前述のように接続することで、離れた場所から音声入力が可能となります。

その他、ホスト(この例では、MAC OSX)からVocalFusion スピーカー開発キットを介して音楽を再生した場合には、マイクに入力された音声と音楽のうち、音声だけをホストに送ることが可能です。

このため、音楽再生中でも、クリアな音声をホストに送ることになるため、音声認識率が向上することが期待できます。




12. 実行ファイルの記述



実際の記述を以下に紹介します。
これは、あくまで 「とりあえず、動いた」というレベルの内容であり、様々環境下での動作検証を行なったものではございませんので、何卒、ご了承ください。

(あくまで、Node.js も知らない人が、Google Cloud Platform の Example をコピーして動かしてみたレベルのものとご理解ください。公開するのも恥ずかしいですが、ご参考まで。フィードバック歓迎です。)


(12-1) package.json  (必要となるNode Moduleを記載)

--------------------------------------------------------------------------
{
  "name": "nodejs-docs-samples-speech",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "scripts": {
    "test": "cd ..; npm run st -- --verbose speech/system-test/*.test.js"
  },
  "dependencies": {<
    "@google-cloud/translate": "0.8.0",
    "@google-cloud/speech": "0.9.0",
    "@google-cloud/language": "0.10.2",
    "@google-cloud/storage": "1.0.0",
    "node-record-lpcm16": "0.3.0",
    "yargs": "7.0.2"
  },
  "engines": {
    "node": ">=4.3.2"
  }

} --------------------------------------------------------------------------



(12-2) voiceChange.js (Node.js プログラム)

--------------------------------------------------------------------------
/**
 * Copyright 2016, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * This application demonstrates how to perform basic recognize operations with
 * with the Google Cloud Speech API.
 *
 * For more information, see the README.md under /speech and the documentation
 * at https://cloud.google.com/speech/docs.
 */

'use strict';

var TMPTEXTFILE     = "tmp000_mei.txt";
var TMPWAVFILE      = "tmp000_mei.wav";
var OPENJTALKDIR    = "/usr/local/Cellar/open-jtalk/1.10_1";
var OPENJTALKEXEC   = "/usr/local/bin/open_jtalk";
var OPENJTALKDIC    = OPENJTALKDIR + "/dic";
var OPENJTALKVOICE  = OPENJTALKDIR + "/voice/mei/mei_happy.htsvoice";
var OPENJTALKCMD    = OPENJTALKEXEC + " -x " + OPENJTALKDIC + " -m " + OPENJTALKVOICE + " -ow " + TMPWAVFILE + " -r 0.9 " + TMPTEXTFILE ;
var AFPLAYCMD       = "afplay " + TMPWAVFILE + "; rm -f " + TMPTEXTFILE + " " + TMPWAVFILE;

///////////////////////////////////////////////////
function execOpenJTalk (text) {

  const execSync = require('child_process').execSync;
  var fs = require('fs');

  fs.writeFileSync(TMPTEXTFILE, text);

  const result1 = execSync(OPENJTALKCMD);

  const result2 = execSync(AFPLAYCMD);


}


/////////////////////////////////////////////////////////////
function analyzeSyntaxOfText (text) {
  // [START language_syntax_string]
  // Imports the Google Cloud client library
  const Language = require('@google-cloud/language');

  // Instantiates a client
  const language = Language();

  // Instantiates a Document, representing the provided text
  const document = language.document({ content: text });


  // Detects syntax in the document
  document.detectSyntax()
    .then((results) => {
      const syntax = results[0];

      console.log('\n*** Natural language analysis - parts of speech in Japanese:');
      syntax.forEach((part) => {
        console.log(`${part.partOfSpeech.tag}:\t ${part.text.content}`);
      });
      console.log('\n');
    })
    .catch((err) => {
      console.error('ERROR:', err);
    });
}


///////////////////////////////////////////////////////////////////
function translateText (text, langOrg, target) {
  // [START translate_translate_text]
  // Imports the Google Cloud client library
  const Translate = require('@google-cloud/translate');

  // Instantiates a client
  const translate = Translate();


  translate.translate(text, target)
    .then((results) => {
      let translations = results[0];
      translations = Array.isArray(translations) ? translations : [translations];

      translations.forEach((translation, i) => {

        console.log(`(${target}) ${translation}`);

        if (target == "ja") {
                execOpenJTalk(translation);
        }

        //Analysis
        if (langOrg == "ja") {
                analyzeSyntaxOfText(text);
        }
        if (target == "ja") {
                analyzeSyntaxOfText(translation);
        }
      });

    })
    .catch((err) => {
      console.error('ERROR:', err);
    });
}


///////////////////////////////////////////////////////////////////////
function myTextToSpeech (text, languageCode, languageCodeTo) {

  //printout result of speech-to-text
  process.stdout.write("\n(" + languageCode +") " + text + "\n\n");


  //translate
  translateText(text, languageCode, languageCodeTo);


  if (languageCode == "ja") {
        execOpenJTalk(text);
  }

}



//////////////////////////////////////////////////////////

function streamingMicRecognize (encoding, sampleRateHertz, languageCode, languageCodeTo) {
  // [START speech_streaming_mic_recognize]
  const record = require('node-record-lpcm16');

  // Imports the Google Cloud client library
  const Speech = require('@google-cloud/speech');

  // Instantiates a client
  const speech = Speech();

  const request = {
    config: {
      encoding: encoding,
      sampleRateHertz: sampleRateHertz,
      languageCode: languageCode
    }
  };

  // Create a recognize stream
  const recognizeStream = speech.createRecognizeStream(request)
    .on('error', console.error)
    .on('data', (data) => myTextToSpeech(data.results, languageCode, languageCodeTo));

  // Start recording and send the microphone input to the Speech API
  record.start({
    sampleRateHertz: sampleRateHertz,
    threshold: 0
  }).pipe(recognizeStream);

  console.log('Listening, press Ctrl+C to stop.');
}



require(`yargs`)
  .demand(1)
  .command(
    `listen`,
    `Detects speech in a microphone input stream. This command requires that you have SoX installed and available in your $PATH. See https://www.npmjs.com/package/node-record-lpcm16#dependencies`,
    {},
    (opts) => streamingMicRecognize(opts.encoding, opts.sampleRateHertz, opts.languageCode, opts.languageCodeTo)
  )
  .options({
    encoding: {
      alias: 'e',
      default: 'LINEAR16',
      global: true,
      requiresArg: true,
      type: 'string'
    },
    sampleRateHertz: {
      alias: 'r',
      default: 16000,
      global: true,
      requiresArg: true,
      type: 'number'
    },
    languageCode: {
      alias: 'l',
      default: 'en-US',
      global: true,
      requiresArg: true,
      type: 'string'
    },
    languageCodeTo: {
      alias: 't',
      default: 'en-US',
      global: true,
      requiresArg: true,
      type: 'string'
    }
  })
  .example(`node $0 listen`)
  .wrap(120)
  .recommendCommands()
  .epilogue(`For more information, see https://cloud.google.com/speech/docs`)
  .help()
  .strict()

  .argv;

--------------------------------------------------------------------------


(その他の投稿については、右上の「ページ」をご確認ください)

0 件のコメント:

コメントを投稿