アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

ssml-builderでらくらくセリフ実装

morishitaです。

Alexa スキル「いこーよのおでかけナビ」で VirtualAlexa の次に使っているライブラリ ssml-builderを紹介します。

tech.actindi.net

ssml-builder ってなに?

Alexa スキルの開発では、スキルに喋らせるセリフの組み立てをする必要があります。 強調したいこのセルフの前にちょっと間を取りたいなとか、元気良さを表現するために 声を大きく、少し早口で喋らせたいという場合があります。

そのような発声の細かな調整は Speech Synthesis Markup Language (SSML) というマークアップを利用すれば可能です。 Alexa で使える SSML は音声合成マークアップ言語(SSML)のリファレンスに書かています。

SSML は XML ベースのマークアップ言語です。 開始タグと終了タグの対応を間違ったり、属性のクオートを閉じ忘れたりするだけで実行時エラーが発生します。 しかも、コード内ではSSML タグを文字列として記述するので IDE 等の構文チェックもなく実装中にこれらのミスに気づきにくいのです。

ssml-builder は SSMLのマークアップの実装をサポートするためのライブラリで、SSML を利用したセリフをメソッドチェインで実装できます。 うっかり実装してしまう SSML のシンタックスミスがなくなるので上記のような問題を軽減してくれます。

使い方

インストール

npm モジュールなのでインストールは次のコマンドを実行するだけです。

$ npm install ssml-builder -S

セリフの組み立て

音声合成マークアップ言語(SSML)のリファレンスにかかれているタグに対応する形でメソッドが定義されています。 Alexa 向け SSML の組み立てでは機能的に網羅されています1

基本的な使い方

import * as Speech from "ssml-builder";

const speech = new Speech()
  .say("最初のセリフ")
  .say("それに続くセリフ")
  .ssml();

最初にnew Speech()で ssml-builder のインスタンスを生成して、 sayメソッドで喋らせたいセリフを設定していきます。 最後にssmlメソッドを実行すると SSMLでマークアップされた文字列が生成されます。

上記のコードを実行すると変数 speechの値は '<speak>最初のセリフ それに続くセリフ</speak>'となります。

ちょっと応用的な使い方

次のコードは「いこーよのおでかけナビ」を初めて使ったときのスキルのセリフの実装です。

import * as Speech from "ssml-builder";

const speech = new Speech()
  .prosody({ rate: "fast" }, "はじめまして!")
  .say("いこーよのおでかけナビです。")
  .say(
    "週末に自宅から車でお出かけする、いちにち楽しめるスポット探しをお手伝いします。"
  )
  .pause("1s")
  .say(
    "途中でわからなくなったらいつでも「使い方を教えて」と言ってください。使い方をご案内します。"
  )
  .say("終了する場合は「バイバイ」と言ってください。")
  .pause("1s")
  .say("それでは早速、お出かけ先を探していきましょう。")
  .say("どこからお出かけしますか?最初にお住まいの郵便番号を教えてください。")
  .sayWithSSML(zipCodeInstructions())
  .ssml();

function zipCodeInstructions(): string {
  return new Speech()
    .say("「郵便番号は")
    .sayAs({ word: "141", interpret: "digits" })
    .say("の")
    .sayAs({ word: "0033", interpret: "digits" })
    .say(
      "」という様にまえ三桁とうしろ四桁を「の」で区切って話しかけてください。"
    )
    .ssml(true);
}

prosodyメソッドはSSMLの<prosody>タグに対応したメソッドで、音量・高さ・速さを指定できます。ここでは{rate: 'fast'}を指定して、少し早口にすることで元気良さを演出しています。

pauseメソッドは<break>タグに対応していて、ここでは'1s'と次のセリフとの間に1秒の間を入れています。

sayWithSSMLメソッドは引数に SSML の断片を取るメソッドです。zipCodeInstructions関数で作る SSML の断片を受け取っています。 注意しないと行けないのは、<speak>を含んでいる文字列をsayWithSSMLメソッドの引数に入れてはいけないということです。
引数が<speak>を含んでいると最終的に出力するSSMLで<speak>がルートタグとして付加されるので<speak>タグの入れ子が発生します。 そのようなセリフはAlexaに読ませたときにランタイムエラーが発生します。

zipCodeInstructions関数はユーザに郵便番号の話し方をお知らせするセリフを組み立てています。 sayAsメソッドは<say-as>タグに対応しており、喋らせる文字列をどう解釈すべきかを指示できます。
ここではdigitsを指定し、141を「ひゃくよんじゅうよん」ではなく「いちよんいち」と数字ごとに別々に発音させています。 最後のssmlメソッドには引数trueを与えています。これにより SSML のルートタグである<speak>が出力されません。
これは先に説明したsayWithSSMLメソッドの引数にするためです。

ちょっと便利なメソッド

sayRandomChoiceという便利なメソッドが実装されています。
これはその名の通り、文字列の配列を渡すとその中の 1 つをランダムに選択するメソッドです。

「いこーよのおでかけナビ」では例えば次のように使っています。

const speech = new Speech()
  .sayRandomChoice(["ごめんなさい", "すみません"])
  .say("よくわかりませんでした。探したいお出かけ先のジャンルを教えてください。")
  .ssml();

同意語をランダムに使い分けることで表現の揺れを発生させて、人間らしさを演出するのに使っています。

まとめ

SSML を効果的に使えば、次の効果を期待できます。

  • 発声の速さや強さを調節するとユーザに意図を伝えやすくなる
  • より聞き取りやすくなり、重要な部分を強調できる
  • 発声の演出することにより、親しみを感じてもらえる

SSML を組み立てるのに ssml-builder を利用すると間違いを減らし効率的に実装できます。

最後に

アクトインディではエンジニアを募集しています。

actindi.net


  1. Google Home のアクション開発でも SSML が利用できるのですが、Google が独自拡張している部分(<media>,<par>,<seq>)には対応していないようです。