耳年齢を1才単位で判定するLINE Botを作った

## はじめに

年を取ると高い周波数が聞こえにくくなる耳年齢なるものがあるそうです。自分の耳年齢が知りたくなったので計測機を作りました。

## 作ったもの

登録した周波数に応じて、スピーカーから0.5秒ほど音が鳴り、耳年齢を1才単位で教えてくれます。（↓クリックでYouTubeに飛びます）

[![耳年齢を細かく教えてくれるLINE Bot](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4a9787a4-7642-fd05-768e-4b3671099158.png)](https://www.youtube.com/watch?v=KlEo4nSeOOQ&feature=youtu.be)

試したところ、15640Hzまで聞こえた私の耳年齢は34才と判定されました。

## 構成

こんなの作りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/0918c5f5-330b-6aca-2265-8926a3c378cc.png)

obnizを使い、LINE Botとスピーカーを付けたIoTデバイスを連携しました。これでLINEに投稿した値に応じた音がスピーカーから鳴ります。（nodejsのバージョンは、v12.11.0です）

IoT機器の情報は以下になります。
[obnizボード](https://obniz.myshopify.com/collections/devices/products/obniz-board-hobby-license-90-day-trial)
[圧電スピーカー](http://akizukidenshi.com/catalog/g/gP-04118/)


## 耳年齢を算出する計算式を考える

耳年齢を1才単位で表示するため、周波数と耳年齢の対応表を探したのですが、見つからないので、作りました。単回帰分析を使い、周波数から耳年齢を算出する計算式をエクセルでサクッと作れます。

まずは、[こちらのサイト](https://www.sainokuni-rionet.jp/choice/7_index_detail.html)  から以下の情報を頂きました。

|周波数|年齢|
|---|---|
|10000Hz|60代の方まで聞こえるレベル|
|12000Hz|50代の方まで聞こえるレベル|
|14000Hz|40代の方まで聞こえるレベル|
|15000Hz|30代の方まで聞こえるレベル|
|16000Hz|20代の方まで聞こえるレベル|


取得した情報をエクセルに入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c1225dc3-a4b0-2fbd-7bd1-c2879282526b.png)


入力した情報で散布図を描きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/153bdaf9-9532-1c6f-ffc4-3a7df2a010d3.png)

プロットされた値を右クリックし、近似曲線を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/be553b1b-afe3-de90-adfb-639e945dd104.png)

近似曲線のオプションから`グラフに数式を表示する`にチェックを入れると、近似曲線の数式が表示されます。これで、周波数から耳年齢を算出する数式ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/a5235247-444f-12a6-c701-c649e4144a8c.png)

y= -0.0065x + 135.64
この数式で耳年齢を算出できるようになりました。この直線は単回帰分析という機械学習の手法の1つになります。

試しに耳年齢を算出してみます。

|周波数|算出した耳年齢|
|---|---|
|10000|	71|
|11000|	64|
|14500|	41|
|15663|	34|
|15878|	32|

それっぽい耳年齢が算出できるようになりました。


## スクリプト

LINE Botとobnizに連携したNodejsのスクリプトです。これは、[n0bisukeさんのQiita記事](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)を参考にしました。

```javascript:linebot-speaker.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '',
    channelAccessToken: ''
};

// Obnizの準備
var Obniz = require("obniz");
var obniz = new Obniz("");  // Obniz ID を入力
var speaker; // 全体で使えるようにするスコープ
obniz.onconnect = async function () {
    speaker = obniz.wired("Speaker", {signal:0, gnd:1});
}

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  var hz = Number(event.message.text);
  speaker.play(hz);
  obniz.wait(500);
  speaker.play(0);

  let hantei = Number(event.message.text);
  hantei = Math.round(-0.0065 * hantei + 135.64); //耳年齢を算出
  let retnMsg = event.message.text;

  retnMsg += "Hz（ヘルツ）が聞こえたら、耳年齢は" + hantei + "才以下です";

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: retnMsg //実際に返信の言葉を入れる箇所
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
```

## 余談

- 何回も聞いてると聞こえなかった16000Hzも聞こえ耳年齢が2才若返りました。耳の老化防止にも使えそうです。

- 作成した数式は、周波数 10000Hz～16000Hzの範囲でのみ、それっぽい耳年齢を算出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e7c2e68b-77ca-386d-4c65-25f36a41e361.png)