VRChatで遊びながら遠隔（無線）でDiscordのマイクミュート・スピーカーミュートを切り替えるスイッチを自作した

予算2000円くらいでできるよ！
無線だからパソコンから離れていても使えるよ！
__※ただし通電のための電源は必要__

# できたもの
https://twitter.com/fctokyo1016/status/1410985336553934853?s=20

# 参考にさせていただいた先駆者様
[Zoomをミュートする物理ボタンを作る](https://qiita.com/akaneburyo/items/076da6a4bd350e5820e9)

# VRChatを知らない人に一言で説明すると
遠隔（無線）でDiscordのマイクミュート・スピーカ－ミュートを切り替えられるスイッチを作ったよ！
以下、VRChat向けの説明なので本編まで読み飛ばしてOKです。

# VRChatを知ってる人に説明
VRChat開かれているイベントのスタッフを経験したことのある人なら分かると思いますが、HMDをかぶりながらDiscordを繋いで裏でスタッフ間の無線として活用する方法

VRChat内ではお客様相手に会話しながら、何か連絡事項などある場合は都度DiscordのマイクのON/OFFを切り替える必要があります
（VRChatのマイク切り替えは手元のコントローラーで行えるのでさほど煩わしさは感じませんが）

この方法を取った人なら一度は思う「マイクミュートの切り替えが面倒くさい！」というストレス
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126452/f74c46c2-9b39-5d77-d9b3-d4ff2421203a.png)
このマウスとの物理的距離によりいちいち

* HMDを外す
* 自身がどっちを向いているのか確認
* マウスまで向かってDiscordのウインドウを探す

を毎回繰り返すわけです

頻発的に切り替えを行う場合は非常に煩わしさを感じます
自分も様々なイベントでスタッフを務めているので日頃からそう思っていました

そこで考えました

__手元にDiscordのマイクを切り替えられるスイッチがあったらいいのに__

無ければ作ればいいじゃない！というVRCユーザー特有の発想により自作に挑むことに

# 制作における選定
色々と実現する方法はありそうだったんですが
* 安く済ませたい（上手くいかなかった場合に泣き寝入りできる額に収めたい）
* 無線化したい（PCとスイッチの間にコードがあるのは論外）
この2点を最重視しました

## 候補1
__Raspberry Piであれこれする方法__
真っ先に候補になったんですが

* ラズパイの種類が多すぎて迷う
* なにより価格がちょっと....

の2点により次の候補の次点となりました

## 候補2
__ATOM Matrixdeであれこれする方法__
結果的にこちらをチョイスしたんですが

* ラズパイより価格が安価
* 先駆者様がいた

の2点が決め手になりました

# 用意する物
* ATOM Matrix
* Bluetoothレシーバー
* USB typeCのケーブル
* モバイルバッテリー等、ATOM Matrixに通電する電源

# 手順
もしかすると詳しくないVRCユーザーさんがこの記事を見るかもしれないので簡略に書きます

