青魔法を詠唱して青魔法を詠唱してみた！

## 音声認識キャンペーンに参加！
音声認識エンジンを使って何かやってみませんか？みたいなキャンペーンをQiitaで開催されていて、AmiVoice APIという音声認識APIサービスの無料利用クーポンも振る舞われるとか。
面白そうなキャンペーンだけど、音声認識できたとして、さて何に使えば面白いかなー？

https://qiita.com/official-events/23333339b694ca71810a

## 大好きなFF14の青魔法を詠唱してみよう！
身振り手振りで遊ぶゲームがあるんだから、声を出してもいいだろう。
大好きなFF14の青魔道士で、ボタンぽちーではなく、かっこ良く青魔法を詠唱し、その魔法でモンスターをやっつけたらカッコいいのではないか！

流れはこんな感じか？
青魔法をマイクでしゃべる ＞ AmiVoice APIに送信 ＞ 認識した文字を貼り付け

うむ、ゲームプレイ的には全く効率的とは思えないが、魔法詠唱できるのだから小さいことには目をつむろう

## 早速作ってみる！
言語：Python3.11
音声認識：AmiVoice API
音声入力：pyaudio
キーボード操作：pyautogui
特に問題なくpipでインストールできました。

300秒間待っていて、キーボードのEnterキーの入力をトリガーにマイクの録音を開始、
3秒間録音し、AmiVoice APIに送信して音声認識してもらう、
音声認識の結果テキストの前に、`/blueaction `をつけて、貼り付けて、Enterを入力。
（結果テキストが青魔法だったら、ゲーム内で青魔法の詠唱が始まる）

ちょうど私のゲーム設定で、Enterキーを押すと、チャット入力できるようになるので、そこに青魔法を実行するコードを貼り付ける、という形にしました。

恥ずかしながら、参考にさせていただいた記事を丸っとパクッてます・・・
記事にもありましたが、マイクがindex何番かが結構曲者ですね。（地味にはまります）

<details><summary>コードはこちら（折り畳み）</summary>

```python:bluespell.py
from send_voice import send_voice
from input_key import input_blue_spell
from mic_audio import wait_key,record_and_save

def main():
    while 1:
        wait_key()
        record_and_save()
        spell=send_voice()
        input_blue_spell(str(spell))


if __name__ == '__main__':
    main()
```

```python:mic_audio.py
import wave
import time

import pyaudio
import keyboard
import atexit
import sys

FORMAT        = pyaudio.paInt16
TIME          = 3           # 録音時間[s]
SAMPLE_RATE   = 44100        # サンプリングレート
FRAME_SIZE    = 1024         # フレームサイズ
CHANNELS      = 1            # モノラルかバイラルか
INPUT_DEVICE_INDEX = 1       # マイクのチャンネル
NUM_OF_LOOP   = int(SAMPLE_RATE / FRAME_SIZE * TIME)

WAV_FILE = "./output.wav"


def cleanup():
    print('cleanup')

atexit.register(cleanup)

def wait_key():
    waittime = 300
    endtime = time.time() + waittime
    zt = endtime - time.time()

    while zt > 0:
        zt = endtime - time.time()
        print(f"waittime={int(zt)}",end="\r")
        if keyboard.is_pressed('enter'):
            print("Enterキーが押されました")
            break
        if keyboard.is_pressed('q'):
            print("qキーが押されましたq")
            sys.exit()

def record_and_save():
    """
    デバイスから出力される音声の録音をする
    """
    pa = pyaudio.PyAudio()

    stream = pa.open(format   = FORMAT,
                     channels = CHANNELS,
                     rate     = SAMPLE_RATE,
                     input    = True,
                     input_device_index = INPUT_DEVICE_INDEX,
                     frames_per_buffer  = FRAME_SIZE)

    print("RECORDING...")

    list_frame = []


    for i in range(NUM_OF_LOOP):
        data = stream.read(FRAME_SIZE)
        list_frame.append(data)

    print("RECORDING DONE!")

    stream.stop_stream()
    stream.close()
    pa.terminate()


    wf = wave.open(WAV_FILE, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(pa.get_sample_size(FORMAT))
    wf.setframerate(SAMPLE_RATE)
    wf.writeframes(b''.join(list_frame))
    wf.close()

```

