kun432's blog

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

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

APL for Audioで音声にあわせてオーディオの再生時間を自動で調整する①

概要

APL for Audioで、発話の長さに合わせてオーディオの再生時間が自動で調整されるという地味に嬉しい機能が追加されました。

これ何が嬉しいのか?実際にやってみましょう。

これまでのAPL for Audioのめんどくさかったところ

APL for Audioの一番わかり安い例として、BGMにあわせてAlexaに発話させるというのがあります。以下のサンプルコードを見てください。

{
  "type": "Alexa.Presentation.APLA.RenderDocument",
  "token": "token",
  "document": {
    "type": "APLA",
    "version": "0.91",
    "description": "APL for Audioのサンプルです。",
    "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "item": {
        "type": "Mixer",
        "items": [
          {
            "type": "Speech",
            "contentType": "SSML",
            "content": "<speak><break time=\"2s\"/><voice name=\"Takumi\">はじめまして。僕の名前は ${payload.user.name}。これからお供の動物たちと一緒に鬼ヶ島に鬼を退治に行く途中なんだ。応援してね</voice><break time=\"1s\"/>おしまい。次回をお楽しみに。</speak>"
          },
          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1"
          }
        ]
      }
    }
  }
}

"type": "Mixer"を使うと配下のアイテム(speechやaudio)が並列で再生されます。サンプルではitemsに2つの要素が指定してあります。"type":"Speech"がAlexaの発話、"type":"Audio"がオーディオファイルの再生です。音楽に乗せてAlexaが話してくれるわけですね。

早速試してみましょう。Alexa開発者コンソールのどのスキルでもいいので、一度開いて、メニューにある「マルチモーダル」をクリックします。

マルチモーダル画面が開いたら、"Create Audio Response"をクリックします。2つボタンがありますが、どちらでもよいです。

APL for Audioのエディタが開きます。

左のメニューで"APLA"が選択されていることを確認して・・・

サンプルコードですべて上書きします。

次に、左のメニューから"DATA"をクリックします。

こちらに以下のコードを全部上書きしてください。

{
    "user": {
        "name": "桃太郎"
    }
}

できたら下にある"Preview"ボタンをクリックします。

APLのコードが読み込まれ、波形が表示されているのがわかりますね。再生ボタンを押してみてください。

Alexaの発話と音楽が同時に流れたと思います。が、Alexaの発話が終わっても音楽が延々と流れていますよね?

今回のオーディオファイルはちょうど4分あります。でもAlexaの発話はせいぜい20秒程度です。ちょっとオーディオが長すぎですよね。

これに対応するには以下の2つの方法があります。

  • オーディオファイルをAlexaの発話時間に合わせて短くしたものに差し替える
  • APL for Audioのフィルタを使ってオーディオの再生時間を制御する

1つ目はオーディオファイルそのものを時間を短くするやり方です。2つ目はAPL for Audioのフィルタ機能を使うやり方です。後者のほうがお手軽なのでやってみましょう。以下のコードで全部上書きしてください。

{
  "type": "Alexa.Presentation.APLA.RenderDocument",
  "token": "token",
  "document": {
    "type": "APLA",
    "version": "0.91",
    "description": "APL for Audioのサンプルです。",
    "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "item": {
        "type": "Mixer",
        "items": [
          {
            "type": "Speech",
            "contentType": "SSML",
            "content": "<speak><break time=\"2s\"/><voice name=\"Takumi\">はじめまして。僕の名前は ${payload.user.name}。これからお供の動物たちと一緒に鬼ヶ島に鬼を退治に行く途中なんだ。応援してね</voice><break time=\"1s\"/>おしまい。次回をお楽しみに。</speak>"
          },
          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1",
            "filter": [
              {
                "type": "Trim",
                "end": 20000
              },
              {
                "type": "FadeOut",
                "duration": 3000
              }
            ]
          }
        ]
      }
    }
  }
}

上書きしたら、左下の再読み込みボタンをクリックします。

そして再生してみます。

変更したのは以下のところです。

          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1",  // 最後にカンマを追加
            "filter": [      // 以下を追加
              {
                "type": "Trim",
                "end": 20000
              },
              {
                "type": "FadeOut",
                "duration": 3000
              }
            ]      // ここまで
          }

"source"で指定されているオーディオファイルのURLの下にfilterというのが追加されています。あと、URL行の末尾にカンマが追加されていることにも注意してください。

filterを使うとオーディオの再生を調整できます。設定しているのは以下です。

  • "Trim"を使って、オーディオファイルの再生時間を20秒に指定
  • "FadeOut"を使って、オーディオファイルの最後3秒間でフェードアウトさせる

つまり、発話の時間にあわせて再生時間を調整しているわけですね。オーディオファイルそのものを短くするよりはぜんぜん楽ですよね。

でもちょっと待って下さい。

  • もし発話の長さが変わったらどうしましょう?
  • さらに、発話部分がユーザから受け取った発話に応じて動的に変わったらどうすればよいでしょう?

再生時間を事前に制御するのはとても難しくなります。

