HOME > Various Tips > 音声合成の活用 > リモート音声時計


リモート音声時計  (2019/02/18 new)

リモート音声時計の概要

このTipsでは、Python、Ruby、 .Net言語でHaruzira SDKを使用したリモート音声時計を紹介します。
リモート音声時計は、指定したインターバル(interval)で現在時刻を送信できるアプリです。
送信された現在時刻は、Haruziraの音声合成で再生されます。
また、音声時計の開始や停止、設定値の変更は、音声認識等を利用してHaruziraからリモートで操作できます。

各言語で作成するプログラムの共通機能概要は、次のようになります。

  • 音声認識またはダイレクトコマンド送信による、リモートからの音声時計通知の開始・停止機能
  • 音声認識またはダイレクトコマンド送信による、リモートからの音声時計プロセスの終了機能
  • 音声認識またはテキスト入力による、リモートからの時刻通知インターバル値の設定機能
  • 音声認識またはテキスト入力による、リモートからの音声合成エンジンの性別設定機能


デモ(demo)動画では、仮想PC上に.Netで作成したSample Programを動作させ、同一PC上のHaruziraと通信させています。
実行ファイルのみでもダウンロードできるので、プログラムに興味がないユーザーでも試してみて下さい。

また、GUIを利用しているので、動作している様子もわかりやすいと思います。












動画サイズ: 約35.8MB





フリーテキスト送信機能

Haruzira version 3.2.0から、フリーテキスト(free text)によるコマンド送信機能を搭載しています。
この機能は、音声認識やキーボードなどを利用し、入力された任意のテキストをコマンド(command)として送信できる機能です。

従来の音声認識によるコマンド送信機能では、予めコマンドを登録しておく必要がありましたが、登録不要で任意のテキストコマンドを送信できるようになりました。
もちろん、兼用することも可能です。

基本的には、頻繁に利用するコマンドの場合は従来のコマンド送信機能を利用し、設定値や検索など条件が一定でないコマンドの場合に、フリーテキスト機能を利用することを想定しています。
Sample Programでは、双方の機能を利用しているので参考にして下さい。

各機能のメリットとデメリットは、次のようになります。

登録コマンド送信

    メリット
  • 登録されたコマンド以外は認識されない(誤送信がない)
  • インターネット環境を必要としない(プライバシーが漏れにくい)
  • 音声認識以外にコマンドを選択しダイレクトに送信できる(マイク不要)
  • コマンド受信時のプログラムが容易(登録済みコマンドのみ送信される)
    デメリット
  • コマンド数が多くなると登録するのが面倒
  • 検索や設定値の変更などをコマンドにしたい場合に実現が難しい(条件が一定でないため)

フリーテキストでのコマンド送信

    メリット
  • コマンドの登録が不要
  • 検索や設定値の変更などをコマンドにできる(任意のテキストを送信)
  • 音声認識以外にキーボードなどで入力したテキストをコマンドとして送信できる(マイク不要)
  • 登録コマンドも送信できる
    デメリット
  • コマンド受信時のプログラムが複雑になる(送信されたテキストをコマンドに変換する解析が必要)
  • 音声認識時は、マイクロソフトのクラウドサービスに接続する必要があるためインターネット環境が必要(MSにプライバシーが漏れる)

双方の機能を兼用することで、それぞれのデメリットをカバーできるようになるので、上手に使い分けて下さい。

Sample Programのフリーテキスト解析処理では、単純に正規表現を利用しているだけですが、機械学習や自然言語処理を組み合わせて、よりインテリジェント(intelligent)なシステムへの改造に挑戦してみて下さい。
自然言語処理や機械学習に興味があるまたは研究をしているユーザーであれば、Haruziraをフロントエンドツール(front-end tool)として利用できるのではないかと思います。








実行前の事前準備

Sample Programを実行する前に、各プログラム言語共通で必要となる事前準備について説明します。

音声再生を行う言語追加などの基本的な設定に関しては、前回記事のSample Programの概要を参考にして下さい。
ここでは、コマンドと送信先の登録について説明します。

以下、次の環境で動作している場合の例です。

Server UWPアプリHaruziraを動作させる。
IP Address: 192.168.1.10
受信Port: 46000
Windows 10(Anniversary Update以降)が動作するDeviceまたは仮想PC
Client 音声時計プログラムを動作させる。
IP Address: 192.168.1.7
コマンド受信Port: 46200
コマンド以外の受信Port: 46210
.Net、PythonまたはRubyプログラムが動作できれば、OSやデバイスは問わない。


接続先の登録(音声時計が稼働するデバイス)

1.Haruziraのリモートデバイス管理画面でアイテムの追加を選択


2.デバイス名、IPアドレス、ポート番号の入力
音声時計が稼働するデバイスの情報を登録する。
デバイス名は、自分で認識できれば好きな名称で構わない。
IPアドレスは、DNS解決可能であればホスト名でも構わない。
ポート番号は、規定値で問題なければ「46200」と入力する。



音声コマンドの登録

1.Haruziraのリモート音声コマンド管理画面でデータの復元を選択する
今回は、Sample Program用に用意したファイルを読み込むだけで問題ない。


2.VoiceClockCommands.jsonファイルを読み込む
次のように3つのコマンドが追加される。
認識語句は、音声認識で自分が認識させたい語句に変更しても構わない。
また、複数の認識語句を追加したい場合は、「;」で区切る。

VoiceClockCommands_en_us.jsonファイルを読み込みたい場合は、OSの言語設定が英語になっている必要があります。
(リモート機能の音声認識言語はOSの設定を利用しているため。次期バージョンで多言語対応予定)
日本語設定の状態で英語モードによるフリーテキストコマンドを送信する場合は、キーボードなどで直接入力して下さい。


3.登録結果の確認
接続先と音声コマンドの登録が完了すると、リモート読み上げ画面で次のように確認できます。













Python Sample Program

haruzira_sdkが未インストールの場合は、前回までの記事を参考にしてインストールして下さい。
また、基本的なSDKの利用方法(Haruziraへのメッセージの送信方法など)についても前回までの記事を参考にして下さい。
ここでは、音声コマンドの受信と処理方法を中心に説明します。

1.ソースコードの作成
voice_clock.py

# coding: utf-8
import time, threading, datetime
import sys, signal, re
from haruzirasdk import hz_client_tcp_communication as hzcomm

