Pythonを使ってWiiリモコンから音を出す

果たして自分以外にこんなことやりたい人がいるのか分からないですけど他にあまりにも情報が少ないので一応残しておきます。 基本的にはhttp://wiibrew.org/wiki/Wiimoteの通りです。

必要なもの

フォーマットについて

Wiiリモコンのスピーカーで使える音声ファイルのフォーマットには8bit符号付きPCMか4bitADPCMのどちらかを選べるようです。

ただADPCMの方はヤマハのチップにしか使われてないフォーマットらしく変換できなかったのでPCMを選択しました。(ffmpegがサポートしてるらしいけど詳しくは知らない)

ファイルを変換するにはsoxを使って

sox --norm input.mp3 -b 8 out.raw channels 1 rate 2000  # 8bit, 2khz, mono

でできます。

Bluetoothソケット

PyBluezでどうやってWiiリモコンと通信したのか一応補足。

import bluetooth

ADDR = '00:19:1D:30:D2:2C'  # wiiリモコンのMACアドレス
socket = bluetooth.BluetoothSocket(bluetooth.L2CAP)
socket.connect((ADDR, 0x13))  # ここでWiiリモコンの①と②を同時押し

def write(value):
    socket.send(('a2' + value).decode('hex'))


def read(bytes):
    return socket.recv(bytes).encode('hex')

wiiリモコンMACアドレスhcitool scanとするかbluetooth.discover_devices(lookup_names=True)で調べられます。 write()、read()でWiiリモコンにデータ送ったり受け取ったりできます。

頭の0xa2はこちらから送信するときは必ず付けなくてはいけないようです。 また、Wiiリモコンから送られてくるデータの1バイト目は必ずa1になってます。

コレだけでとりあえず必要なものは揃いましたね。

スピーカー初期化

Wikiによると

  1. スピーカーの有効化(0x14へ0x04を送信)
  2. スピーカーのミュート(0x19へ0x04を送信)
  3. レジスタ0xa20009へ0x01を書き込み
  4. レジスタ0xa20001へ0x08を書き込み
  5. レジスタ0xa20001へ7-byte configurationを書き込む
  6. レジスタ0xa20008へ0x01を書き込み
  7. ミュート解除(0x19へ0x00を送信)

とあります。順番に行きましょう。

スピーカー有効化

write('1404')

スピーカーのミュート、ミュート解除

2. スピーカーのミュート(0x19へ0x04を送信)

write('1904')

7. ミュート解除(0x19へ0x00を送信)

write('1900')

7-byte configuration

スピーカーを鳴らすための設定は以下のような7バイトのデータになってます。 00 FF RR RR VV 00 00

FF: フォーマット。0x40なら8bitPCM、0x00ならADPCM

RR RR: サンプリングレート。 PCMの場合 12000000/RATEの値を16進数にしたものがRR RRになります。但しリトルエンディアンなので注意。

どうもBluetoothドライバ他との兼ね合いで2kHzより上はうまく音が出ないみたいです。

2kHzなら 12000000/2000 = 6000 = 0x1770 となるのでRR RRは 70 17となります。

VV: ボリューム 0x00から0xffの範囲

ボリュームを0x40とすると今回必要な7-byte configurationは 00 40 70 17 40 00 00 のようになります。

レジスタへの書き込み

レジスタに書き込みをするには以下のような23バイトのデータを送ります。 (a2) 16 MM FF FF FF SS DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD

MM: 書き込むレジスタの種類。今回は0x04

FF FF FF : アドレス

SS: サイズ(bytes)

DD: データ。20バイトに満たない場合はデータを左詰め?で0で埋めます。

以上を踏まえて

3. レジスタ0xa20009へ0x01を書き込み 4. レジスタ0xa20001へ0x08を書き込み 5. レジスタ0xa20001へ7-byte configurationを書き込む 6. レジスタ0xa20008へ0x01を書き込み は、

write('1604a200090101000000000000000000000000000000')
write('1604a200010108000000000000000000000000000000')
write('1604a200010700407017400000000000000000000000')
write('1604a200080101000000000000000000000000000000')

音声データ送信

スピーカーの初期化が終わったら実際にデータを送信します。

音声データをWiiリモコンへ送信するときのフォーマットは

(a2) 18 LL DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD

DDはデータ。LLはバイト長…と思いきやビット長らしいです、ここで数時間詰まりました(よく見たらWikiにもちゃんと書いてある)

あとは実際にガンガン送信するだけなんですけどもう一つ重要な事があって、それは「設定したサンプルレートに合わせた間隔でデータを送る」ということです。つまり8bit、サンプルレートが2kHzなら2kB/s、Wiiリモコン送れる音声データは一度に20バイト。

ということは結局10ミリ秒間隔で送信すれば良いことになります。 この間隔がちょっとでも違うと全く音が出ません。ここでも数時間取られましたよ…。

まとめると

こうなります。書き殴りの汚いコードなのは許してね。

import time

import bluetooth


def write(value):
  r.send(('a2' + value).decode('hex'))


def recv():
  return r.recv(25).encode('hex')


def init_speaker():
  write('1404')
  write('1904')

  write('1604a200090101000000000000000000000000000000')
  write('1604a200010108000000000000000000000000000000')
  write('1604a200010700407017400000000000000000000000')
  write('1604a200080101000000000000000000000000000000')

  write('1900')


def send_song(songname, dur=0.01):
  f = open(songname).read().encode('hex')
  for i in range(len(f)/20):
    write('18a0' + f[i*40:(i+1)*40])
    time.sleep(dur)

ADDR = '00:19:1D:30:D2:2C'

r = bluetooth.BluetoothSocket(bluetooth.L2CAP)

r.connect((ADDR, 0x13))


write('11f0')  # LEDの点滅を止める

init_speaker()
send_song('out.raw')

実際に動いてるとこ

うーん……楽しい!!!!!!!!!!!!!

たまに何故か音が出ない時があって、どうもスピーカーの初期化に失敗してるみたいです。そういうときはinit_speaker()を何回か繰り返すと音が出るようになります。

ちなみにPythonWiiリモコン使いたいだけならcwiidってライブラリがあるのでそっち使ったほうがいいです。ただ自分でWiiリモコンぽちぽち押しながらWikiとにらめっこしてコード書くのも楽しいですね。