予算2000円くらいでできるよ!
無線だからパソコンから離れていても使えるよ!
※ただし通電のための電源は必要
できたもの
参考にさせていただいた先駆者様
VRChatを知らない人に一言で説明すると
遠隔(無線)でDiscordのマイクミュート・スピーカ-ミュートを切り替えられるスイッチを作ったよ!
以下、VRChat向けの説明なので本編まで読み飛ばしてOKです。
VRChatを知ってる人に説明
VRChat開かれているイベントのスタッフを経験したことのある人なら分かると思いますが、HMDをかぶりながらDiscordを繋いで裏でスタッフ間の無線として活用する方法
VRChat内ではお客様相手に会話しながら、何か連絡事項などある場合は都度DiscordのマイクのON/OFFを切り替える必要があります
(VRChatのマイク切り替えは手元のコントローラーで行えるのでさほど煩わしさは感じませんが)
この方法を取った人なら一度は思う「マイクミュートの切り替えが面倒くさい!」というストレス
このマウスとの物理的距離によりいちいち
- HMDを外す
- 自身がどっちを向いているのか確認
- マウスまで向かってDiscordのウインドウを探す
を毎回繰り返すわけです
頻発的に切り替えを行う場合は非常に煩わしさを感じます
自分も様々なイベントでスタッフを務めているので日頃からそう思っていました
そこで考えました
手元にDiscordのマイクを切り替えられるスイッチがあったらいいのに
無ければ作ればいいじゃない!というVRCユーザー特有の発想により自作に挑むことに
制作における選定
色々と実現する方法はありそうだったんですが
- 安く済ませたい(上手くいかなかった場合に泣き寝入りできる額に収めたい)
- 無線化したい(PCとスイッチの間にコードがあるのは論外)
この2点を最重視しました
候補1
Raspberry Piであれこれする方法
真っ先に候補になったんですが
- ラズパイの種類が多すぎて迷う
- なにより価格がちょっと....
の2点により次の候補の次点となりました
候補2
ATOM Matrixdeであれこれする方法
結果的にこちらをチョイスしたんですが
- ラズパイより価格が安価
- 先駆者様がいた
の2点が決め手になりました
用意する物
- ATOM Matrix
- Bluetoothレシーバー
- USB typeCのケーブル
- モバイルバッテリー等、ATOM Matrixに通電する電源
手順
もしかすると詳しくないVRCユーザーさんがこの記事を見るかもしれないので簡略に書きます
- ATOM Matrixを入手する
- スイッチサイエンス等、公式代理店で買いましょう
- Bluetoothレシーバーを入手する
- USBでPCに刺すやつ
- 多分なんでも大丈夫なはず
- Arduinoをインストール
-
ESP32-BLE-Keyboardをインストール
- ダウンロードしたのを[スケッチ]→[ライブラリをインクルード]→[.ZIP形式のライブラリをインストール..]で読み込む
- ATOM MatrixをPCとBluetoothで接続
- WindowsのBluetooth設定から繋げる
- まだこのタイミングだと検出されないかも...?
- AruduinoでコードをATOM Matrixに覚えさせる
- ここらへんは「arduino atom matrix」とかでググったほうが分かりやすいかも
- Discordのショートカットキーを設定する
- 設定->キー割り当てで「ミュート切り替え」「スピーカーミュート切り替え」を設定
コード
#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();
}