#region VoiceClock is a class for sending current time and receiving voice commands.
class VoiceClock(object):

    # <summary>
    # Construct
    # </summary>
    # <param name="ip">ip address of the distination</param>
    # <param name="port">port number of the distination</param>
    def __init__(self, ip, port):
        self.__alarm_interval = 10
        self.__flg_stop = False
        self.__flg_term = False
        self.__th_worker = None
        self.__stop_evt = threading.Event()

        #for sending current time messages.
        self.__clt_tcp_comm = hzcomm.ClientTcpCommunication()
        self.__clt_tcp_comm.ServerIP = ip
        self.__clt_tcp_comm.ServerPortNo = port
        self.__clt_tcp_comm.ReceivePort = 46210
        self.__clt_tcp_comm.ReqSendDataCompletionNoticeNecessity = hzcomm.HzSpCompletionNoticeNecessity.NoNeed.value
        self.__clt_tcp_comm.ReqSendDataSpeechRepeat = 1
        self.__clt_tcp_comm.startAsynchronousListener()
        self.__clt_tcp_comm.setTraceOutPut(False)

        #for receiving voice commands.
        self.__cmd_rcv_com = hzcomm.ClientTcpCommunication()
        self.__cmd_rcv_com.ReceivePort = 46200
        self.__cmd_rcv_com.ReqSendDataCompletionNoticeNecessity = hzcomm.HzSpCompletionNoticeNecessity.NoNeed.value
        self.__cmd_rcv_com.startAsynchronousListener()
        self.__cmd_rcv_com.setTraceOutPut(False)

        #regist callback functions
        self.__cmd_rcv_com.evNotifySendSpeechRecognitionCommand = self.notify_send_speech_recognition_command
        self.__clt_tcp_comm.evNotifyMessageEvent =  self.notify_communicaton_message
        self.__clt_tcp_comm.evNotifyReceivedDisconnectEvent = self.notify_received_disconnect

    #region define accessor
    def get_flg_term(self):
        return self.__flg_term

    def set_flg_term(self, value):
        self.__flg_term = value

    def get_th_worker(self):
        return self.__th_worker

    def set_th_worker(self, value):
        self.__th_worker = value

    flg_term = property(get_flg_term, set_flg_term)
    th_worker = property(get_th_worker, set_th_worker)
    #endregion

    #region call back functions
    ###############################################################
    # call back functions for haruzirasdk.
    # called when occurred events on a network communication.
    ###############################################################
    # notify when 'Send Speech Recognition Command' message received.
    # <param name="cmd_info">command infomation</param>
    def notify_send_speech_recognition_command(self, cmd_info):
        print("Received, Send Speech Recognition Command;\n  Ip[{0:s}], Port[{1:d}], Mode[{2:d}], Command[{3:s}], Timestamp[{4:s}]".format(cmd_info.ip_addr, cmd_info.port, cmd_info.mode, cmd_info.command, cmd_info.timestamp))

        announce = ""
        #check received command
        if (cmd_info.command.lower() == "start voice clock"):
            self.__flg_stop = False
            announce = "音声時計を開始します。"
            if(self.__stop_evt.is_set() != True):
                self.__stop_evt.set()
        elif (cmd_info.command.lower() == "stop voice clock"):
            self.__flg_stop = True
            announce = "音声時計を停止しました。"
        elif (cmd_info.command.lower() == "terminate voice clock process"):
            self.__flg_term = True
            announce = "音声時計プロセスを終了しました。"
            if(self.__stop_evt.is_set() != True):
                self.__stop_evt.set()
        else:
            if (re.match(r"^(?=.*インターバル)(?=.*[\d]+)(?=.*(変更|設定))", cmd_info.command)):
                vals = re.match(r".*?(\d+).*", cmd_info.command)
                interval = int(vals.group(1))
                #if (interval >= 1 and interval <= 60):
                if (1 <= interval <= 60):
                    self.__alarm_interval = interval
                    announce = "送信インターバルを{0:d}分に設定しました。".format(self.__alarm_interval)
                else:
                    announce = "送信インターバルは、1分~60分の範囲で指定して下さい。[指定された値:{0:d}分]".format(interval)
            elif (re.match(r"^(?=.*(男性|女性))(?=.*(変更|設定))", cmd_info.command)):
                vals = re.match(r".*?(男性|女性).*", cmd_info.command)
                if(vals.group(1) == "女性"):
                    self.__clt_tcp_comm.ReqSendDataSpeechGender = hzcomm.HzSpeechGender.Female.value
                    self.__cmd_rcv_com.ReqSendDataSpeechGender = hzcomm.HzSpeechGender.Female.value
                else:
                    self.__clt_tcp_comm.ReqSendDataSpeechGender = hzcomm.HzSpeechGender.Male.value
                    self.__cmd_rcv_com.ReqSendDataSpeechGender = hzcomm.HzSpeechGender.Male.value
                announce = "{0:s}に設定しました。".format(vals.group(1))
            else:
                announce = "該当するコマンドが有りませんでした。"

        print("announce [{0:s}]".format(announce))

        try:
            #send speech data.
            self.send_responce(cmd_info, announce)
        except Exception as ex:
            print(ex)

    # notify when occurred issue that disconnected from access point or on receiving threads.
    # <param name="msg">message</param>
    # <param name="st">status</param>
    def notify_received_disconnect(self, msg, st):
        print("{0:s}, Status[0x{1:02x}]".format(msg, st))
        #self.__flg_stop = True

    # notify when occurred issue for some reasons(except disconnect).
    # <param name="msg">message</param>
    # <param name="msg_id">message id</param>
    # <param name="err_code">error code</param>
    def notify_communicaton_message(self, msg, msg_id, err_code):
        print("Message ID[0x{0:02x}], Error Code[0x{1:02x}], {2:s}".format(msg_id, err_code, msg))
        #self.__flg_stop = True

    #endregion define call back function 

    #cancel listeners for receiving asynchronous message.
    def cancel_asynchronous_listener(self):
        self.__cmd_rcv_com.cancelAsynchronousListener()
        self.__clt_tcp_comm.cancelAsynchronousListener()

    #send a message of the current time.
    def send_current_time(self, msg):
        self.__clt_tcp_comm.ReqSendDataText = msg
        return self.__clt_tcp_comm.sendSpeechDataEx()

    #send a responce message when recieved 'Send Speech Recognition Command'.
    def send_responce(self, cmd_info, msg):
        self.__cmd_rcv_com.ServerPortNo = cmd_info.port
        self.__cmd_rcv_com.ServerIP = cmd_info.ip_addr
        self.__cmd_rcv_com.ReqSendDataText = msg
        self.__cmd_rcv_com.sendSpeechDataEx()

    # thread worker to send voice clock messsage.
    def worker_clock(self):
        self.__stop_evt.wait()
        print("start worker thread.")
        while True:
            try:
                if(self.__flg_term):
                    break
                self.__stop_evt.clear()
                nowtime = datetime.datetime.now().time()

                if(self.__flg_stop != True and nowtime.second == 0 and (nowtime.minute % self.__alarm_interval == 0)):
                    print("alarm time.[{0}]".format(nowtime))
                    msg = "{0:02d}:{1:02d}です。".format(nowtime.hour, nowtime.minute)
                    time_stamp = self.send_current_time(msg)
                    if(time_stamp is not None):
                        print("success. timestamp[{0:s}]".format(time_stamp))
                    else:
                        print("failed to send.")
            
                time.sleep(1)

                if(self.__flg_stop):
                    print("stop thread.")
                    self.__stop_evt.wait()
                    self.__flg_stop = False
                    print("restart thread.")
            
            except Exception as ex:
                print("exception: {0:s}".format(ex))
                break

