キーボードのバックライトLEDで Bad Apple!!



キーボードのバックライト LED をハックして，動画プレイヤーにしました．

実際の様子は以下の[動画](https://www.nicovideo.jp/watch/sm37756535)を見てください．
[<img width = 480 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188347/0cefa358-039d-7793-c3c1-feab292cffd2.png">](https://www.nicovideo.jp/watch/sm37756535)

ヒトカラに行って Bad Apple!! を 6 回ぐらい歌ってたら思いつきました（昔はカラオケで歌えなかったのですが，[JOYSOUNDで今年3月に導入されました](https://news.nicovideo.jp/watch/nw6858669)．[DAMも去年8月に導入されました](https://nlab.itmedia.co.jp/nl/articles/1907/25/news137.html)．嬉しいね）．


2020/11/4 追記：おかげさまでニコニコ動画の「技術・工作」ランキング1位，更にニコニコ Daily Topics にセレクトされて，ニコニコトップページに掲載させていただきました！これに調子に乗って [いーあるふぁんくらぶ](https://www.nicovideo.jp/watch/sm37771488) と [千本桜](https://www.nicovideo.jp/watch/sm37771357) 版も公開させていただきました．よかったらこちらもどうぞ御覧ください！
[<img width = 480 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188347/fb13ef8e-3961-5b4e-5c81-279327f9477e.png">](https://www.nicovideo.jp/watch/sm37771488)
[<img width = 480 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188347/b5db0e6a-9627-a5ee-810e-61477f91a6d0.png">](https://www.nicovideo.jp/watch/sm37771357)


# Related Work

今年2020年の3月，bilibili 動画に 64コア128スレッドの Ryzen Threadripper 3990X のタスクマネージャーの画面を使って，Bad Apple!! を再生する動画が投稿され，Twitter で話題になりました．([PC Watchの記事](https://pc.watch.impress.co.jp/docs/news/yajiuma/1260433.html))
[<img width = 240 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188347/410630b1-c092-357f-b3d5-76edd02ec0fc.jpeg">](https://www.bilibili.com/video/av96396151/)


更にそれに誘発されてか，28コア/56スレッドの Xeon Platinum 8180 を4ソケット×8基 で 1792 スレッド のマシンで再現した方も現れました．([PC Watchの記事](https://pc.watch.impress.co.jp/docs/news/yajiuma/1266462.html))
[<img width = 240 src = "http://img.youtube.com/vi/RY5_gutA_Vw/0.jpg">](http://www.youtube.com/watch?v=RY5_gutA_Vw)

この様に最近タスクマネージャーを使った動画再現がプチブームになっていましたが，太古のニコニコを探すと，[8x8 のLEDマトリックスとArduinoで再現](https://www.nicovideo.jp/watch/sm11162185)，[ファミコンエミュレータ上で再現](https://www.nicovideo.jp/watch/sm13418595)，[7セグマトリクス表示機で再現](https://www.nicovideo.jp/watch/sm27598174) など様々ありました．

今回はキーボードのLEDバックライトを用いて再現しようと思いつきました．

# ハードウェア
自分で基板から設計した自作キーボードを用いました．
<img width="480" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188347/ed846535-5e7a-0c2c-bb92-eb159054429f.jpeg">
写真では紫色に光っているバックライトLEDを制御して光らせます．左手に 35 個，右手に63個で合計 98 個あります．
これは [YS-SK6812MINI-E](https://yushakobo.jp/shop/ys-sk6812mini-e/) というフルカラーシリアルLEDを使用しており，直列に接続させても1個1個のLEDの色をシリアル信号を用いて個別に制御することができます．コレが1キーに1つずつ割り当てられています．
マイコンは [Pro Micro](https://yushakobo.jp/shop/promicro-spring-pinheader/) を用いており，両手のそれぞれに搭載して TRS ケーブルで電源供給・通信しています．

基板から設計したから特殊であるという事ではなく，基本的に普通の自作キーボードと同じ構成です．
基板が設計済みの [Helix キーボードキット](https://yushakobo.jp/shop/helix-keyboard-kit/) などを用いても同じことが出来ると思います．
[<img width = 400 src = "https://yushakobo.jp/wp-content/uploads/2018/05/YKB0002-1.jpg">](https://yushakobo.jp/shop/helix-keyboard-kit/)
これらは既製品ではないので組み立てる必要があるのですが，例えば [遊舎工房さんのキーボード組み立てサービス](https://yushakobo.jp/shop/assembly-service/) を用いると，自分ではんだ付けなどの作業をすること無く完成品を入手することが出来ます．

# ソフトウェア
中身のキーボードの制御には，自作キーボードでよく用いられている [QMK Firmware](https://github.com/qmk/qmk_firmware) を用いています．これは自作キーボードを作る上で必要な便利な機能を共通で使えるファームウェアです．キーボードに合わせて config や C 言語のコードを書いて環境に合わせて利用することが出来ます．

# 動画を再生するには？
既存の Arduino などでの再現では FLASH ROM に動画を焼き付けてそれを再生していました．しかし ProMicro の FLASH  ROM は 32KB 程度しか無く，EEPROM も 1KB しかありません．
1 LED 1bit で表現するとして1フレームあたり98bit，10 fps として 3分半再生しようとしたら 205,800bit ≒ 25KB 必要です．LED を点灯させるだけなら大丈夫かもしれませんが，キーボードとして使用できる事を維持しようとすると ROM に余裕はありません．

今回はキーボードをUSB接続したPC上で動画データを持ち，キーボードに対してリアルタイムでLEDの点灯指示を送り再生する手法を採用しました．

# バックライト LED のカスタマイズ

QMK には [Backlight](https://beta.docs.qmk.fm/using-qmk/hardware-features/lighting/feature_backlight) という バックLED をアニメーション出来る機能があります．これは LED を1列のテープLEDだと思って1次元的なアニメーションを作ることが出来ます．標準では SNAKE など様々なアニメーションが利用でき，LEDを渦巻状に配線するとクルクル回るアニメーションが設定可能です．しかし，この機能には現状(2020年10月) ユーザーがアニメーションをカスタムするには QMK 本体をいじる必要があります．
他にも [LED Matrix](https://beta.docs.qmk.fm/using-qmk/hardware-features/lighting/feature_led_matrix) と言う，LED番号とキー座標，キー番号の対応を定義することで，キーの物理的な座標と連動したアニメーションをさせることが出来ます．この機能の `Custom layer effects` を指定することでユーザーの好きに定義したアニメーションを表示することが出来ますが，僕のキーボードはROMに余裕が無かったので LED Matrix を有効にすることは出来ませんでした．

今回はアニメーションさせないモードを用いて，PC から信号が来たら勝手にLEDの色を上書きする方法でカバーしました．QMK の アニメーションタイマーが発火したり，キーの明るさを変えるキーを打ったりすると独自アニメーションが上書きされてしまいますが，弄らなければ問題ありません（）．

ちなみに画面全部が白になってLEDの消費電力が多くなると，場合によってはキーボードが停止します（最初，謎のバグを疑ってた…）．普段は最大輝度を 95/255 にしていたのですが，今回のために最大輝度を 50/255 に落としました．

# PC との通信
PC との通信は [QMK の Raw HID機能](https://beta.docs.qmk.fm/using-qmk/software-features/feature_rawhid) を用いました．PC 側は Python プログラムを書きました．本当は C/C++ の方が得意なのですが，キーボードを接続して情報を取ることは出来てもデータを送ることが出来なかったので Python でやりました．

# 分割キーボード間の通信
両方のキーボードをUSBでPCにつなげてそれぞれHIDで繋げてもよいのですが，ロマンがないので分割キーボード間で通信させました．
通常であれば master から slave への通信は LEDのアニメーションモード程度しか送られないのですが，PCから各キーの輝度を指示するには配列データを送る必要があります．
[QMK の SPLIT 通信をカスタム指定](https://beta.docs.qmk.fm/using-qmk/hardware-features/feature_split_keyboard#firmware-configuration)することでこの通信部分もカスタムすることが出来ます．
自分のキーボードの `rules.mk` に

```makefile
SPLIT_TRANSPORT = custom
SRC += transport.c serial.c
```

と書き，`transport.c` をカスタムすることで自由な通信内容を送ることが出来ます．QMK 標準の [quantum/split_common/transport.c](https://github.com/qmk/qmk_firmware/blob/master/quantum/split_common/transport.c) をコピーして，[適当に書くことで](https://github.com/TumoiYorozu/zetta-keyboard/blob/master/split_common/transport.c) master から slave 側のキーボードに信号を送れます．

# LEDの信号
今回は 1LED 1byte でRGB色データを送りました．Bad Apple!! ならグレーだけ送れれば問題ないけど，他の映像でもカラーで送れるようにこのようにしました．
最初は R,G,B それぞれ 3,3,2 bit で送っていたのですが，暗いグレーを送る時，例えば1/8 の明るさのグレーを送るときは 1/8,1/8,0/4 とエンコードされるわけですが，LEDの無点灯と点灯ではエネルギー差が大きく，グレーでは無く黄色に見えてしまうのでこの方法は不採用になりました．
最終的には各色6階調で $6\times 6 \times 6 = 216$ 色を表現できる様にしました．この場合，ビット演算ではなく除算で色を抽出しないといけなくなり速度が気になりますが，どうせLEDの数は少なく，また6や36で割るなどの定数除算は掛け算にコンパイラが勝手に変えてくれるはずなので効率は無視しました．

# PC 側
Python3 にて，OpenCV, numpy などを用いて実装しました．キーのマッピングを定義しておいて，動画を読み込んで $184\times 48$のキャンパスにクロップ・リサイズして，各 LED に対応するピクセルをサンプリング，LED番号順に1列に並べ直して1LED 1Byte にエンコードして，hid 機能でいくつかに分けて送信しました．([ソース例](https://github.com/TumoiYorozu/zetta-keyboard/blob/master/led_key_monitor/qmk_rawhid_test.py))

各ピクセルをサンプリングする際，キーの担当範囲の平均でも良いのですが，この場合中途半端な値になりモヤモヤした映像になってしまうので，ある1点の値をそのまま使いました．この場合，くっきりした映像になり，さらに numpy でシンプルに書けるので高速に処理することができます．

# おわり
みなさんもオレオレ環境で Bad Apple!! しましょう（？）

よかったら Qiita の LGTM お願いします．