APLでのあれこれのメモ

# APLでのあれこれのメモ

## 1.はじめに

先日、[ねこねこゲーム](https://www.amazon.co.jp/dp/B07VVKV6YH/)　という画面対応のAlexaスキルを公開しました。APL対応スキルとして私がつくった3番目のものですが、いくつか試行錯誤した部分を自分のメモも兼ねて残します。


### 1.1. 前提知識

このドキュメントは以下の知識を前提としています。

- Alexaスキル開発の一般的手順・経験
- APL(Alexa Presentation Language)のある程度基礎的な知識

### 1.2. 「ねこねこゲーム」概要

ひと言で表せば、音とイラストの神経衰弱ゲームです。
Alexa Developer スキルアワード2019 に応募した際の動画=> https://youtu.be/n2K_YbenX70

ゲームの処理としてはこんな感じです。

1. 数字で１から８まで書かれたカードを表示し、ユーザーの選択を求める。
1. ユーザーが１から８までのカードのうち２つを選択する
1. 選択したカード２枚を表示し、同じカードかどうかを示す
 1. 選択した２枚のカード番号を表示する
 2. １つめのカードをめくり、カードに紐付けられた中身（音と画像）を提示する
 3. ２つめのカードをめくり、カードに紐付けられた中身（音と画像）を提示する
 4. カード２枚が一致したかどうか、判定結果を（音と画像で）提示する
1. カード選択画面に戻る。このとき、一致したカードについては数字では無く中身画像を提示する。

このゲームを作るときに試行錯誤したあれこれのポイントを以下にまとめました。


## 2.あれこれ本編

### 2.1. 画面操作と再生タイミング（speak(speechOutput),reprompt(repromptText),addDirective( directive )の順番）

今回一番時間がかかったのが、ゲーム概要の「選択したカード２枚を表示し、同じカードかどうかを示す」部分です。
カードをめくると同時に音を再生したいのですが、単純に speak(speechOutput)  に全部を載せてもタイミングは合いません。

たとえば、こんな実装の場合を考えましょう。

```` index.js
handlerInput.responseBuilder
    .addDirective( directive_RenderDocument )
    .addDirective( directive_ExecuteCommands )
handlerInput.responseBuilder
    .speak(speechOutput)
    .reprompt(repromptText)
````

EchoShow端末で提示される順番はこのようになります。

1. directive_RenderDocument (APL画面)
2. speechOutput
3. directive_ExecuteCommands  (APL画面への操作)
4. repromptText


speechOutputで判定結果を喋らせると、そのあとにdirective_ExecuteCommands で画面が動き、最後にrepromptTextが来ます。あとから動く画面、全然ドキドキしません。
ということで、「カードをめくると同時に音を再生」を実現するために ExecuteCommands を使いました。


### 2.2. カードをめくるような表示(SequenceにScrollToIndex)と画面切り替え（PagerにSetPage）

カードをめくる動作として、次の２つを考えました

- Pager に対して SetPage する
- Sequence  に対して ScrollToIndex する

試した結果、次のように使い分けました

- カードをめくる：PagerにSetPageを使う。画像がスルスルと動く感じが出る。
- 画面を切り替える：Sequence にScrollToIndexを使う。画面がスパッと切り替わる。


### 2.3. カードをめくったあとにテキストを読み上げる(SpeakItemとtransformers)

カードをめくった後にテキストを読み上げ、さらに別のカードをめくる・・ということで、次のように実現しました。

1. Textコンポーネントを用意する
  -  高さ・幅ともに 0 とする
2. 読み上げるテキストと、transformersの宣言を行う
  - inputPathに読み上げさせたいテキストを指定
  - transformer属性はssmlToSpeechを指定
  - outputNameで出力名を定義
  - その出力名をTextコンポーネントのspeechプロパティに設定
3. ExecuteCommandsで、TextコンポーネントをSpeakItemさせる

実際のデータやコードはこんな感じになります。あちこち省略していますので、そのままコピペしても動かないと思います。

````homepage.json
{
    "layouts": {
        "twoPagersLayout": {
            "item": [
                {
                    "item": [
                        {
                            "type": "Text",
                            "id": "talk_left_pre",
                            "text": "ひとつめ",
                            "speech": "${payload.bodyTemplate3Data.properties.Speech_0}",
                            "position": "absolute",
                            "width": 0,
                            "height": 0
````

````data.json
{
    "bodyTemplate3Data": {
        "properties": {
            "Ssml_0": "<speak>１番の鳴き声</speak>",
        },
        "transformers": [
            {
                "inputPath": "Ssml_0",
                "outputName": "Speech_0",
                "transformer": "ssmlToSpeech"
            },
````
````index.js
  const directive_RenderDocument = {
      type : 'Alexa.Presentation.APL.RenderDocument',
      version: '1.0',
      "token"   : "token",
      document: require('./homepage.json'),
      datasources: require('./data.json')
    };

  const directive_ExecuteCommands = {
    "type"    : "Alexa.Presentation.APL.ExecuteCommands",
    "token"   : "token",
    "commands": [
      {
        "type": "SpeakItem",
        "componentId": "talk_left_pre"
      }]
    };

handlerInput.responseBuilder
    .addDirective( directive_RenderDocument )
    .addDirective( directive_ExecuteCommands )
````


### 2.4. カードをめくったあとにmp3を再生する(mp3にも制約がある)

mp3の再生は、テキストの読み上げとは似て非なる処理となりました。

1. Textコンポーネントを用意する
  -  高さ・幅ともに 0 とする
  -  speech属性に、mp3のURLが設定されたdatasoureのパスを指定する
3. ExecuteCommandsで、TextコンポーネントをSpeakItemさせる

大筋ではこれで再生できます。ただ、私はここでつまずきました。用意したmp3は、[公式資料](https://developer.amazon.com/ja/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html#audio) に書かれている次の制約に引っかかっていたのです。

- ビットレートは48 kbps
- サンプルレートは22050Hz、24000Hz、16000Hzのいずれか

そこで、次の記事を参考にffmpegでデータを変換し、無事に解決しました。

* [[Alexa] SSMLのAudioタグで応答に効果音を挿入する 4 Audio変換](https://dev.classmethod.jp/cloud/alexa-ssml-audio/)



### 2.5. 同時に動かす(Parallelコマンド)

カードをめくる(ScrollToIndex)と同時にテキストを読み上げる(SpeakItem)は、Parallelコマンドで実現します。

````index.js

  const directive_ExecuteCommands = {
    "type"    : "Alexa.Presentation.APL.ExecuteCommands",
    "token"   : "token",
    "commands": [
     {
        "type": "Parallel",
        "commands": [
          {
            "type": "SpeakItem",
            "componentId": "talk_left"
          },
          {
            "type"       : "ScrollToIndex",
            "componentId": "leftSequence",
            "index": 1
          },
````


## 3. 参考にした資料

* [APLコマンド](https://developer.amazon.com/ja/docs/alexa-presentation-language/apl-commands.html)
* [[Alexa] SSMLのAudioタグで応答に効果音を挿入する](https://dev.classmethod.jp/cloud/alexa-ssml-audio/)


## 4.おわりに
少しでもどなたかの参考になれたなら幸いです。