#endregion

#========== main routine ===================
#create instance. (Haruzira's ip and port)
voice_clock = VoiceClock("192.168.1.7", 46000)

#signal handler
def exit_signal_recieved(signum, frame):
    global voice_clock
    if(signum == signal.SIGINT):
        print("received interrupt signal. (ctrl+c)")
    elif(signum == signal.SIGTERM):
        print("received termination signal. (kill -15)")
    voice_clock.flg_term = True
    time.sleep(2)
    #close receiving listener.
    voice_clock.cancel_asynchronous_listener()
    exit()

for sig in (signal.SIGINT, signal.SIGTERM):
    signal.signal(sig, exit_signal_recieved)



#start working thread.
voice_clock.th_worker = threading.Thread(target=voice_clock.worker_clock)
voice_clock.th_worker.start()

#waiting for working thread until it complete.
voice_clock.th_worker.join()

#close receiving listener.
voice_clock.cancel_asynchronous_listener()

                                    
  • 7-179行目:VoiceClockクラスの定義。現在時刻の取得や送信及びコマンドの受信等に関する処理をまとめている。
  • 15行目:現在時刻を通知するインターバルを設定する。
  • 16-19行目:現在時刻の送信を行うスレッドの停止や終了を制御するフラグ。
  • 22-29行目:現在時刻の送信を行う通信クラスのインスタンス生成及び宛先情報などの設定。
  • 32-36行目:音声コマンドの受信を行う通信クラスのインスタンス生成及び受信元情報などの設定。
    (サンプルでは、現在時刻の送信インスタンスと分けて生成しているが同じインスタンスにしても問題ない)
  • 39-41行目:Callback関数の登録。音声コマンドの受信用のCallback関数は、音声コマンド用の通信クラスインスタンスに登録している。
  • 44-57行目:VoiceClockクラスが提供するaccessor(property)に関する定義。
  • 67-113行目:音声コマンド受信時のCallback関数の定義。
  • 72-76行目:音声時計の通知開始処理
    コマンド受信時に現在時刻送信スレッドを開始する。
  • 77-79行目:音声時計の通知停止処理
    コマンド受信時に現在時刻送信スレッドを停止(待機状態)する。
  • 80-84行目:音声時計プロセスの終了処理
    コマンド受信時にvoice_clock.pyプロセスを終了する。
  • 85-105行目:フリーテキストで送信されたコマンドの処理
    解析結果、該当するコマンドが無い場合は、エラーメッセージを通知する。(105行目)
  • 86-94行目:送信インターバルの設定コマンドの解析処理
    正規表現で3つのkeywords(インタバール、数字、設定or変更)が含まれている場合にのみ処理対象としている。
    数字が複数含まれていた場合は、先頭の数字を設定値としている。
    例)受信したテキスト:"送信インターバルを10分または20分に変更" -> 10分を設定値とする
  • 95-103行目:音声合成エンジンの性別設定コマンドの解析処理
    正規表現で2つのkeywords(男性or女性、設定or変更)が含まれている場合にのみ処理対象としている。
    性別となる語句が複数含まれていた場合は、先頭の語句を設定値としている。
    例)受信したテキスト:"女性または男性にに設定" -> 女性を設定値とする
  • 109行目:コマンド受信結果の応答を送信する。
  • 118-120行目:Haruziraから「Communication Stop Notice(0x11)」メッセージの受信時に通知されるCallback関数の定義。
    Error Codeに関しては、Pythonで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 126-128行目:通信クラスでエラー(Haruzira側からの切断以外)が発生した場合に通知されるCallback関数の定義。
    Error Codeに関しては、Pythonで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 133-135行目:通信クラスのリスナーを停止し破棄するためのMethod定義。
  • 138-140行目:現在時刻の送信を行うためのMethod定義。
  • 143-147行目:音声コマンド受信時に、処理結果に関する応答を行うためのMethod定義。
  • 150-179行目:現在時刻のメッセージ通知に関する処理を行うスレッド定義。
  • 151行目:音声時計の通知開始コマンドを受信するまで待機する。
  • 155-156行目:音声時計プロセスの終了コマンド受信時に、スレッドを終了する。
  • 160-167行目:現在時刻を通知する処理。
    分単位に送信インターバル値に一致しているかチェックする。
  • 171-175行目:音声時計の通知停止コマンドを受信した場合は、スレッドを待機させる。
  • 185行目:VoiceClockクラスのインスタンス生成。(Haruziraが稼働するIP addressとPortを指定する)。
  • 188-201行目:OSからのシグナル受信時の処理定義。
    ctrl+c、kill -15を実行時のシグナル受信時に、後処理をしプロセスを終了させる。
  • 206-210行目:現在時刻の通知処理スレッドを起動し、終了するまで待機する。
  • 213行目:プログラム終了時にリスナースレッドを停止する。(Pythonの場合は、必須処理)

2.プログラムの実行手順

①185行目のIP addressとPort番号を、環境に合わせて編集して下さい。(Port番号は任意)

②15行目の通知インターバル既定値を、好みに合わせて1分~60分の範囲内で編集して下さい。(任意)

③25行目(通常受信)と33行目(音声コマンドの受信)のPort番号を、環境に合わせて編集して下さい。(任意)

④Sample Programの実行
Daemonプロセス(Backgroundプロセス)として起動します。(Debug時などはForegroundでも構わない)
なお、Logを出力しない場合は、リダイレクト(redirect)を省略して下さい。
また、ファイアーウォールの許可を求められた場合は、許可して下さい。

nohup python voice_clock.py > output.log &

                                        

⑤Haruziraから、音声認識、ダイレクト送信、フリーテキストなどを利用して音声時計の通知を開始させます。

⑥Haruziraから、フリーテキストで送信インターバルの変更をさせてみましょう。
インターバル、設定値、変更or設定のキーワードが含まれていれば語順や組み合わせは自由です。
例)「インターバルを1分に変更」、「設定インターバル1分」
※音声認識で数値を認識させたい場合のコツ。 1分 -> 「イチフン」、10分 -> 「ジップン」

⑦Haruziraから、フリーテキストで音声合成エンジンの性別を変更してみましょう。
男性or女性、変更or設定のキーワードが含まれていれば、語順や組み合わせは自由です。
例)「男性に変更」、「性別設定を男性にする」










Ruby Sample Program

haruzira_sdkが未インストールの場合は、前回までの記事を参考にしてインストールして下さい。

1.ソースコードの作成
voice_clock.rb