* ATOM Matrixを入手する
    * [スイッチサイエンス](https://www.switch-science.com/catalog/6260/)等、公式代理店で買いましょう
* Bluetoothレシーバーを入手する
    * USBでPCに刺すやつ
    * 多分なんでも大丈夫なはず
* Arduinoをインストール
    * [公式](https://www.arduino.cc/en/software)からダウンロード
    * そのあとは[公式](https://docs.m5stack.com/en/arduino/arduino_development)とか[ここ](https://qiita.com/jksoft/items/fa92281f90bcbc32929c)を参照してセットアップ
    * シリアルポートの設定が必要かもしれないが、詳しくない人には多分これが一番難易度高いかもしれない
* [ESP32-BLE-Keyboard](https://github.com/T-vK/ESP32-BLE-Keyboard)をインストール
    * ダウンロードしたのを[スケッチ]→[ライブラリをインクルード]→[.ZIP形式のライブラリをインストール..]で読み込む
* ATOM MatrixをPCとBluetoothで接続 
    * WindowsのBluetooth設定から繋げる
    * まだこのタイミングだと検出されないかも...？
* AruduinoでコードをATOM Matrixに覚えさせる
    * ここらへんは「arduino atom matrix」とかでググったほうが分かりやすいかも
* Discordのショートカットキーを設定する
    * 設定->キー割り当てで「ミュート切り替え」「スピーカーミュート切り替え」を設定

# コード
```c++:discord_mute
#include "M5Atom.h"
#include <BleKeyboard.h>

#define NUM_LEDS 25
#define LED_PIN 27

CRGB leds[NUM_LEDS];
BleKeyboard bleKeyboard("MUTE BUTTON");

const uint8_t mic_mute[] = {KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'p'}; // マイクミュートに使いたいショートカットキー
const uint8_t spe_mute[] = {KEY_LEFT_CTRL, KEY_LEFT_SHIFT, KEY_LEFT_ALT, 'o'}; // スピーカーミュートに使いたいショートカットキー
bool isMicMute = true;
bool isSpeMute = false;

void sendMicMute(){
    int length = sizeof mic_mute / sizeof mic_mute[0];
    for (int index = 0; index < length; index++) {
        bleKeyboard.press(mic_mute[index]);
    }
    delay(100);
    bleKeyboard.releaseAll();
}

void sendSpeMute(){
    int length = sizeof spe_mute / sizeof spe_mute[0];
    for (int index = 0; index < length; index++) {
        bleKeyboard.press(spe_mute[index]);
    }
    bleKeyboard.releaseAll();
    delay(100);
}

const unsigned char image_blue[77]=
{
/* width  005 */ 0x05,
/* height 005 */ 0x05,
/* Line   000 */ 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   001 */ 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   002 */ 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   003 */ 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   004 */ 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
};

const unsigned char image_red[77]=
{
/* width  005 */ 0x05,
/* height 005 */ 0x05,
/* Line   000 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, // 
/* Line   001 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, // 
/* Line   002 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, // 
/* Line   003 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, // 
/* Line   004 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, 0xff,0x00,0x00, // 
};

const unsigned char image_border[77]=
{
/* width  005 */ 0x05,
/* height 005 */ 0x05,
/* Line   000 */ 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, // 
/* Line   001 */ 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, // 
/* Line   002 */ 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, // 
/* Line   003 */ 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, // 
/* Line   004 */ 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, 0x00,0x00,0xff, 0xff,0x00,0x00, // 
};

const unsigned char image_half[77]=
{
/* width  005 */ 0x05,
/* height 005 */ 0x05,
/* Line   000 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   001 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   002 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   003 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
/* Line   004 */ 0xff,0x00,0x00, 0xff,0x00,0x00, 0x00,0x00,0xff, 0x00,0x00,0xff, 0x00,0x00,0xff, // 
};

void toggleLight(int mic, int spe) {
    if(mic == 0 && spe == 0){
      M5.dis.displaybuff((uint8_t*)image_blue, 0, 0);
    } else if(mic == 1 && spe == 0){
      M5.dis.displaybuff((uint8_t*)image_half, 0, 0);
    } else {
      M5.dis.displaybuff((uint8_t*)image_red, 0, 0);
    }
}

void setup() {
    M5.begin(true, false, true);
    bleKeyboard.begin();

    delay(50);
}

void loop() {
    if(bleKeyboard.isConnected()){
        if(M5.Btn.wasReleased()){ //ボタンを押してすぐ離すとミュート
            sendMicMute();
            isMicMute = !isMicMute;
        }
        else if (M5.Btn.pressedFor(300)) { // ボタンを300msec押し続けるとスピーカーミュート
            sendSpeMute();
            isSpeMute = !isSpeMute;
            toggleLight(isMicMute, isSpeMute);
            while (! M5.Btn.wasReleased()) { // ボタンが離されるまで待つ
              delay(50);
              M5.update();
            }
        }
        toggleLight(isMicMute, isSpeMute);
        
    }else{
      M5.dis.displaybuff((uint8_t*)image_border, 0, 0);
    }
    delay(10);
    M5.update();
}
```