RubyでSAPI 5をしゃべらせる
RubyからMicrosoft Speech API 5.0を動かして遊んでみたので書いてみます。
以前BEPというプロジェクトでEmacspeakに日英2カ国語をしゃべらせるために、スピーチサーバと呼ばれる音声出力部を自作したことがありました。本家EmacspeakのスピーチサーバはTCL(エンジン制御部はC)で書かれているのですが、その頃の知識の問題もあって、日本語版は全体をC++で開発しました。文字列操作がとても面倒だったことを覚えています。
今ならLinux版はエンジン制御はCで書き、その上にスクリプト系言語をかぶせるのがいいだろうと思っています。RubyにSwigあたりでCライブラリをつなぐのが良さそうです。
ところで、BEPにはWindows版もあり、そちらはCOMを利用したC++のプログラムになっています。COMならRubyのWin32OLEでできるんじゃないか、ということで、SAPIを利用する方法を調べてみました。
参考としたページは以下です。
利用したRubyはCygwinのものです。
結果として、意外とすんなりできそうな感触を得ました。サンプルコードを以下に貼り付けておきます。動作にはXPに標準で入っているMicrosoft SAMと、JAWS for Windows日本語版に付属するDTalker Taroという日本語TTSエンジンが必要です。ほかの日本語TTSを使う場合はIdを調べて置き換えればできます。
なお、後からwin32-sapiというWin32Utilsプロジェクトのラッパーがgemでインストールできることを知りました。こっちの方が読みやすくできそうです。
ただ、BEPのスピーチサーバを作るには、日本語と間の間を狭くする工夫、通知音を出すためのsin波ジェネレータとそのサウンド出力、XMLタグによる音声マークアップなどの課題が残っています。
RubyからSAPI5を使うサンプルプログラム
require 'win32ole' # SpVoiceのインスタンスを生成 s = WIN32OLE.new('SAPI.SpVoice') # エンジンの一覧を取得 vs = s.GetVoices # 名前がtaroであるエンジンを選択 voice_taro = nil voice_sam = nil # taroとsamのエンジンを見つける (0 .. vs.Count-1).each do |i| id = vs.Item(i).Id #puts id if (id.match(/DTalker_Taro/)) voice_taro = vs.Item(i) elsif (id.match(/MSSam/)) voice_sam = vs.Item(i) end end # 音声としてtaroを選択 s.Voice = voice_taro # 速度を10に設定 s.Rate = 10 # 読み上げ開始。第2引数はビットORで指定することになっていて、1は非同期出力を表す。 # ちなみに2は直前の残っている音声を切り捨て、 # 8は文字列中のXMLマークアップを解釈する。 s.Speak('これはRubyでSpeech APIを制御してしゃべらせています。どうでしょうか。', 1) # タイムアウトを1秒に指定して終了待ち。1秒じゃ終わらないので途中で制御が次に移る。 s.WaitUntilDone(1000) # 二つ目の文を読み上げ。フラグには前に残っている音声を切り捨てるために2を指定。 s.Speak('二つ目の文です。', 2) # MS Samに切り替え s.Voice = voice_sam s.Speak('this is sam') # 再びtaro s.Voice = voice_taro s.Speak('四つ目の文です。', 2) # 同期読み上げを指定しているので全部読み終わってから終了する。