#!/usr/bin/ruby
# -*- coding: utf-8 -*-
require "timeout"
require "date"
require "time"
require "haruzira_sdk"

# if it's necessary for you start as a daemon process.
#Process.daemon

#region VoiceClock is a class for sending current time and receiving voice commands.
class VoiceClock

    def initialize(ip, port)
        @alarm_interval = 10
        @flg_stop = false
        @flg_term = false
        @th_worker = nil

        #for sending clock message.
        @clt_tcp_comm = ClientTcpCommunication.new
        @clt_tcp_comm.ServerIP = ip
        @clt_tcp_comm.ServerPortNo = port
        @clt_tcp_comm.ReceivePort = 46210
        @clt_tcp_comm.ReqSendDataCompletionNoticeNecessity = HzSpCompletionNoticeNecessity::NoNeed
        @clt_tcp_comm.ReqSendDataSpeechRepeat = 1
        @clt_tcp_comm.startAsynchronousListener()
        @clt_tcp_comm.setTraceOutPut(false)

        #for receiving voice commands.
        @cmd_rcv_com = ClientTcpCommunication.new
        @cmd_rcv_com.ReceivePort = 46200
        @cmd_rcv_com.ReqSendDataCompletionNoticeNecessity = HzSpCompletionNoticeNecessity::NoNeed
        @cmd_rcv_com.startAsynchronousListener()

        ###############################################################
        # call back functions for haruzirasdk. called when occurred events on a network communication.
        ###############################################################
        notify_send_speech_recognition_command = lambda{|cmdInfo|
            puts("Ip[%s], Port[%d], Command[%s], Timestamp[%s]" % [cmdInfo.IpAddr, cmdInfo.Port, cmdInfo.Command, cmdInfo.Timestamp])
        
            #check received command
            announce = ""
            if (cmdInfo.Command.casecmp("start voice clock") == 0)
                @flg_stop = false
                announce = "音声時計を開始します。"
                if(@th_worker.stop?)
                    @th_worker.run
                end
            elsif (cmdInfo.Command.casecmp("stop voice clock") == 0)
                @flg_stop = true
                announce = "音声時計を停止しました。"
            elsif (cmdInfo.Command.casecmp("terminate voice clock process") == 0)
                @flg_term = true
                announce = "音声時計プロセスを終了しました。"
            else
                if (/^(?=.*インターバル)(?=.*[\d]+)(?=.*(変更|設定))/ =~ cmdInfo.Command)
                    vals = cmdInfo.Command.scan(/[\d]+/)
                    interval = vals[0].tr("0-9", "0-9").to_i
                    if(interval >= 1 && interval <= 60)
                        @alarm_interval = interval
                        announce = "送信インターバルを%d分に設定しました。" % [@alarm_interval]
                    else
                        announce = "送信インターバルは、1分~60分の範囲で指定して下さい。[指定された値:%d分]" % [interval]
                    end
                elsif (/^(?=.*(男性|女性))(?=.*(変更|設定))/ =~ cmdInfo.Command)
                    vals = cmdInfo.Command.scan(/男性|女性/)
                    if (vals[0] == '女性')
                        @clt_tcp_comm.ReqSendDataSpeechGender = HzSpeechGender::Female
                        @cmd_rcv_com.ReqSendDataSpeechGender = HzSpeechGender::Female
                    else
                        @clt_tcp_comm.ReqSendDataSpeechGender = HzSpeechGender::Male
                        @cmd_rcv_com.ReqSendDataSpeechGender = HzSpeechGender::Male
                    end
                    announce = "%sに設定しました。" % vals[0]
                else
                    announce = "該当するコマンドが有りませんでした。"
                end
            end

            puts announce

            begin
                #send speech data.
                @cmd_rcv_com.ServerPortNo = cmdInfo.Port
                @cmd_rcv_com.ServerIP = cmdInfo.IpAddr
                @cmd_rcv_com.ReqSendDataText = announce
                @cmd_rcv_com.sendSpeechDataEx
                if(@flg_term)
                    Thread.kill(@th_worker)
                end
            rescue Exception => ex
                puts(ex)
            ensure
            end

        }

        # notify when occurred issue that disconnected from access point or on receiving threads.
        # <param name="msg">message</param>
        # <param name="st">status</param>
        notify_received_disconnect = lambda{|msg, st|
            puts("{%s}, Status[0x%02x]" % [msg, st])
            #@flg_stop = true
        }

        # notify when occurred issue for some reasons(except disconnect).
        # <param name="msg">message</param>
        # <param name="msg_id">message id</param>
        # <param name="err_code">error code</param>
        notify_communicaton_message = lambda{|msg, msg_id, err_code|
            puts("Message ID[0x%02x], Error Code[0x%02x], %s" % [msg_id, err_code, msg])
            #@flg_stop = true
        }
        #end define call back function 

        @cmd_rcv_com.EvNotifySendSpeechRecognitionCommand = notify_send_speech_recognition_command
        @cmd_rcv_com.EvNotifyMessageEvent = notify_communicaton_message
        @cmd_rcv_com.EvNotifyReceivedDisConnectEvent = notify_received_disconnect
        @clt_tcp_comm.EvNotifyMessageEvent = notify_communicaton_message
        @clt_tcp_comm.EvNotifyReceivedDisConnectEvent = notify_received_disconnect
    end

    attr_accessor   :flg_stop, :th_worker

    # thread worker to send voice clock messsage.
    def worker_clock
        Thread.abort_on_exception = true
        @th_worker = Thread.start{
            Thread.stop
            loop do
                begin
                    nowtime = Time.new
    
                    if(!@flg_stop && nowtime.sec == 0 && (nowtime.min % @alarm_interval == 0))
                        puts("alarm time.[%s]" % [nowtime.to_time])
                        @clt_tcp_comm.ReqSendDataText = "%02d:%02dです。" % [nowtime.hour, nowtime.min]
                        timeStamp = @clt_tcp_comm.sendSpeechDataEx
                        if(timeStamp != nil)
                            puts("success. timestamp[%s]" % [timeStamp])
                        else
                            puts("failed to send.")
                        end
                    end
                
                    sleep(1)
    
                    if(@flg_stop)
                        Thread.stop
                        @flg_stop = false
                    end
                
                rescue Exception => ex
                    puts(ex)
                    break
                ensure
                end
            end
        }
    end

end
#endregion

#========== main routine ===================
#create instance. (Haruzira's ip and port)
voice_clock = VoiceClock.new("192.168.1.7", 46000)

#start working thread.
voice_clock.worker_clock()

def exit_signal_recieved(vc)
    Thread.kill(vc.th_worker)
    exit
end

Signal.trap(:INT){
    puts("received interrupt signal. (ctrl+c)")
    exit_signal_recieved(voice_clock)
}

Signal.trap(:TSTP){
    puts("received terminal stop signal. (ctrl+z)")
    exit_signal_recieved(voice_clock)
}

