challenge 音声合成でHello, world!

与えられた文字列を音声合成して再生する関数を作ってください。

使用したライブラリはタグでつけてください。またOSに依存する場合もタグでつけてください。日本語文字列も発音できることが好ましいですが、必須ではありません。

以下はサンプルです。

>>> say("Hello, world!")
>>> say("con nitch were") # 「こんにちは」
>>> say("daw cat coo org, sole what program mar know tum yen know Colosseum death")

Posted feedbacks - Flatten

Nested Hidden
一番のりしてみる。
1
sub say { `say $_[0]`; }

Windows では COM 経由で SAPI を使うといいようですね。
1
2
3
4
function say($msg) {
  $speaker = new-object -com Sapi.SpVoice
  $speaker.Speak($msg, 1)
}

Squeak Smalltalk で。組み込みです。
1
Speaker default say: 'Hello, world!'

Microsoft Agent を使ってます。
AxAgent1がAgentコンポーネントオブジェクトです。
Visual Studioでの操作は、Windowsアプリケーションを作り
COMコンポーネントの追加で Microsoft Agent Control 2.0を選択、
そしてFormにコンポーネントを貼り付けというふうになります。

' .NET Frameworkにも音声合成用の抽象クラスだけはあるのね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Private Sub MainForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    AxAgent1.Connected = True
    AxAgent1.Characters.Load("A")
End Sub

Private Sub MainForm_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
    AxAgent1.Characters.Unload("A")
    AxAgent1.Connected = False
End Sub

Sub Say(ByVal message As String)
    Dim chr As AgentObjects.IAgentCtlCharacter = AxAgent1.Characters("A")
    chr.Show()
    chr.Speak(message)
End Sub

> 組み込みです

惚れました。

他力本願
eSpeak のコマンドを呼ぶ。
espeakはおもしろいですね。
ちゃんとそれらしく、聞える
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module Main (main) where

import System.Environment
import System.Cmd

main =  system . mkcmd . unwords  =<< getArgs
mkcmd s = "espeak '" ++ s ++ "'"

{-
字では表現できませんが、以下でちゃんとしゃべってくれます。
*Main> :main Hello, world!
ExitSuccess
*Main> :main con nitch were
ExitSuccess
*Main> :main daw cat coo org, sole what program mar know tum yen know Colosseum death
ExitSuccess
-}


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
require "rubygems"
require "win32/sapi5" # http://rubyforge.org/projects/win32utils
include Win32

def say(msg)
  v = SpVoice.new
  v.Speak(msg)
end

if $0 == __FILE__
  if ARGV[0]
    say(ARGV[0])
  else
    say("Hello, world!")
    say("con nitch were") # 「こんにちは」
    say("daw cat coo org, sole what program mar know tum yen know Colosseum death")
  end
end

ぜひ将来 Gauche にも。w

他にも、ランダムに生成されるキャラ顔にセリフをしゃべらせる(発声のタイミングにあわせてそれっぽく口を動かします)…といったこともできます。
1
2
3
4
| speaker |
speaker := Speaker manWithHead.
(speaker findAVoice: GesturalVoice) head openInHand.
speaker say: 'Hello, world!'

自分には難しかったので、シェルからfestivalを呼び出すことにしました。

festivalは内部スクリプティングにshcemeが採用されてるんですねー。
ubuntu/debianでは、パッケージがあったので導入は簡単でした。
1
2
3
4
5
6
7
8
9
(defpackage :doukaku-59 (:use :cl :asdf) (:export say))
(in-package :doukaku-59)

