Windows端末＋Slack＋VOICEVOXによる音声読み上げ

件名の通り、Windows端末でSlackのメッセージが書き込まれたらVOICEVOXで音声読み上げをやる記事です。
※記事はたくさん出てきたのですが、細かい内容の記載がなかったりしたので備忘録的に。。。
※前回同様、割と雑な記事ですみません。

## 1. Slack設定
以下のサイトを参考にさせていただきました。（Slackの設定はまんま下記の記事通りです）
https://dev.classmethod.jp/articles/slack-api-bolt-js-getting-start/

## 2. nodejsのインストール
下記サイトより推奨版をインストールしました。
https://nodejs.org/ja/

## 3. Visual Studioインストール（インストール後は再起動しましょう）
下記サイトを参考にインストールしました。
https://resanaplaza.com/visual-studio-2022-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%89%8B%E9%A0%86/
※必ず「C++によるデスクトップ開発」ワークロードを選択してインストールしましょう。
![C++.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/dc4f7796-519e-5d00-ec75-7ef9aa850bc7.png)

## 4. VOICEVOXインストール
下記サイトよりダウンロードしてインストールします。
https://voicevox.hiroshiba.jp/
※自分はGPU/CPU版をインストールしました。

## 5. VOICEVOX起動
GUIでもコマンドプロンプトでもどちらでも。
もし、別ノードからアクセスさせることを前提にするなら下記の通り実施。
```bash
cd AppData\Local\Programs\VOICEVOX
run.exe --host [ローカルIP]
```

## 6. ライブラリインストール
適当なフォルダを作成して、下記コマンドを実行
```
npm init
npm install @slack/bolt
npm install node-fetch@2.6.1
npm install speaker
npm install web-audio-api
```

## 7. コード実装(app.js)
```javascript
const { App } = require('@slack/bolt');
const fetch = require('node-fetch');
const { AudioContext } = require('web-audio-api');
const context = new AudioContext();
const Speaker = require('speaker');

const app = new App({
  token: "xoxb-XXXXXXXXXXXXXXXXXXXX",       #自分が取得した値に置き換えてください。
  signingSecret: "XXXXXXXXXXXXXXXXXXXX",    #自分が取得した値に置き換えてください。
  socketMode: true,
  appToken: "xapp-XXXXXXXXXXXXXXXXXXXX",    #自分が取得した値に置き換えてください。
  port: process.env.PORT || 3000
});

app.message(async ({ message }) => {
  // 読み上げたいメッセージを Slack から取得
  const msg = message.text;

  // メッセージが存在しない場合は処理を終了
  if (!msg) {
    return;
  }

  // クエリ作成
  const audio_query_response = await fetch(
    "http://xx.xx.xx.xx:50021/audio_query?text=" + encodeURIComponent(msg) + "&speaker=0",
    {
      method: 'post',
      headers: {'Content-Type': 'application/json'}
    }
  );
  const audio_query_json = await audio_query_response.json();

  // 音声合成
  const synthesis_response = await fetch(
    "http://xx.xx.xx.xx:50021/synthesis?speaker=0",
    {
      method: 'post',
      body: JSON.stringify(audio_query_json),
      headers: {"accept": "audio/wav", 'Content-Type': 'application/json'},
    }
  );
  const synthesis_response_buffer = await synthesis_response.buffer();

  // 再生
  context.outStream = new Speaker({
    channels: context.format.numberOfChannels,
    bitDepth: context.format.bitDepth,
    sampleRate: context.sampleRate
  });

  context.decodeAudioData(synthesis_response_buffer, function(audioBuffer) {
    // サンプルレートを一致させる
    const sourceSampleRate = audioBuffer.sampleRate;
    const destinationSampleRate = context.sampleRate;
    const ratio = sourceSampleRate / destinationSampleRate;
    const length = audioBuffer.length / ratio;
    const targetBuffer = context.createBuffer(
      audioBuffer.numberOfChannels,
      length,
      destinationSampleRate
    );

    for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
      const inputData = audioBuffer.getChannelData(channel);
      const outputData = targetBuffer.getChannelData(channel);

      for (let i = 0; i < length; i++) {
        const index = i * ratio;
        const floor = Math.floor(index);
        const ceil = Math.ceil(index);
        const fraction = index - floor;
        const value = inputData[floor] + (inputData[ceil] - inputData[floor]) * fraction;
        outputData[i] = value;
      }
    }

    var bufferNode = context.createBufferSource();
    bufferNode.connect(context.destination);
    bufferNode.buffer = targetBuffer;
    //bufferNode.loop = true;
    bufferNode.start(0);
  });

});

(async () => {
  // Start your app
  await app.start();

  console.log('⚡️ Bolt app is running!');
})();
```

## 8. アプリ起動
作成したアプリを起動します。
```bash
node app.js
```


## 9. Slackへメッセージ書き込み
1番の手順で、作成したアプリを参加させたチャネルでメッセージを書き込んでみてください。うまく動作すればスピーカーから音声が聞こえてきます。