Signal.trap(:TERM){
    puts("received termination signal. (kill -15)")
    exit_signal_recieved(voice_clock)
}

#waiting for working thread until it complete.
voice_clock.th_worker.join

                                    
  • 12-162行目:VoiceClockクラスの定義。現在時刻の取得や送信及びコマンドの受信等に関する処理をまとめている。
  • 15行目:現在時刻を通知するインターバルを設定する。
  • 16-18行目:現在時刻の送信を行うスレッドの停止や終了を制御するフラグ。
  • 21-28行目:現在時刻の送信を行う通信クラスのインスタンス生成及び宛先情報などの設定。
  • 31-34行目:音声コマンドの受信を行う通信クラスのインスタンス生成及び受信元情報などの設定。
    (サンプルでは、現在時刻の送信インスタンスと分けて生成しているが同じインスタンスにしても問題ない)
  • 39-97行目:音声コマンド受信時のCallback関数の定義。
  • 44-49行目:音声時計の通知開始処理
    コマンド受信時に現在時刻送信スレッドを開始する。
  • 50-52行目:音声時計の通知停止処理
    コマンド受信時に現在時刻送信スレッドを停止(待機状態)する。
  • 53-55行目:音声時計プロセスの終了処理
    コマンド受信時にvoice_clock.rbプロセスを終了する。
  • 56-79行目:フリーテキストで送信されたコマンドの処理
    解析結果、該当するコマンドが無い場合は、エラーメッセージを通知する。(77行目)
  • 57-65行目:送信インターバルの設定コマンドの解析処理
    正規表現で3つのkeywords(インタバール、数字、設定or変更)が含まれている場合にのみ処理対象としている。
    数字が複数含まれていた場合は、先頭の数字を設定値としている。
    例)受信したテキスト:"送信インターバルを10分または20分に変更" -> 10分を設定値とする
  • 66-75行目:音声合成エンジンの性別設定コマンドの解析処理
    正規表現で2つのkeywords(男性or女性、設定or変更)が含まれている場合にのみ処理対象としている。
    性別となる語句が複数含まれていた場合は、先頭の語句を設定値としている。
    例)受信したテキスト:"女性または男性にに設定" -> 女性を設定値とする
  • 83-88行目:コマンド受信結果の応答を送信する。
  • 102-105行目:Haruziraから「Communication Stop Notice(0x11)」メッセージの受信時に通知されるCallback関数の定義。
    Error Codeに関しては、Rubyで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 111-114行目:通信クラスでエラー(Haruzira側からの切断以外)が発生した場合に通知されるCallback関数の定義。
    Error Codeに関しては、Rubyで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 117-121行目:Callback関数の登録。音声コマンドの受信用のCallback関数は、音声コマンド用の通信クラスインスタンスに登録している。
  • 124行目:VoiceClockクラスが提供するaccessor(property)に関する定義。
  • 150-179行目:現在時刻のメッセージ通知に関する処理を行うスレッド定義。
  • 130行目:音声時計の通知開始コマンドを受信するまで待機する。
  • 135-144行目:現在時刻を通知する処理。
    分単位に送信インターバル値に一致しているかチェックする。
  • 148-151行目:音声時計の通知停止コマンドを受信した場合は、スレッドを待機させる。
  • 167行目:VoiceClockクラスのインスタンス生成。(Haruziraが稼働するIP addressとPortを指定する)。
  • 170行目:現在時刻の通知処理スレッドを起動する。
  • 172-190行目:OSからのシグナル受信時の処理定義。
    ctrl+c、ctrl+z、kill -15を実行時のシグナル受信時に、後処理をしプロセスを終了させる。
  • 193行目:現在時刻の通知処理スレッドを起動し、終了するまで待機する。

2.プログラムの実行手順

①185行目のIP addressとPort番号を、環境に合わせて編集して下さい。(Port番号は任意)

②15行目の通知インターバル既定値を、好みに合わせて1分~60分の範囲内で編集して下さい。(任意)

③25行目(通常受信)と33行目(音声コマンドの受信)のPort番号を、環境に合わせて編集して下さい。(任意)

④Sample Programの実行
Daemonプロセス(Backgroundプロセス)として起動します。(Debug時などはForegroundでも構わない)
なお、Logを出力しない場合は、リダイレクト(redirect)を省略して下さい。
また、ファイアーウォールの許可を求められた場合は、許可して下さい。

nohup ruby voice_clock.rb > output.log &

                                        

⑤Haruziraから、音声認識、ダイレクト送信、フリーテキストなどを利用して音声時計の通知を開始させます。

⑥Haruziraから、フリーテキストで送信インターバルの変更をさせてみましょう。
インターバル、設定値、変更or設定のキーワードが含まれていれば語順や組み合わせは自由です。
例)「インターバルを1分に変更」、「設定インターバル1分」
※音声認識で数値を認識させたい場合のコツ。 1分 -> 「イチフン」、10分 -> 「ジップン」

⑦Haruziraから、フリーテキストで音声合成エンジンの性別を変更してみましょう。
男性or女性、変更or設定のキーワードが含まれていれば、語順や組み合わせは自由です。
例)「男性に変更」、「性別設定を男性にする」










.Net Sample Program

.NetのSample Programは、WPFのGUIアプリとして作成しています。また、英語と日本語のローカライズ(Localize)に対応させています。
そのため、PythonやRubyと比較するとコード量が多くなります。

極力難しいことはやらないように作成しているので、Visual Studio 2017を利用してDebugしながらソースを解析してもらえれば理解できると思います。
また、基本的な利用方法はPythonやRubyと同じなので、SDK Manualを参照していただければ理解できると思います。

ここでは、SDKを利用している処理を中心に説明します。

Sample ProgramにはSDKが含まれていませんので、ダウンロードし参照設定を行って下さい。
Haruzira SDKの参照方法などについては、Haruzira SDK User's Manual - Usageを参照して下さい。

先ずは、主なソースファイルについて説明します。(画面は、メイン画面のみです。)

MainWindow.xaml メイン画面のデザインを行っているXAMLファイル。
MainWindow.cs メイン画面のコードビハインド(Code-behind)ファイル。
今回は、コントロールをバインド(Bind)させていますが、イベントのバインドは行っていません。
(イベント自体が少なくバインドするメリットがないため。)
Style/Style.xaml デザイン時に利用する共通定義を行っているリソースファイル。
MyClass/VoiceClockProc.cs 現在時刻の送信や、音声コマンド受信に関わる処理をまとめたクラス。
今回のSample Programのメイン処理を定義したソースファイル。
MyClass/JsonConfig.cs 送信先情報や、アプリの設定に関わる情報をJSON形式の設定ファイル「config.json」から取得している。
この設定ファイルの処理や、バインドされたコントロールのプロパティ(Property)通知に関する処理をまとめたクラス。
MyClass/JsonCommon.cs JSON形式の設定ファイルから取得したデータをデシリアライズ(Deserialize)するクラス。
Properties/Resources.* Resources.resx: 英語のローカライズファイル。
Resources.ja-JP.resx: 日本語のローカライズファイル。
多言語化させたい場合は、同様に追加する。