そこで今回新しく追加されたのが"Duration"です。

Durationプロパティ

DurationプロパティはMixerの子コンポーネントに対して指定します。Durationプロパティには"auto"と"trimToParent"が指定でき、"auto"がデフォルトです。"auto"は何も変わりませんが、重要なのは"trimToParent"です。これが設定されたコンポーネントは、Mixerの子コンポーネントの中で最も再生時間が長いコンポーネントにあわせて、再生時間が自動で調整されます。

ちょっと説明がわかりにくいかもしれません。やってみましょう。以下のコードで上書きします。

{
  "type": "Alexa.Presentation.APLA.RenderDocument",
  "token": "token",
  "document": {
    "type": "APLA",
    "version": "0.91",
    "description": "APL for Audioのサンプルです。",
    "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "item": {
        "type": "Mixer",
        "items": [
          {
            "type": "Speech",
            "contentType": "SSML",
            "content": "<speak><break time=\"2s\"/><voice name=\"Takumi\">はじめまして。僕の名前は ${payload.user.name}。これからお供の動物たちと一緒に鬼ヶ島に鬼を退治に行く途中なんだ。応援してね</voice><break time=\"1s\"/>おしまい。次回をお楽しみに。</speak>"
          },
          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1",
            "duration": "trimToParent"
          }
        ]
      }
    }
  }
}

変更したのはAudioコンポーネントにDuration: "trimToParent"を追加しただけです。

発話が終わるのと同時に再生が止まりましたね!

今回、Mixerで指定されているコンポーネントは2つです。最も再生時間が長いのはオーディオの方ですが、こちら側にDuration: "trimToParent"が設定されるので、これ以外で最も長いのは発話の方になります。つまり、発話の時間に合わせてオーディオの再生時間が自動で調節されるというわけです。

発話内容が動的に変化したとしても、オーディオはそれにあわせて再生時間が調整されるのでとても便利ですね!

ただ、ちょっといきなり切られる感じになりますね・・・ということで、filterでfadeOutを指定してみました。

{
  "type": "Alexa.Presentation.APLA.RenderDocument",
  "token": "token",
  "document": {
    "type": "APLA",
    "version": "0.91",
    "description": "APL for Audioのサンプルです。",
    "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "item": {
        "type": "Mixer",
        "items": [
          {
            "type": "Speech",
            "contentType": "SSML",
            "content": "<speak><break time=\"2s\"/><voice name=\"Takumi\">はじめまして。僕の名前は ${payload.user.name}。これからお供の動物たちと一緒に鬼ヶ島に鬼を退治に行く途中なんだ。応援してね</voice><break time=\"1s\"/>おしまい。次回をお楽しみに。<break time=\"3s\"/></speak>"
          },
          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1",
            "duration": "trimToParent",
            "filter": [
              {
                "type": "FadeOut",
                "duration": 3000
              }
            ]
          }
        ]
      }
    }
  }
}

が、効かないですね・・・発話の方にもSSMLで3秒の空白追加して長くしてみたんですが変わらず。FadeOutフィルターは再生時間の最後n秒をフェードアウトするものなので、trimToParentで再生時間を自動調整する場合はちょっと難しいのかもしれませんね。FadeInの方は動作しますし。ここは今後の改善に期待かも。

2021/02/14 追記

Twitterでできないよー、とつぶやいてみたら、ヒントもらえました。

こういう感じですね。

{
  "type": "Alexa.Presentation.APLA.RenderDocument",
  "token": "token",
  "document": {
    "type": "APLA",
    "version": "0.91",
    "description": "APL for Audioのサンプルです。",
    "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "item": {
        "type": "Mixer",
        "filter": [
          {
            "type": "FadeOut",
            "duration": 3000
          }
        ],
        "items": [
          {
            "type": "Speech",
            "contentType": "SSML",
            "content": "<speak><break time=\"2s\"/><voice name=\"Takumi\">はじめまして。僕の名前は ${payload.user.name}。これからお供の動物たちと一緒に鬼ヶ島に鬼を退治に行く途中なんだ。応援してね</voice><break time=\"1s\"/>おしまい。次回をお楽しみに。<break time=\"3s\"/></speak>"
          },
          {
            "type": "Audio",
            "description": "Audioでオーディオファイルの再生が可能です。今回のBGMはSHW様(http://shw.in/)のフリー音源を使用させていただいております。",
            "source": "https://www.dropbox.com/s/fjy0lc35708cbuf/takanoniharuka.mp3?dl=1",
            "duration": "trimToParent"
          }
        ]
      }
    }
  }
}

親となるMixerコンポーネント側でFadeOutフィルターを適用すればいいみたいです。ただしその場合は、FadeOutがMixer配下のコンポーネント全体にかかる、つまりSpeechにもかかってしまうので、Speechの最後にFadeOutの長さと同じだけのbreakを追加しておくということですね。なるほど。

まとめ

今回の例では、発話の時間<オーディオの時間でした。逆に、発話の時間>オーディオの時間の場合はどうなるのでしょうか?

というところは次回ご紹介します。