kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

kindでお手軽Kubernetesマルチクラスタを試す

f:id:kun432:20210901161635p:plain

Kubernetesを触るようになってそこそこ経つんだけど、未だにわからないことのほうが圧倒的に多い。体系的にちゃんと学習する必要があるなぁということで、これまで辞書的にしか使ってこなかったこれを改めて読んでます。

で、色々試すに当たり、ローカル環境でかんたんにマルチクラスタができるものがよいなぁということで、kindを触ってみました。

kind.sigs.k8s.io

詳しく知りたい方はQiitaあたりにたくさん記事があるのでそちらを見てください。あくまでも自分用メモです。

目次

インストール

前提として、Dockerが必要。Macの場合はDocker Desktopをインストールしておいて、Homebrewでかんたんにインストールできる。dockerもHomebrewでいいかもね。

$ brew install kind kubectl
$ kind version
kind v0.11.1 go1.16.4 darwin/amd64

クラスタの作成(シングル)

かんたんにクラスタを作成して起動するだけならkind create clusterでOK。

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/

サクッとクラスタが作られます。初回だけnodeのイメージを取得するのに時間が少しかかるみたい。

デフォルトだとクラスタ名はkindになります。

$ kind get clusters
kind

kubeconfig的にはkind-kindになってますね。というか普通にkubectlが使えるようになります。

$ kubectl config get-contexts
CURRENT   NAME        CLUSTER     AUTHINFO    NAMESPACE
*         kind-kind   kind-kind   kind-kind

nodeを見てみる。デフォルトだとcontrol-plane/worker兼用の1台だけっぽい。

$ kubectl get node -o wide
NAME                 STATUS   ROLES                  AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION     CONTAINER-RUNTIME
kind-control-plane   Ready    control-plane,master   8m37s   v1.21.1   172.18.0.2    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2

podはこんな感じ。

$ kubectl get pod --all-namespaces -o wide
NAMESPACE            NAME                                         READY   STATUS    RESTARTS   AGE   IP           NODE                 NOMINATED NODE   READINESS GATES
kube-system          coredns-558bd4d5db-6w67p                     1/1     Running   0          11h   10.244.0.2   kind-control-plane   <none>           <none>
kube-system          coredns-558bd4d5db-thh9z                     1/1     Running   0          11h   10.244.0.4   kind-control-plane   <none>           <none>
kube-system          etcd-kind-control-plane                      1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
kube-system          kindnet-z78fn                                1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
kube-system          kube-apiserver-kind-control-plane            1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
kube-system          kube-controller-manager-kind-control-plane   1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
kube-system          kube-proxy-k62zn                             1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
kube-system          kube-scheduler-kind-control-plane            1/1     Running   0          11h   172.18.0.2   kind-control-plane   <none>           <none>
local-path-storage   local-path-provisioner-547f784dff-5nhpt      1/1     Running   0          11h   10.244.0.3   kind-control-plane   <none>

試しにnginxのpodをあげてみます。

$ kubectl run nginx --image=nginx
$ kubectl get pod  -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP           NODE                 NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          2m23s   10.244.0.5   kind-control-plane   <none>           <none>
$ kubectl exec -ti nginx -- bash
root@nginx:/# 

普通に使えてますね。

クラスタの削除

削除する場合はkind delete clusterで、サクッと消えます。

$ kind delete cluster
Deleting cluster "kind" ...

クラスタの作成(マルチnode)

kindの最大の特徴はマルチcontrol-plane/マルチworkerが作れること。control-plane3台、worker3台だと、こんな感じのマニフェストになります。ちなみに、nameでクラスタ名の指定ができます。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: control-plane
- role: control-plane
- role: worker
- role: worker
- role: worker

クラスタ作成時に--configでこのマニフェストを指定します。

$ kind create cluster --config multi.yaml
Creating cluster "sample" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦 📦 📦
 ✓ Configuring the external load balancer ⚖️
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
 ✓ Joining more control-plane nodes 🎮
 ✓ Joining worker nodes 🚜
Set kubectl context to "kind-sample"
You can now use your cluster with:

kubectl cluster-info --context kind-sample

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/

nodeを見てみるとちゃんと複数立ってますね。