1.メイン画面のソースコード説明
MainWindow.cs

/// <summary>
/// window loaded event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    //set binding.
    MainGrid.DataContext = jconfig;

    //create instance. (Haruzira's ip and port)
    vcp = new VoiceClockProc(jconfig.Items.DestinationIPAddress, jconfig.Items.DestinationPort);


    //start a worker task for current time processing.
    vcp.StartWorkerClock();


    //start a task for waiting for worker task.
    StartWorkerWaitingTask();

    Debug.WriteLine("end loaded event.");

}

/// <summary>
/// start a working task.
/// </summary>
private void StartWorkerWaitingTask()
{
    //waiting for working task until it's completed or canceled.
    Task.Run(() =>
    {
        try
        {
            vcp.ThWorker.Wait();
        }
        catch (Exception ex)
        {
            Debug.WriteLine("canceld worker task.\r\n" + ex.Message);

        }
        finally
        {
            if (vcp.CancelToken.IsCancellationRequested)
                Debug.WriteLine("canceld worker task.");
            vcp.TokenSource.Dispose();
            Dispatcher.BeginInvoke(new Action(() =>
            {
                Application.Current.Shutdown();
            }));
            Debug.WriteLine("application exit.");
        }
    });

}

                                    
  • 6-24行目:Window_Loadedイベント(Event)の定義。
    VoiceClockProcクラスのインスタンスを生成し、現在時刻の表示や送信処理を行うメインワーカータスク(main worket task)を起動している。
    また、起動したメインワーカータスクの終了を待ち受けるタスクを起動している。
  • 29-56行目:メインワーカータスクの待ち受けタスクの定義。
    上記で起動したメインワーカータスクが終了または中断されるまで待機する。

2.SDKの処理を記述したソースコードの説明
VoiceClockProc.cs

public VoiceClockProc(string ip, ushort port)
{
    // for canceling worker task.
    TokenSource = new CancellationTokenSource();
    CancelToken = TokenSource.Token;

    //get cultureinfo(lang code name -> hex).
    CultureInfo CurrentCultureInfo = new CultureInfo(jconfig.Language, false);

    // for sending clock message.
    clt_tcp_comm.ServerIP = ip;
    clt_tcp_comm.ServerPortNo = port;
    clt_tcp_comm.ReceivePort = jconfig.Items.ReceivePort;
    clt_tcp_comm.ReqSendDataCompletionNoticeNecessity = HzSpCompletionNoticeNecessity.NoNeed;
    clt_tcp_comm.ReqSendDataSpeechRepeat = jconfig.Items.RepeatPlaybackCount;
    clt_tcp_comm.ReqSendDataSpeechLocaleId = (ushort)CurrentCultureInfo.LCID;
    clt_tcp_comm.startAsynchronousListener();

    // for receiving voice commands.
    cmd_rcv_com.ReceivePort = jconfig.Items.CommandReceivePort;
    cmd_rcv_com.ReqSendDataCompletionNoticeNecessity = HzSpCompletionNoticeNecessity.NoNeed;
    cmd_rcv_com.ReqSendDataSpeechLocaleId = (ushort)CurrentCultureInfo.LCID;
    cmd_rcv_com.startAsynchronousListener();

    //Regist event handler when asynchronous message received.(if you need)
    clt_tcp_comm.evNotifyMessageEvent += new delegateNotifyMessageEvent(NotifyCommunicatonMessage);
    clt_tcp_comm.evNotifyReceivedDisConnectEvent += new delegateNotifyReceivedDisConnectEvent(NotifyReceivedDisconnect);
    cmd_rcv_com.evNotifySendSpeechRecognitionCommand += new delegateNotifySendSpeechRecognitionCommand(RcvSendSpeechRecognitionCommand);

    //initialize controls and etc.
    jconfig.CommunicationStatus = StatusDefault;
    jconfig.CurrentTime = "00:00:00";
    jconfig.TimeFormatText = "AM";
    if (jconfig.Items.TimeFormat == TimeFormatType.Hour_24)
        jconfig.TimeFormatVisibility = System.Windows.Visibility.Collapsed;
    else
        jconfig.TimeFormatVisibility = System.Windows.Visibility.Visible;
    // gender of voice synthesizer.
    SetGenderInfo(jconfig.Items.Gender);

}

#region Remote Communication Event(callback funtions)
/// <summary>
/// When disconected.(received a asynchronous message from access point or when occurred on receiving thread for some reasons.)
/// </summary>
/// <param name="msg">message</param>
/// <param name="st">status</param>
private void NotifyReceivedDisconnect(string msg, int st)
{
    Debug.WriteLine(string.Format("{0}, Status[0x{1:x2}]\r\n", msg, st));
}

/// <summary>
/// Error notify when occurred on connecting.
/// </summary>
/// <param name="msg">message</param>
/// <param name="msgId">message id</param>
/// <param name="errCode">error code</param>
private void NotifyCommunicatonMessage(string msg, int msgId, int errCode)
{
    Debug.WriteLine(string.Format("Message ID[{0}], Error Code[0x{1:x2}], {2}\r\n", msgId, errCode, msg));
}