(defun say (str)
  (let ((exec-str (concatenate 'string "echo " str "|festival --tts")))
    (let ((res (run-shell-command exec-str)))
      (case res
	(0 (values str 0))
	(otherwise (values nil res))))))

↓から落としてインストールすれば様々な言語が使える模様。(TTSが登録されて無いようなら "regsvr32 VText.dll" する。)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function doukaku59(text, option){ // テキスト [, {language:言語, gender:性別, speed:速度}]
  var i = 1, c, l, g, s;
  with(new ActiveXObject('TextToSpeech.TextToSpeech')){
    c = countEngines;
    if(l = option.language) for(; i <= c && !~modeName(i).indexOf(l); i++);
    if(g = option.gender)   for(; i <= c && !~modeName(i).indexOf(g); i++);
    if(s = option.speed)    if(95 <= s && s <= 270) speed = s;
    try { select(i) } catch(e){ WSH.echo('No engines for the option.'); WSH.quit() }
    for(speak(text); isSpeaking; WSH.sleep(590));
  }
}
doukaku59('Hello, world!', {language:'English', gender:'Male', speed:160});
doukaku59('こんにちは!', {language:'Japanese', gender:'Female'});

eSpeakのライブラリをSML#のFFI機能を使って呼んでいます。
ライブラリの場所は/usr/libに決め打ち。さらに設定はすべて固定。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
fun say text =
let
  open DynamicLink

  val lib = dlopen "/usr/lib/libespeak.so"
  val init = dlsym (lib, "espeak_Initialize")
  val synth = dlsym (lib, "espeak_Synth")
  val sync = dlsym (lib, "espeak_Synchronize")

  val espeakSyncronize = sync : _import () -> int
  fun espeakInitialize () =
    _ffiapply init (0 : int,
                    0 : int,
                    NULL : string ptr,
                    0 : int) : int

  fun espeakSynth () =
    _ffiapply synth (text : string,
                     size text + 1 : int,
                     0 : int,
                     1 : int,
                     0 : int,
                     0 : int,
                     NULL : word ptr,
                     NULL : char ptr) : int
in
  espeakInitialize ();
  espeakSynth ();
  espeakSyncronize ()
end;

say "Hello, world!"

もう少しましな日本語を喋らせることもできますが、とりあえず投稿
1
2
say "Hello, world."
say "con itch wha."

FreeTTSというライブラリを使ってみました。"Hello, world!"だとちょっと変な感じになるので、"Hello world!"にしています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;

public class Speaker {
    public static void main(String[] args) {

        args = args.length == 0 ? new String[] { "Hello world!" } : args;
        StringBuffer message = new StringBuffer();
        for (int i = 0; i < args.length; i++) {
            message.append(args[i]);
            message.append(' ');
        }

        VoiceManager voiceManager = VoiceManager.getInstance();
        Voice helloVoice = voiceManager.getVoice("kevin16");
        
        System.out.printf("%s say \"%s\".%n", helloVoice.getName(), message);
        helloVoice.allocate();
        helloVoice.setVolume(100.0f);
        helloVoice.speak(message.toString());
        helloVoice.deallocate();
    }
}

MS Agentを使ってます。
最低限Text-to-speech engines (日本語か英語版)とSAPI 4.0 が必要です。
1
2
3
4
5
6
7
8
9
say(「Hello, world」, "英語")
say(「こんにちは」, "日本語")
say(「どう書くorg、それはプログラマの為のコロシアムです。」, "日本語")

*say(text, lang)
 エージェントマーリン召喚
 langへエージェント言語変更
 textとエージェント言う
 エージェント待つ

おおお、これはsayの定義の仕方を変えれば
「"日本語"で「こんにちは」と言う」
なんてこともできそうですね!

できました。
でも、標準命令の「言う」が使えなくなってしまいます。
「いう」の方は使えるから問題ないと思いますが。

ちなみに、「言う、いう」はOKボタンだけのダイアログを出す命令です。
1
2
3
4
5
6
7
8
"英語"で「Hello, world!」と言う
"日本語"で「こんにちは」と言う

*言う(langで, textと)
 エージェントマーリン召喚
 langへエージェント言語変更
 textとエージェント言う
 エージェント待つ

c-wrapperのホームページに張ってあるサンプルコードそのままです。Objective-CとSchemeが融合した変なコードになっています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(c-load "Cocoa/Cocoa.h" 
        :libs "-framework Foundation -framework Cocoa")

[[NSAutoreleasePool :alloc] :init]

(define say 
  (let1 s [[NSSpeechSynthesizer :alloc] :init]
    (lambda (str)
      [s :startSpeakingString (@ str)])))

(say "Hello, world")

しまった、最初の(use objc-wrapper)が抜けていた。正しくは以下の通りです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(use objc-wrapper)

(c-load "Cocoa/Cocoa.h"
        :libs "-framework Foundation -framework Cocoa")

[[NSAutoreleasePool :alloc] :init]

(define say 
  (let1 s [[NSSpeechSynthesizer :alloc] :init]
    (lambda (str)
      [s :startSpeakingString (@ str)])))

(say "Hello, world")

同等のコードをpythonで書いてみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from ctypes import cdll

def init():
  es = cdll.LoadLibrary('libespeak.so.1')
  es.espeak_Initialize(0, 0, None, 0)
  es.espeak_Synchronize()
  return lambda s: es.espeak_Synth(s, len(s)+1, 0, 1, 0, 0, None, None)

say = init()
say('Hello, world!')
say('con nitch were')
say('daw cat coo org, sole what program mar know tum yen know Colosseum death')

僕が用意していたのもこの方法でした。
1
2
import win32com.client
say = win32com.client.Dispatch("SAPI.SpVoice").Speak

.NET3.0を使いました。
システムの音声合成エンジンに日本語を話せるエージェントが設定してある事を前提。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using System;
using System.Speech.Synthesis;
class Program {
    static void say(string s) {
        SpeechSynthesizer syn = new SpeechSynthesizer();
        syn.Speak(s);
    }
    static void Main(string[] args) {
        say("Hello, world!");
        say("こんにちは");
    }
}

何のひねりもないのは…すごい反則してる気がしてきます。
1
2
3
say "Hello, world!"
say "con nitch were" -- 「こんにちは」
say "daw cat coo org, sole what program mar know tum yen know Colosseum death"


NSLog(@"%@", [NSSpeechSynthesizer availableVoices]);
で使える声の一覧を確認できます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>
#import <Appkit/Appkit.h>

int main(int argc, char** argv)
{
  NSAutoreleasePool* pool = [NSAutoreleasePool new];
  
  NSSpeechSynthesizer* s = [[[NSSpeechSynthesizer alloc] initWithVoice:@"com.apple.speech.synthesis.voice.Alex"] autorelease];
  [s startSpeakingString:@"Hello, world!"];
  while ([s isSpeaking]) ;
  [s startSpeakingString:@"con nitch were"];
  while ([s isSpeaking]) ;
  [s startSpeakingString:@"daw cat coo org, sole what program mar know tum yen know Colosseum death"];
  while ([s isSpeaking]) ;
  
  [pool release];
  return 0;
}

既存ライブラリを用いて実現してみました。 アルファベットでHelloWolrdだと読み上げなかったので仕方なく…

1
2
3
4
5
6
7
8
9
class VoiceHelloWorld
{
    [System.Runtime.InteropServices.DllImport("AquesTalkDa.dll")]
    private static extern int AquesTalkDa_PlaySync(string voice,int speed);
    static void Main(string[] args)
    {
        AquesTalkDa_PlaySync("はろーわーるど", 100);
    }
}

個人的にはnipotanと言う発音は神がかってると思う。

1
2
3
4
5
6
7
function say(msg) {
  var sprk = new ActiveXObject("SAPI.SpVoice");
  sprk.Speak(msg);
}

say("Hello world");
say("nipotan");

MacOSXのsayコマンドに文字列を渡しています。
(say "Hello, world!")
1
2
3
(def say (mesg)
 (system (+ "say " mesg "&")) 
 mesg)

 FreeTTSを使って実装してみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.beans.PropertyVetoException;
import java.util.Locale
import javax.speech.AudioException
import javax.speech.Central
import javax.speech.EngineException
import javax.speech.EngineStateError
import javax.speech.synthesis.Synthesizer
import javax.speech.synthesis.SynthesizerModeDesc
import javax.speech.synthesis.Voice

class HelloWorld {
    private    var    s:Synthesizer = null
    val    d:SynthesizerModeDesc = new SynthesizerModeDesc(null, "general", Locale.US, null, null)
    var    v:Voice = null
    s = Central.createSynthesizer(d)
    if (s == null) {
        Console.println("cannot create synthesizer.")
        System.exit(1)
    }
    v = new Voice("kevin16", Voice.GENDER_DONT_CARE, Voice.AGE_DONT_CARE, null)
    if (v == null) {
        Console.println("cannot create voice.")
        System.exit(1)
    }
    s.allocate
    s.resume
    s.getSynthesizerProperties().setVoice(v)
    
    def sayHello(m:String):Unit = {
        s.speakPlainText(m,null)
        s.waitEngineState(Synthesizer.QUEUE_EMPTY)
    }
    
    def deallocateSynthesizer:Unit = s.deallocate
}

object HelloWorldMain {
    def main(args:Array[String]):Unit = {
        try {
            val    h:HelloWorld = new HelloWorld
            args.size match {
                case    0 => h.sayHello("Hello World")
                case    _ => h.sayHello(args(0))
            }
            h.deallocateSynthesizer
        } catch {
            case e:Exception => {
                e.printStackTrace
                System.exit(1)
            }
        }
    }
}

uwscのマニュアルには「要SAPI 5.x」とあります。
コントロールパネルに「音声認識」があれば使えるようです。
1
2
3
SPEAK("Hello, world!")
SPEAK("こんにちは")
SPEAK("どう書くorg、それはプログラマの為のコロシアムです。")

LLVMアセンブリ(32bit)で。Mac OS Xのsayコマンドにコマンドライン引数を渡してます。いいのかこんなんで…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
; Usage: ./say message

declare i32 @strlen(i8*)
declare i32 @sprintf(i8*, i8*, ...)
declare i32 @system(i8*)

@cmd = internal constant [9 x i8] c"say \22%s\22\00"

define i32 @main(i32 %argc, i8** %argv) {
    %cmdp = getelementptr [9 x i8]* @cmd, i32 0, i32 0
    %argv_addr = ptrtoint i8** %argv to i32
    %msg_addr = add i32 %argv_addr, 4
    %msgp = inttoptr i32 %msg_addr to i8**
    %msg = load i8** %msgp
    %slen = call i32 @strlen(i8* %msg)
    %len = add i32 %slen, 9
    %s = malloc i8, i32 %len
    call i32 (i8*, i8*, ...)* @sprintf(i8* %s, i8* %cmdp, i8* %msg)
    call i32 @system(i8* %s)
    ret i32 0
}

Index

Feed

Other

Link

Pathtraq

loading...