$ kubectl get node -o wide
NAME                    STATUS   ROLES                  AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION     CONTAINER-RUNTIME
sample-control-plane    Ready    control-plane,master   8m55s   v1.21.1   172.18.0.7    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2
sample-control-plane2   Ready    control-plane,master   8m12s   v1.21.1   172.18.0.6    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2
sample-control-plane3   Ready    control-plane,master   7m18s   v1.21.1   172.18.0.8    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2
sample-worker           Ready    <none>                 7m      v1.21.1   172.18.0.3    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2
sample-worker2          Ready    <none>                 6m59s   v1.21.1   172.18.0.4    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2
sample-worker3          Ready    <none>                 6m59s   v1.21.1   172.18.0.5    <none>        Ubuntu 21.04   5.10.25-linuxkit   containerd://1.5.2

dockerコマンドで見てみると、haproxyも一つ立っているのがわかる。

$ docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED         STATUS         PORTS                       NAMES
a9cd9e1dfecc   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes                               sample-worker
a319976c1294   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes   127.0.0.1:49999->6443/tcp   sample-control-plane
06382bd1fe9d   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes                               sample-worker2
41c70051f965   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes   127.0.0.1:50002->6443/tcp   sample-control-plane3
481c1f7a9f47   kindest/haproxy:v20200708-548e36db   "/docker-entrypoint.…"   9 minutes ago   Up 9 minutes   127.0.0.1:50000->6443/tcp   sample-external-load-balancer
5cea2faf1a7c   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes                               sample-worker3
9d73f13cf90c   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   9 minutes ago   Up 9 minutes   127.0.0.1:50001->6443/tcp   sample-control-plane2

こいつがapiserverへのLoad Balancerになっている。

$ docker exec -ti sample-external-load-balancer sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 haproxy -sf 8 -W -db -f /usr/local/etc/haproxy/haproxy.cfg
   32 root      0:05 haproxy -sf 8 -W -db -f /usr/local/etc/haproxy/haproxy.cfg
   36 root      0:00 sh
   44 root      0:00 ps

/ # cat /usr/local/etc/haproxy/haproxy.cfg
(snip)
frontend control-plane
  bind *:6443

  default_backend kube-apiservers

backend kube-apiservers
  option httpchk GET /healthz
  # TODO: we should be verifying (!)

  server sample-control-plane sample-control-plane:6443 check check-ssl verify none resolvers docker resolve-prefer ipv4
  server sample-control-plane2 sample-control-plane2:6443 check check-ssl verify none resolvers docker resolve-prefer ipv4
  server sample-control-plane3 sample-control-plane3:6443 check check-ssl verify none resolvers docker resolve-prefer ipv4
/ #

マルチnodeなので、deploymentを立ててみる。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: nginx
          image: nginx:1.19.4
          ports:
            - containerPort: 80

複数のnodeで起動していることがわかる。

$ kubectl get pod -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP           NODE             NOMINATED NODE   READINESS GATES
my-app-58b49559b8-bwm8z   1/1     Running   0          43s   10.244.4.2   sample-worker3   <none>           <none>
my-app-58b49559b8-jdntf   1/1     Running   0          43s   10.244.3.2   sample-worker    <none>           <none>
my-app-58b49559b8-vwgjc   1/1     Running   0          43s   10.244.5.2   sample-worker2   <none>           <none>

ローカルPCからクラスタ内podへのネットワークアクセス

kindで作成したクラスタはdockerで構成されているので、そのままではpodへネットワークアクセスできない。extraPortmappingsを使う。

まずは1 control-plain/1 workerで試してみる。クラスタ作成用マニフェストをこんな感じで作成する。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample2
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    protocol: TCP

クラスタ作成

$ kind create cluster --config sample2.yaml

podとserviceはこんな感じで。

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  containers:
  - name: nginx
    image: nginx:1.19.2
    ports:
      - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30000

curlでアクセスしてみる

$ curl localhost:30000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

dockerでみるとworkerのpodへport-forwardされていることがわかる。

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
bf8df82909a2   kindest/node:v1.21.1   "/usr/local/bin/entr…"   2 minutes ago   Up 2 minutes   0.0.0.0:30000->30000/tcp    sample2-worker
4756002bd30d   kindest/node:v1.21.1   "/usr/local/bin/entr…"   2 minutes ago   Up 2 minutes   127.0.0.1:53630->6443/tcp   sample2-control-plane