```python:send_voice.py
import requests

def send_voice():
    # AmiVoiceのAPIキーを設定(マイページで確認できます)
    APP_KEY = "*****"

    # 認識対象の音声ファイルパス
    AUDIO_FILE = "output.wav"

    # エンドポイントのURL
    URL = "https://acp-api.amivoice.com/v1/recognize"

    # 音声ファイルを開く
    with open(AUDIO_FILE, "rb") as f:
        audio_data = f.read()

    # リクエストのパラメータを設定
    params = {
        "d": "-a-general"
    }
    files = {
        "u": (None, APP_KEY),
        "a": (AUDIO_FILE, audio_data)
    }

    # POSTリクエストを送信
    response = requests.post(URL, params=params, files=files)

    # レスポンスを処理
    if response.status_code == 200:
        result = response.json()
        print("認識結果:", result["text"])
    else:
        print("エラー:", response.status_code, response.text)
    
    return result["text"]
```

```python:input_key.py
import pyautogui
import pyperclip

def input_blue_spell(txt):
    pyperclip.copy('/blueaction ' + txt)
    pyautogui.hotkey('ctrl', 'v')
    pyautogui.hotkey('enter')
```
```python:mc_check.py
import pyaudio

pa = pyaudio.PyAudio()

for i in range(pa.get_device_count()):
    device_info = pa.get_device_info_by_index(i)
    print(device_info)
pa.terminate()
```
</details>

## 動かしてみた！
さぁマイクに向かって`水鉄砲`としゃべるんだ！

<iframe width="560" height="315" src="https://www.youtube.com/embed/v9OjE9BP2VQ?si=ek83wWXKizgMqXnn" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


・
・・
・・・たどたどしすぎるのか、認識率がいまいち。マイクの調整不足かもしれない。
大人の本気を見せてやろう！

<iframe width="560" height="315" src="https://www.youtube.com/embed/HwP_b3w0UR8?si=fY9Rj5XlDZwlZ6ZV" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

結構青魔法が発動しているのではないでしょうか。
実は青魔道士の`超硬化`や`ホワイトウィンド`なども使いたかったのですが、音声認識を通すと、`超高価`、`ホワイトウインドウ`というような具合に認識され、うまく行きませんでした。恐らく、日常でよく使いそうな文字列が優先されるのかな？と感じました。
辞書登録もしてみたのですが、超硬化はうまくいきませんでしたねー。
なので動画では、うまくいきそうな青魔法を選んでマイク入力しています。

2024/05/13追記
AMIVOICE_APIをエンジンを、うっかり”会話_汎用”を選択していました。
”音声入力_汎用”にすると、`超硬化`を詠唱できたのかもしれません！
AMIVOICEさん疑ってごめんなさい（やり直す元気はないですが）

2024/06/04追記
今ぼんやり見てたら、単語登録の画面に
> セッション接続時に「profileId」の値に「サービスID」を指定した場合に限り、本ページで登録した単語が読み込まれ、音声認識に活用されます。

って書いてありました！
全然見てませんでした。申し訳ございません！

```diff_python:send_voice.py
    # リクエストのパラメータを設定
    params = {
-        "d": "-a-general"
+        "d": "-a-general-input"
    }
```
## これであなたも青魔道士！
ゲームプレイ的には、通常の操作で約2秒ごとに青魔法を発動できるのに対し、音声入力待ち3秒＋通常の2秒で、5秒ごとの魔法発動で、全く役には立ちませんね。

## 参考にさせていただいた記事
全ては参考にさせていただいた皆様のおかげです！記事投稿ありがとうございます！

https://qiita.com/KENTAROSZK/items/8d82a495b7cffec69862

https://qiita.com/masaoza0916/items/0e6d667bf5fb8aff3293

https://qiita.com/umashikate/items/98c94cdd269ea26c41c6

https://qiita.com/nave2000/items/986237cae79bf0488da4