2021年07月29日 更新

BLEデータに応じて音声合成で読み上げ!

どうも、クラゲです。
今回は、BLEデバイスからNotifyで読み込んだデータに応じて読み上げを行う方法を紹介します。音声ファイルは不要で、指定したテキストを音声合成で読み上げます。例えば、センサーの値を読み上げるなどの応用に使えます。

動画の音声はONにして見てください。今回もmicro:bitを傾けることで、センサー値の変化を代用しています。バラツキがある場合、ある程度値が落ち着いたところで読み上げているのが分かると思います。

【 必要なもの 】

Scan と同様

【 micro:bitの準備 】

Scan と同様

micro:bitが手元にない人は、HTML側のUUIDと受信データフォーマットを変更すれば、他のBLEデバイスでも対応可能です

【 デモページとソースコード 】

WEB上で試せるデモページはこちら
BlueJelly Sample : Speech

ローカル環境で試したい方はこちらのGitHubからダウンロードし、advance_speech.htmlを開いて実行してください。

【 プログラム解説(HTML) 】

プログラムのベースは Notify です。
では、早速読み上げ追加部分について見てゆきましょう

HTMLは特に追加変更部分はありません!

【 プログラム解説(JavaScript) 】

音声合成を使うために、最初にインスタンス生成と言語設定が必要です

var speech = new SpeechSynthesisUtterance();//インスタンス生成
speech.lang = 'ja-JP';//言語の設定

例えば、"こんにちは"を音声合成で読み上げる場合は、以下の記述のみでOKです。

speech.text = "こんにちは";
speechSynthesis.speak(speech);

これをそのままvalueに置き換えれば使えそうですね。
しかし、onRead部において"こんにちは"の代わりにvalueにすると、値を読み上げますが、notifyによりデータをずっと連続で受信しているため、読み上げが止まらなくなってしまいます。
そこでクラゲは以下の3つの工夫を追加しました。

  • 直前の読み上げキャンセル
    新たに読み上げる前に、現在読み上げ中であれば、その直前の読み上げをキャンセルします。
    このようにすることで、前の読み上げを待たずに、最新の読み上げを行うことが可能になります。

  • 読み上げるタイミングを遅らせる
    読み上げキャンセルだけだと、例えば傾けて 111 -> 125 -> 136 と値が変わったときに、「ひゃくじゅ・・・ひゃくにじゅ・・・ひゃくさんじゅうろく」と発音されてしまいます。
    対策として、値がある程度落ち着いてから読み上げさせて、「さんびゃくさんじゅうろく」だけ発音するようにします。
    具体的にはタイマーを用いて、例えば500ms秒後に読み上げるようにします。もし500ms以内に新たな読み上げデータが来た場合は、そのデータで上書きしてタイマーをリセットします。
    このようにすることで、落ち着く前の読み上げは行われず、最後に落ち着いた値のみを読み上げることが可能となります。
    500msという数値は、使用環境などに応じて調整してください。

  • 前回と同じ値になったら読み上げ確定
    今回micro:bit側のnotifyは、値の変化あるなしに関係なくずっと送信されています。
    上記の"読み上げるタイミングを遅らせる"対策だけだと、タイマーがずっとリセットされて、全く読み上げないことになります。
    そこで、前回と同じ値の時はタイマーリセットしないことで、最後に確定した値で読み上げが実行されるようになります。

それでは見て行きましょう。

読み上げキャンセルは簡単です。speakメソッドの前にcancelメソッドを行うだけです。

speechSynthesis.cancel();
speechSynthesis.speak(speech);

読み上げのタイミングを遅らせる方法は、setTimeoutを使って実現しています。clearTimeoutでタイマーをリセットしています。Global部にてtimer_idという変数を用意して、タイマーを管理しています。
speech_laterという関数を作り、先ほどの2行の処理(cancelとspeak)を行っています。

  if(value < old_value - 1 || value > old_value + 1)
  {
    old_value = value;
    speech.text = value;
    clearTimeout(timer_id);
    timer_id = setTimeout(speech_later, 500);
  }

値がフラフラしているときに、同じ値や近い値を読み上げしまうので、if文を使って、直前の値に対し ±1 を超える変化があったときのみを対象としました。
なお、Global部にold_valueという変数を用意して、前回の値を管理しています。

さらにスマホ対策として、一度ボタン押しで読み上げを行わないと、うまく実行されなかったため、ボタンイベントで読み上げを行うように工夫しています。

document.getElementById('startNotifications').addEventListener('click', function() {
      //読み上げ
      speech.text = "スタートします";
      speechSynthesis.speak(speech);

      ble.startNotify('UUID1');
});

いかがでしょうか?
画面が見れないような環境で値を読み上げてくれると色々と便利な使い方も思いつくかもしれません!

以上、BLEデータに応じて音声合成で読み上げでした。