うーん、nodeportの場合、マルチクラスタで同じことをやる場合にはちょっとひねりが必要な感じ。

続きは次回。

Voiceflowでテスト共有が記録できる「Transcript」がリリースされました

f:id:kun432:20210313005910p:plain

昨年末ぐらいから、Voiceflowのテスト機能が大幅に強化されています。優れた会話設計は机上の設計だけで完結することはほとんどありません。実際に音声でテストを行ってみて、その結果をフィードバックして、さらに改善を繰り返していくことが必要になります。

Voiceflowにはビルトインのテストツールが用意されており、作成した会話フローをその場ですぐにテストすることができ、さらに共有可能なURLを発行して遠く離れたユーザやクライアントにも実際に体験してもらうことが可能になっています。ただし、そのフィードバックは、(例えばメールやチャットなど)Voiceflowの外で行う必要がありました。

今回リリースされた「Transcript」機能を使うと、ユーザやクライアントに行ってもらったテスト結果を、開発者やデザイナーがVoiceflowのインタフェースの中で直接確認することができます。

www.voiceflow.com

では早速見てみましょう。

目次

前提条件

  • 現状はカスタムアシスタントプロジェクトのみと思います。
    • Voiceflowのテストツールは、Alexa/Google向けプロジェクトのビルトインのスロットタイプを認識できません(正しいスロット値を入力しても認識されない。)カスタムアシスタントプロジェクトでのみ利用可能な機能と考えてください。
  • 現状はベータリリースです。
    • ただし、全てのユーザが利用可能です。

デモはよくあるコーヒーショップの注文をするやつをカスタムアシスタントプロジェクトで作成してます。

f:id:kun432:20210825012341p:plain

インテントやスロットもごくごくシンプルです。

f:id:kun432:20210825012442p:plain

f:id:kun432:20210825012611p:plain

以下にインポート可能なカスタムアシスタントプロジェクトを用意してますので、お試しいただけます。(右クリックでダウンロードした.vfファイルをVoiceflowでインポートしてください。)

https://raw.githubusercontent.com/kun432/voiceflow-line/main/coffeeshop.vf

準備

Transcriptは、キャンバス画面左の4つのアイコンの上から2番めのアイコンになります。これをクリックします。

f:id:kun432:20210825015308j:plain

おっと、うまくいきません・・・

f:id:kun432:20210825015446p:plain

Trasnscriptは、いきなり使えるわけではなく、

  • 一度テストを実行してトレーニングを行う
  • テストの共有を行って、テストを行ってもらう

という事前準備が必要になります。まずは、テスト画面を開いてテストとトレーニングを行いましょう。"Go to Test"をクリックします。

f:id:kun432:20210825015801j:plain

テスト画面が開いたら、右の方にある"Train Assistant"をクリックして、会話モデルのトレーニングを行います。

f:id:kun432:20210825020204j:plain

数分程度、トレーニングが行われます。

f:id:kun432:20210825020300p:plain

Trainedが100%になればトレーニングが完了しています。テストしてみましょう。下の方にある”Run Test”をクリックします。

f:id:kun432:20210825020429j:plain

会話のテストを行います。ちゃんと動いてますね。

f:id:kun432:20210825020654p:plain

次にテストを共有しましょう。右上の”Share Prototype”をクリックします。

f:id:kun432:20210825021052j:plain

テストのタイプは今回は"Chat Input"を使います。その他はデフォルトで。"Copy Link"をクリックすると、共有可能なテスト用URLがクリップボードにコピーされます。

f:id:kun432:20210825023620j:plain

本来はこのURLを別の人に送ってテストしてもらうことになりますが、今回は擬似的に一人でやります。Chromeのシークレットウインドウを別に開いて、さきほどのURLにアクセスします。以下のような画面が表示されたら、"Start Conversation"をクリックします。

f:id:kun432:20210825023714j:plain

テストします。

f:id:kun432:20210825023816j:plain

ちゃんと動いてますね。

Transcriptを使ってみる

では、Transcriptを見てみましょう。元のウインドウに戻って、キャンバス画面からTranscriptのアイコンをクリックします。

f:id:kun432:20210825015308j:plain

さきほど行ったテストが記録されているのがわかりますね。

f:id:kun432:20210825024225p:plain

このテストに、次にやるべきアクションを記録しておいたり、

f:id:kun432:20210825024725p:plain