/// <summary>
/// notify when received a SendSpeechRecognitionCommand mmessage.
/// </summary>
/// <param name="cmdInfo">SpeechRecognitionCommandInfo class object</param>
private async void RcvSendSpeechRecognitionCommand(SpeechRecognitionCommandInfo cmd_info)
{
    Debug.WriteLine(string.Format("Received 'send speech recognition command' message;\r\n ip[{0}], port[{1}], mode[{2}],  command[{3}], time stamp[{4}]",
        cmd_info.IpAddr, cmd_info.Port, cmd_info.Mode, cmd_info.Command, cmd_info.Timestamp) + "\r\n");

    string announce = "";

    // check received command
    if (cmd_info.Command.ToLower() == "start voice clock")
    {
        //start voice clock.
        flg_stop = false;
        announce = Properties.Resources.StartVoiceClock;
        //update status lamp(lime)
        jconfig.CommunicationStatus = StatusActive;
    }
    else if(cmd_info.Command.ToLower() == "stop voice clock")
    {
        //stop voice clock.
        flg_stop = true;
        announce = Properties.Resources.StopVoiceClock;
        //update status lamp(gray)
        jconfig.CommunicationStatus = StatusDefault;
    }
    else if(cmd_info.Command.ToLower() == "terminate voice clock process")
    {
        //shutdown app.
        flg_stop = true;
        flg_term = true;
        announce = Properties.Resources.ShutdownApp;
    }
    else
    {
        //confirm that keywords are included in the command.(free text command)
        MatchCollection vals;
        string val;
        ushort alarm_interval = 30;
        if (Regex.IsMatch(cmd_info.Command.ToLower(), string.Format(@"^(?=.*{0:s})(?=.*[\d]+)(?=.*({1:s}|{2:s}))",
            Properties.Resources.Interval, Properties.Resources.Change, Properties.Resources.Set)))
        {
            //sending interval command
            vals = Regex.Matches(cmd_info.Command, @"[\d]+");
            Regex num_rg = new Regex("[0-9]+");
            val = num_rg.Replace(vals[0].Value, Full2Harlf);
            ushort.TryParse(val, out alarm_interval);
            if (alarm_interval >= 1 && alarm_interval <= 60)
            {
                announce = string.Format(Properties.Resources.SetSendingInterval, alarm_interval);
                //update sending interval
                jconfig.SendInterval = alarm_interval;
            }
            else
                announce = string.Format(Properties.Resources.WarningSendingInterval, alarm_interval);
        }
        else if (Regex.IsMatch(cmd_info.Command.ToLower(), string.Format(@"^(?=.*({0:s}|{1:s}))(?=.*({2:s}|{3:s}))",
            Properties.Resources.Male, Properties.Resources.Female, Properties.Resources.Change, Properties.Resources.Set)))
        {
            //gender command
            vals = Regex.Matches(cmd_info.Command, string.Format(@"({0:s}|{1:s})", Properties.Resources.Male, Properties.Resources.Female));
            GenderType type = GenderType.Female;
            if (vals[0].Value == Properties.Resources.Male)
                type = GenderType.Male;

            announce = string.Format(Properties.Resources.SetGender, vals[0].Value);
            //update gender
            SetGenderInfo(type);
        }
        else
        {
            //command not found.
            announce = Properties.Resources.CommandNotFound;
        }
    }

    Debug.WriteLine(announce);

    try
    {
        // send speech data.
        await SendResponce(cmd_info, announce);
        if (flg_term)
            TokenSource.Cancel();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
}
#endregion

/// <summary>
/// send a message of the current time.
/// </summary>
/// <param name="msg"></param>
/// 
private void SendTimeMessage(string msg)
{
    Task.Run(async () =>
    {
        clt_tcp_comm.ReqSendDataText = msg;
        string time_stamp = await clt_tcp_comm.sendSpeechDataEx();
        if (time_stamp != null)
        {
            Debug.WriteLine("success. timestamp[{0:s}]", time_stamp);
        }
        else
        {
            Debug.WriteLine("failed to send.");
        }
    });
}

/// <summary>
/// start a worker task for sending current time.
/// </summary>
public void StartWorkerClock()
{
    ThWorker = Task.Run(async () =>
    {
        Debug.WriteLine("start worker thread.");
        StringBuilder msg = new StringBuilder();
        flg_stop = true;
        while (true)
        {
            //already canceled?
            CancelToken.ThrowIfCancellationRequested();

            try
            {
                DateTime nowtime = DateTime.Now;

                if (!flg_stop && nowtime.Second == 0 && (nowtime.Minute % jconfig.SendInterval == 0))
                {
                    Debug.WriteLine("alarm time.[{0}]", nowtime.ToString());
                    msg.Clear();
                    if (jconfig.Items.TimeFormat == TimeFormatType.Hour_24)
                    {
                        msg.Append(string.Format(Properties.Resources.CurrentTimeMessage, nowtime.ToString("HH:mm")));
                    }
                    else
                    {
                        if(jconfig.Language.ToLower() == LANG_JP)
                        {
                            if (nowtime.Hour == 0 || nowtime.Hour == 12)
                                msg.Append(string.Format(Properties.Resources.CurrentTimeMessage, nowtime.ToString("tt、00:mm")));
                            else
                                msg.Append(string.Format(Properties.Resources.CurrentTimeMessage, nowtime.ToString("tt、hh:mm")));
                        }
                        else
                        {
                                msg.Append(string.Format(Properties.Resources.CurrentTimeMessage, nowtime.ToString("hh:mm") +
                                    nowtime.ToString(" tt", CultureInfo.CreateSpecificCulture(jconfig.Language))));
                        }
                    }

                    //send the current time message.
                    SendTimeMessage(msg.ToString());
                }

                Debug.WriteLine("{0:D2}:{1:D2}:{2:D2}", nowtime.Hour, nowtime.Minute, nowtime.Second);
                if(jconfig.Items.TimeFormat == TimeFormatType.Hour_24)
                {
                    jconfig.CurrentTime = nowtime.ToString("HH:mm:ss");
                }
                else
                {
                    if ((nowtime.Hour == 0 || nowtime.Hour == 12) && jconfig.Language.ToLower() == LANG_JP)
                        jconfig.CurrentTime = nowtime.ToString("00:mm:ss");
                    else
                        jconfig.CurrentTime = nowtime.ToString("hh:mm:ss");
                    jconfig.TimeFormatText= nowtime.ToString("tt", CultureInfo.CreateSpecificCulture(jconfig.Language));
                }

                await Task.Delay(1000);

                if (CancelToken.IsCancellationRequested)
                    CancelToken.ThrowIfCancellationRequested();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
                break;
            }
        }
    }, TokenSource.Token);

}

                                    
  • 1-41行目:コンストラクタ(constructor)の定義。
  • 11-17行目:現在時刻の送信を行う通信クラスのインスタンス生成及び宛先情報などの設定。
  • 20-23行目:音声コマンドの受信を行う通信クラスのインスタンス生成及び受信元情報などの設定。
    (サンプルでは、現在時刻の送信インスタンスと分けて生成しているが同じインスタンスにしても問題ない)
  • 26-28行目:Callback関数の登録。音声コマンドの受信用のCallback関数は、音声コマンド用の通信クラスインスタンスに登録している。
  • 49-52行目:Haruziraから「Communication Stop Notice(0x11)」メッセージの受信時に通知されるCallback関数の定義。
    Error Codeに関しては、Rubyで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 60-63行目:通信クラスでエラー(Haruzira側からの切断以外)が発生した場合に通知されるCallback関数の定義。
    Error Codeに関しては、Rubyで定義されたError Codeに準ずる。
    このCallback関数の定義は必須ではない。
  • 69-156行目:音声コマンド受信時のCallback関数の定義。
  • 77-84行目:音声時計の通知開始処理
    コマンド受信時に現在時刻送信タスクを開始する。
    また、ステータスランプを点灯する。
  • 85-92行目:音声時計の通知停止処理
    コマンド受信時に現在時刻送信タスクを停止(待機状態)する。 また、ステータスランプを消灯する。
  • 93-99行目:音声時計プロセスの終了処理
    コマンド受信時にvoice_clock.rbプロセスを終了する。
  • 100-141行目:フリーテキストで送信されたコマンドの処理
    解析結果、該当するコマンドが無い場合は、エラーメッセージを通知する。(77行目)
  • 106-122行目:送信インターバルの設定コマンドの解析処理
    正規表現で3つのkeywords(インタバール、数字、設定or変更)が含まれている場合にのみ処理対象としている。
    数字が複数含まれていた場合は、先頭の数字を設定値としている。
    例)受信したテキスト:"送信インターバルを10分または20分に変更" -> 10分を設定値とする
  • 123-135行目:音声合成エンジンの性別設定コマンドの解析処理
    正規表現で2つのkeywords(男性or女性、設定or変更)が含まれている場合にのみ処理対象としている。
    性別となる語句が複数含まれていた場合は、先頭の語句を設定値としている。
    また、性別によりGUIのハートマーク(heart mark)の色を変更する。
    例)受信したテキスト:"女性または男性に設定" -> 女性を設定値とする
  • 145-155行目:コマンド受信結果の応答を送信する。
    また、終了フラグが立った場合に、タスクへ中断要求を行う。
  • 164-179行目:現在時刻の送信を行うためのMethod定義。
    GUIで1秒毎に現在時刻を表示しているため、送信処理で描画が遅延しないよう別タスクで送信を行う。
    なお、時刻の描画処理と送信処理を別タスクで管理しても構わない。
  • 184-253行目:現在時刻のメッセージ通知とGUIへの表示に関する処理を行うタスク定義。
  • 200-226行目:現在時刻を通知する処理。
    分単位に送信インターバル値に一致しているかチェックする。
    現在時刻の表記は、12時間及び24時間に対応させるため、処理を分けている。また、ローカライズにも対応させている。
  • 229-240行目:現在時刻をGUIへ表示させる処理。
    現在時刻の表記は、12時間及び24時間に対応させるため、処理を分けている。また、ローカライズにも対応させている。