タグを付けたり、

f:id:kun432:20210825024813p:plain

メモを残しておいたりできます。

f:id:kun432:20210825024844p:plain

また、テストを行ったユーザや時間なども記録されています。Voiceflowにログインできるユーザでテストするとユーザ名もちゃんと表示されるようですね。

f:id:kun432:20210825025307j:plain

"Add filters"をクリックすると、特定の期間や特定のタグでフィルタを掛けることもできます。

f:id:kun432:20210825025409j:plain

ダイナミックなサンプル発話の追加

Transcriptで一番凄い機能をご紹介しましょう。

会話のやり取りの一番上の三点リーダーをクリックすると、インテントの信頼率とデバッグメッセージを表示することができます。

f:id:kun432:20210825030113j:plain

こんな感じで、デバッグメッセージやインテントの信頼度が表示されます。そして、インテントにマッチしないような発話の場合、"No Match"と表示され、"Add utterance to Intent"をクリックすると・・・

f:id:kun432:20210827020825j:plain

その場でインテントのサンプル発話として追加できます!

f:id:kun432:20210827020949p:plain

もちろん新しいインテントを作成してサンプル発話として追加することも可能です!

f:id:kun432:20210827021041p:plain

f:id:kun432:20210827021144p:plain

f:id:kun432:20210827021226j:plain

ユーザからのフィードバックを直接反映することができてとても便利ですね!これは正直すごいなと思います。

既存のインテントへのサンプル発話の追加はこれだけでOKです。新しいインテントとして登録する場合は、会話フローに直接反映されるわけではありませんので、フローの修正が必要です。ここは、フロー内の分岐として登録する(Choice Step)か、グローバルなインテントとして登録する(Commandなど)か、などコンテキストによって判断が必要なので、開発者・デザイナーが判断するということですね。

f:id:kun432:20210827022302j:plain

まとめ

Voiceflowのテストツールは、ユーザの実際の体験に近い形でのテストができ、しかもそれを気軽に共有できます。ここに今回のTranscript機能が追加されたことにより、ユーザのテスト結果の集約とより迅速なフィードバックも可能になったのといえるのではないでしょうか?音声アプリではこのフィードバックループを効率的・継続的に行う事が重要なので、非常に良いアップデートだと思います。

あえて要望を出すならば、

  • 集まったテスト結果を何らかの統計的なデータとして見れると良さそう。
  • インテントだけじゃなく、スロット値もダイナミックに反映できると嬉しい。
  • AlexaやGoogleでもできると最高!

あたりでしょうか。この辺は今後の改善にも期待ですね!

#VUIchallenge #021 - Stories & SSML

f:id:kun432:20210711001052p:plain

#VUIchallengeの第21回です。テーマは「Stories & SSML」。

過去の#VUIchallengeの記事はこちら

お題

The challenge

Using TTS voice only, what SSML tags would you use for an interactive story? Create and test some examples on the developer console or with any online SSML editor.

Jesús' Tips

Warning! Some tags might not sound as you expect and your expectations might not be met. Test them carefully and make sure they make the effect you need for your story.

DeepLによる日本語訳

課題

TTS音声のみを使用して、インタラクティブなストーリーにどのようなSSMLタグを使用しますか?開発者コンソールやオンラインのSSMLエディタで例を作ってテストしてみてください。

Jesúsのヒント

注意!いくつかのタグは、あなたが期待しているようには聞こえないかもしれませんし、あなたの期待に応えられないかもしれません。慎重にテストして、あなたのストーリーに必要な効果があるかどうか確認してください。

前提

今回はSSMLですね。SSMLも過去のチャレンジで何度か出てきています。

過去のチャレンジのときは、人間が聞き取りやすくする、もしくはあえて聞き取りにくくすることでゲーム性を高める、ということで、あくまでも「話し方」を調節するものでした。

今回は「インタラクティブなストーリー」で、というところがミソです。つまり、端的に言ってしまうと、ストーリーに「臨場感」などの効果を与えるための使い方ということですね。

以前に「インタラクティブな物語スキルをVoiceflowで作ろう」という、まさに同じテーマでハンズオンを行ったことがあります。

今回は上記のハンズオンで使った、物語づくりにおけるSSMLの使い方をいくつかご紹介したいと思います。

ハンズオンの資料は以下で公開しています。Voiceflowの画面構成が変わってしまっていますので、読み替えて頂く必要があります。 また、ハンズオンの中でサンプルプロジェクトへのリンクも用意しています。Voiceflowアカウントをお持ちの方は、インポートしてAlexa開発者コンソールにアップロードして、実際に動かしていただくことが可能です。(ちょっと古いですが、多分今でも動くはず・・・) 上記以外にロングバージョンのサンプルもあります。ハンズオン資料の一番最後の付録をごらんください。

デザイン

キャラクターの会話

物語の中には複数のキャラクターが登場するものがあります。上記のハンズオンでは「シンデレラ」をテーマにしていました。シンデレラだと以下のようなキャラクターが登場します。

  • ナレーター
  • シンデレラ
  • まま母
  • いじわるなお姉さん その1
  • いじわるなお姉さん その2
  • 魔法使いのおばあさん
  • 王子様

こういったキャラクターが複数存在する場合、同じ声の種類だけだと誰が喋っているのかわかりにくくなります。SSMLを使うと声質・発話の音程・発話のスピードを変えることができるので、キャラクターごとに設定を変えることで複数の異なるキャラクターが喋っているような感じが再現できます。

f:id:kun432:20210816010544p:plain

ここで使っているタグは、voiceタグ、prosodyタグの2種類です。いくつか見てみましょう。

SSMLは、プラットフォームによって実装が異なります。どのプラットフォームでも同じように使えるもの、複数のプラットフォームで使えるが微妙にパラメータなどが異なるもの、特定のプラットフォームでしか使えないもの、などがあります。このあたりの違いについては各プラットフォームのSSMLリファレンスを参照してください。今回は、AlexaのSSMLを使います。

voiceタグ

Alexaのみになりますが、Voiceタグを使うと、Amazon Pollyで用意されている声質に変えることができます。言語ごとに複数の声質が用意されており、日本語の場合は、"Mizuki"(女性の声)、"Takumi"(男性の声)が利用可能です。

まず、voiceタグを指定しない場合は標準の"Alexa"の声になります。ナレーター的なものは標準の声を使うと良いでしょう。

<speak>
    むかしむかし、あるところにシンデレラというとても美しくて優しい娘がいました。
</speak>

ではvoiceタグを使ってみましょう。女性の声を継母の声にしてみました。

<speak>
    <voice name="Mizuki">シンデレラ、あんたも仕事が終われば、舞踏会に来てもいいわ、ただそのボロボロの服でも来れるなら、だけどね。あっはっは。</voice>
</speak>

次は男性の声。こちらは王子様の声にしてます。

<speak>
    <voice name="Takumi">美しいお嬢さん、ぼくと、踊っていただけませんか?</voice>
</speak>

voiceタグについてのドキュメントはこちら。

ちなみに、langタグと組み合わせると、日本語以外の言語も発話させることができます。

prosodyタグ

以前のチャレンジでもやりましたね。prosodyタグを使うと、発話のスピードを変えることができますが、それだけでなく、発話の音程を変えることができます。pitchで音程、rateでスピードを指定できます。

いじわるなお姉さんその1は、声が高くて早口な感じ。

<speak>
    <voice name="Mizuki"><prosody pitch="x-high" rate="fast">シンデレラ、さっさと掃除を終わらせなさいよ。</prosody></voice>
</speak>

いじわるなお姉さんその2は、声が低くてゆっくりしゃべる感じです。

<speak>
    <voice name="Mizuki"><prosody pitch="x-low" rate="slow">シンデレラ、掃除が終わったら次は洗濯よ、怠けないでやるのよ。</prosody></voice>
</speak>

こんな感じで、voiceタグとprosodyタグを組み合わせると、複数のキャラクターの声を使い分けることができます。ついでに、シンデレラと魔法使いのおばあさんの声はこんな感じです。

シンデレラ

<speak>
    <prosody pitch="x-high">ああ、わたしも舞踏会に行きたいわ。でもこんなボロボロの服じゃとても王子さまに、お会いできないわ、しくしく</prosody>
</speak>

魔法使いのおばあさん

<speak>
    <prosody pitch="x-low" rate="slow">泣くのはおよし、シンデレラ。お前はいつも仕事をがんばるとても良い子じゃ、ごほうびにわたしが魔法で舞踏会へ行かせてあげるとしよう。</prosody>
</speak>