.Netアプリの実行

Sample Programでは、最小化以外の操作は全てHaruziraからリモートで行います。
登録済みコマンドやフリーテキストコマンドなどで、音声認識機能を利用して送信できます。
またマイクが無い場合でも、ダイレクトコマンド送信やキーボード入力でコマンド送信を行うことも可能です。


操作を行っている様子などは、動画を参考にして下さい。


実行ファイルのダウンロード(Download)

※ダウンロード後に解凍して下さい。(インストール不要)



1.アプリの設定ファイル

Sample Programでは、送信先情報などの設定データを「config.json」ファイルで管理しています。(GUIによる設定画面は非搭載)
そのため必要な情報は、起動前にこの設定ファイルを編集して下さい。

通常は、送信先のIP addressのみ変更すれば通信できると思います。後はお好みで変更して下さい。
設定ファイルの項目は、次のようになります。

DestinationIPAddress 送信先(Haruzira)のIP addressを設定する。
DestinationPort 送信先(Haruzira)のPort番号を設定する。(任意)
既定値(46000)で問題ない場合は変更不要。
ReceivePort このアプリの受信Port番号を設定する。(任意)
既定値(46210)で問題ない場合は変更不要。
CommandReceivePort このアプリの音声コマンド受信時のPort番号を設定する。(任意)
既定値(46200)で問題ない場合は変更不要。
RepeatPlaybackCount 現在時刻の再生時に、繰り返す回数を設定する。(任意)
既定値(1回)で問題ない場合は変更不要。(Range:0-127)
SendInterval 現在時刻の通知を行うインターバル値を設定する。(任意)
既定値(10分)で問題ない場合は変更不要。(Range:1分~60分、単位:分)
Gender 音声合成エンジンの性別を設定する。(任意)
既定値(0)で問題ない場合は変更不要。(0:女性、1:男性)
TimeFormat 現在時刻の表示フォーマットを設定する。(任意)
既定値(0)で問題ない場合は変更不要。(0:12時間制、1:24時間制)
Language 表示や再生を行う言語を設定する。(任意)
既定値(ja-JP)で問題ない場合は変更不要。(日本語:ja-JP、英語:en-US)
新たに言語を追加したい場合は、この項目とリソースファイル(Resources.*.resx)を追加する必要がある。(要Rebuild)
ロケールIDについては、MSのサイトを参照する。

※設定値は全て半角で記述する。




2.プログラムの実行手順

音声時計を起動する前に、設定ファイルの編集と実行前の事前準備を済ませて下さい。

①VoiceClock.exeを実行すると、アプリが起動します。
なお、OSのアーキテクチャ(architecture)に合わせて、64bit/32bitのどちらかを選択して下さい。
また、ファイアーウォール(Firewall)の許可を求められた場合は、許可して下さい。

②Haruziraから、音声認識、ダイレクト送信、フリーテキストなどを利用して音声時計の通知を開始させます。
音声時計アプリのステータスランプ(status lamp)が緑色(green)に点灯します。

③Haruziraから、フリーテキストで送信インターバル値を変更してみましょう。
音声時計アプリのインターバル値の表示が更新されます。
インターバル、設定値、変更or設定のキーワードが含まれていれば、語順や組み合わせは自由です。
例)「インターバルを1分に変更」、「設定インターバル1分」
※音声認識で数値を認識させたい場合のコツ。 1分 -> 「イチフン」、10分 -> 「ジップン」

④Haruziraから、フリーテキストで音声合成エンジンの性別を変更してみましょう。
音声時計アプリのハートマークが、男性は青色(blue)、女性はピンク色(pink)に変化します。
男性or女性、変更or設定のキーワードが含まれていれば、語順や組み合わせは自由です。
例)「男性に変更」、「性別設定を男性にする」










まとめ

今回のSample Programで、クライアントからのメッセージの送信とHaruziraからの音声コマンドの送信に関して、一通り紹介できたと思います。
SDKには、他にもセキュリティ機能なども実装していますので試してみて下さい。

現在のITやIoT関連の商品開発はスピードが要求されます。そのため、様々なツールを上手く活用して効率よく業務に活用する必要性があります。

例えば音声合成を利用したシステムを開発する場合、それなりの機能を一から全て作成しようとすると、結構な時間を必要とします。
そのような場合に、Haruziraなどを利用してプロトタイプを作成し提示できれば顧客の反応もわかりやすいと思います。

ドキュメントや動画などの資料によるプレゼンテーション(presentation)だけでは伝わりにくいことも、上手く表現できるのではないでしょうか。

次回以降も、SDKの機能を紹介しながら、実用的なSample Programを紹介していく予定です。

なお、Sample Programは自由に改造して構わないので、独自の仕様にカスタマイズし遊んでみて下さい。
音声時計をローカルで再生するように変更するのも面白いと思います。

筆者も時間が確保できたら、ローカル再生にも対応した音声時計の製品版を公開してみようと考えています。







更新履歴

2019.02.21

 
  • 各言語のSample Program項目のNoteに、登録コマンドのシンプルな受信例のリンク先を追加

2019.02.18

 
  • 新規追加