ただ、聞いてもらうと分かる通り、少しイントネーションがおかしかったり、いかにも合成音声な感じがしますよね。発話の内容や句読点の位置などによっても変わってきますが、SSML、特にprosodyタグを使うとこういうことが起きやすい気がします。なので、必ず実際に発話させてみて確認したほうがよいですね。

prosodyタグのドキュメントはこちら。

効果音

効果音を入れると楽しい感じにできるというのも以前ご紹介しました。

これは物語スキルでももちろん有効です。効果音や音楽などのサウンドを使う場合はaudioタグを使います。

<speak>
    <prosody pitch="x-low" rate="slow">ちちんぷいぷい、えいっ。</prosody>
    そういって、魔法使いのおばあさんは魔法の杖を一振りしました。<audio src="soundbank://soundlibrary/magic_spells/magic_spells_07\"/>
</speak>

魔法をかけた音を出してみました。音が出るとちょっと物語っぽい感じがして楽しいですよね。

ちなみに、上の例では、Alexa Skills Kitサウンドライブラリを使っています。Alexaスキルでのみ利用可能ですが、約2500以上の音源を自由に使うことができます。

もちろん自分で用意したオーディオファイルを使うこともできます。その場合はインターネット上でアクセスできる場所にファイルを配置しておく必要があります。

audioタグのドキュメントはこちら。

BGM

audioタグを使った効果音はとても良いのですが、BGM的に「音楽と発話を同時に行う」ということができません。SSMLのaudioタグでは再生するオーディオファイルを指定することしかできないので、SSMLだけでこれを実現するには、BGMの上に発話の載せたオーディオファイルを自分で用意しておいて再生させる必要があります。ただ、そうなると、ユーザの発話をもとに動的に発話内容を変えるというようなことをやる場合、そのパターンの数だけ、オーディオファイルを予め用意しておく必要があります。

Alexaの場合、APL for Audioを使うと、ユーザの対話に応じて発話内容を変えつつBGMも同時に鳴らすということをコードで記述できます。SSMLだけでは実現できないのでコードは割愛します。実際に動かしてみたサンプルはこちらです。

SHW様のフリー音源を使用させていただきました。

ちなみに、Googleの場合は、SSMLだけでできます。mediaタグとparタグを使います。

</speak>
日本昔ばなしスキルです。今日のお話は、桃太郎。<break time="1s"/>
<par>
  <media xml:id="bgm" end="obaasan_story.end+2s" fadeOutDur="2s">
    <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/momotaro.mp3" 
     soundLevel="-20dB"/>
  </media>
  <media xml:id="intro" begin="0.7s">
    <speak>むかーしむかし、あるところに、おじいさんとおばあさんが住んでいました。毎日、</speak>
  </media>
  <media xml:id="ojiisan_story" begin="intro.end+0.2s">
    <speak>おじいさんは、山に芝刈りに、</speak>
  </media>
  <media xml:id="ojiisan_sound" begin="ojiisan_story.end-0.5s">
    <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/shibakari.mp3"   
     repeatCount="2"/>
  </media>
  <media xml:id="obaasan_story" begin="ojiisan_story.end+0.5s">
    <speak>おばあさんは、川に洗濯に、いっていました。</speak>
  </media>
  <media xml:id="obaasan_sound" begin="obaasan_story.end-2.0s" fadeOutDur="2s">
    <audio src="https://dl.dropboxusercontent.com/s/xxxxxxxxxx/sentaku.mp3" clipEnd="3s"/>
  </media>
</par>
</speak>

発話とオーディオの同時再生やAPL for Audioについては、過去の記事でも記載しています。よろしければそちらをご覧ください。

まとめ

インタクティブなストーリーを作る場合、いかにその物語に没入できるか、というところはとても重要です。

現状の音声アプリはどちらかというと、ユーザのアシスタントとして、音声で命令を受けてタスクをこなす、という前提が強いと思っています。そのため、ストーリーテリングのような表現力が求められるものは正直厳しい面がありますが、音声によるインタラクティブ性はとても間口の広いものだと思いますし、SSMLやオーディオファイルを使うことでカバーできる部分もあると思います。いろいろ工夫すれば楽しいものができるのではないでしょうか。

ただし、ヒントにもあるように、ここは実際に聞いてみないとわからない部分が大きいので、その点には注意が必要ですね。