うならぼ

どうも。アフィリエイトとか広告とか解析とかは/about見てね。

USB-MIDIなデバイスをRaspberryPiでつなぐ

この記事は Raspberry Pi Advent Calendar 2021 18日目の記事です*1

昨日の担当はあっきぃさんでした。明日の担当は未定ですね。って書いてたら埋まってた。

akkiesoft.hatenablog.jp

背景

どこのご家庭にも以下のようなデバイスが余っていることと思います。

おや……この Raspberry Pi (たぶん1B)にはUSBポートがふたつありますね……こいつで音源とコントローラを接続できるのでは??

やってみる

なんのことはない、こういうのは先駆者がいるわけです。

やってることも超簡単。Raspbian入れてaconnectでつなぐだけです。あまりにも簡単すぎる。名前で指定できるからポート変更にも強い。

$ aconnect EWI-USB SonicCell

これを /etc/rc.local にでも突っこんでおけば、接続した状態でラズパイの電源を入れるだけで準備完了。

完。

どうせなのでもうちょっと便利にする

遊んでいるうちに色々気になってきました。

  • 接続が終わったのかどうか、鳴らしてみないとわからない
    • これについては接続できたところで音でも鳴らせばよさそう?ちょうど音源つながってるし。
  • 新しいコントローラーを使う時にはスクリプトの修正が必要
    • aconnect -i -l で入力ポートを列挙できるので、存在する適当な入力デバイスをつないでもよさそう
  • 起動時にしか接続を試行しないので、後からコントローラーだけつなぎ変えると動かない
    • udev あたりで接続を検知すればよさそう?

この辺全部盛りこんだスクリプトはこんな感じになりました。

#! /bin/bash

ACONNECT_OUT=SonicCell
AMIDI_OUT=$(amidi -l | grep -F "${ACONNECT_OUT}" | grep -Eo -m1 "hw:[^ ]+")
ACONNECT_IN=$(aconnect -l -o | grep -Fv "'${ACONNECT_OUT}'" | grep -Fm1 "card=" | sed -E "s/^client [^:]+: '(.+)' .+$/\1/")

NOTES_CONNECT=(45 49 4C)
NOTES_ERROR=(45 45 45)
NOTES_DISCONNECT=(45 45 45)

function play_notes() {
    for nn in $*; do
        amidi -p $AMIDI_OUT -S 90${nn}78
        sleep 0.01
        amidi -p $AMIDI_OUT -S 80${nn}78
    done
}

function reset_controller() {
    # 7Bh(オールノートオフ) 79h(リセットオールコントローラー)
    amidi -p $AMIDI_OUT -S B07B00B07900
}

if [ -z "$AMIDI_OUT" ]; then
    echo "$0: no output ($ACONNECT_OUT)"
    exit 1
fi

if [ -n "$ACONNECT_IN" ]; then
    echo "$0: connecting $ACONNECT_IN -> $ACONNECT_OUT"
    aconnect -x
    reset_controller
    aconnect "$ACONNECT_IN" "$ACONNECT_OUT"
    if [ $? -eq 0 ]; then
        # 音を鳴らすために一旦切断する(?)
        aconnect -x
        play_notes 45 49 4C
        aconnect "$ACONNECT_IN" "$ACONNECT_OUT"
    else
        play_notes 45 45 45
    fi
else
    echo "$0: no input found"
    aconnect -x
    play_notes 4C 49 45
fi

音を鳴らす

ALSAMIDI OUTポートから音を鳴らすのは、この辺が簡単そうです。

  • aplaymidi でSMFファイルを再生する
    例: aplaymidi -p 24:0 ff_fanfare.mid
  • amidi で直接MIDIメッセージを流す
    例: for nn in {45,49,4C}; do amidi -p hw:2,0,0 -S 90${nn}7F; sleep 0.125; amidi -p hw:2,0,0 -S 80${nn}7F; done

……なんでポートの指定方法違うの。

接続するたびに勝利のファンファーレが鳴るというのも楽しそうではありますが、ここはWindowsのデバイス接続音を真似て、ドミソの3音で適当にしました。

# 接続時
for nn in {45,49,4C}; do amidi -p $AMIDI_PORT -S 90${nn}78; sleep 0.01; amidi -p $AMIDI_PORT -S 80${nn}78; done
# 切断時(正確には、入力ポートが見つからなかった時)
for nn in {4C,49,45}; do amidi -p $AMIDI_PORT -S 90${nn}78; sleep 0.01; amidi -p $AMIDI_PORT -S 80${nn}78; done
# エラー時
for nn in {45,45,45}; do amidi -p $AMIDI_PORT -S 90${nn}78; sleep 0.01; amidi -p $AMIDI_PORT -S 80${nn}78; done

MIDIメッセージについては https://www.g200kg.com/jp/docs/tech/midi.html とかを参考に頑張ります。

なお aconnect でつないでいる出力ポートには amidi でメッセージを送ることができません。そういえばMIDIは出力ポートを共有できない子でしたね……。仕方ないので、接続に成功したら一旦切断して音を鳴らすことにします(???)。

バイスを自動選択する

aconnect -l -i で見つかったデバイスから aconnect -l -o で見つかったデバイスに流せばいいんでしょ?簡単じゃん。

pi@raspberrypi:~ $ aconnect -l -i
client 0: 'System' [type=kernel]
    0 'Timer           '
    1 'Announce        '
client 14: 'Midi Through' [type=kernel]
    0 'Midi Through Port-0'
client 24: 'SonicCell' [type=kernel,card=2]
    0 'SonicCell MIDI 1'
    1 'SonicCell MIDI 2'
client 28: 'EWI-USB' [type=kernel,card=3]
    0 'EWI-USB MIDI 1  '
pi@raspberrypi:~ $ aconnect -l -o
client 14: 'Midi Through' [type=kernel]
    0 'Midi Through Port-0'
client 24: 'SonicCell' [type=kernel,card=2]
    0 'SonicCell MIDI 1'
    1 'SonicCell MIDI 2'
client 28: 'EWI-USB' [type=kernel,card=3]
    0 'EWI-USB MIDI 1  '

見分けがつかん……!

仕方ないので少し妥協して、OUT側のデバイスだけ固定して、IN側のデバイスはOUTに使うデバイス以外から自動選択、ということにしました。

udevで自動接続

なにを基準にしようかと考えながら udevadm monitor -p を眺めてみると、 SUBSYSTEM=snd_seq といういい感じのがいますね。

UDEV  [7822.779437] add      /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/sound/card3/seq-midi-3-0 (snd_seq)
ACTION=add
DEVPATH=/devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/sound/card3/seq-midi-3-0
SUBSYSTEM=snd_seq
SEQNUM=1496
USEC_INITIALIZED=7822778562

冒頭のスクリプト/usr/local/bin/update-aconnect.sh に置かれているとして、 /etc/udev/rules.d/10-usbmidi.rules あたりにこんなファイルを生やします。

ACTION=="add", SUBSYSTEM=="snd_seq", RUN+="/usr/local/bin/update-aconnect.sh"
ACTION=="remove", SUBSYSTEM=="snd_seq", RUN+="/usr/local/bin/update-aconnect.sh"

あとは sudo udevadm control --reload で再読み込みして、抜き差しして音が鳴れば成功です。

再接続で色々リセットする

ここまで作って気づいたんですが、単純に aconnect で接続しなおすだけだと、色々な状態が残ったままになっています。発音中のノートだったり、変更されたCCだったり。コントローラーをつなぎ変えた時はそれらをリセットしてくれた方がいいような気がしてきました*2

というわけで、毎回オールノートオフとリセットオールコントローラーを流しておきましょう。

余談: 電源問題

ところでラズパイを動かすにも電源がいるわけですが、おや……この音源、なにやらUSB端子をもうひとつ持っていますね?

f:id:unarist:20211218153552p:plain:w480
USB MEMORY 端子
……試しにつないでみましょうか。
f:id:unarist:20211218153738p:plain:w480
動く…!
動いていますね。

この子の給電能力がどれぐらいかわかりませんが、とりあえず予定していたMIDIコントローラーをつないでも動いている*3ので、いいんじゃないでしょうか。多分。

最終的に、音源用のAC電源があれば一式動くようになりました。なかなかいい感じ。

PC不要なら演奏するのかって?それはまた別の話よ。

*1:Qiitaの Raspberry Pi Advent Calendar 2021 とは別です。見分けがつかないな?

*2:EWIがExpressionを絞ったままで、接続音が鳴らなかったことで気づいたやつ

*3:実はもっと手軽な方法として iPhone × Lightning - USBカメラアダプタ を試していたものの、200mA要求するEWI-USBは使えなかった。噂によると件のアダプタは100mAまでっぽい?

新しい人と新しい風

技術的な話ばかり書いてきたここに、こんなふんわりした話を書くものか少し迷ったけれど。

今の会社に入って少し時間がたって振り返ってみると、わたしがこの会社に入ったことで、色々と新しい風を吹き込めたんじゃないかという気がしている。

もちろんそれは別にわたしに限った話ではない。誰しもが多少なりとも他人と違う色を持っていて、ある集団にそれを加えることで色はまじりあい、あるいは単純な「混色」では説明できないような化学変化を引き起こしたりもする。めちゃくちゃ抽象化して言えば、任意の変更は何らかの影響を生むもの、でいいとも思うが。

そんななかでも、人並みというよりはもうちょっと、大きな影響を与えたというか、ちょっと新しい風を吹きこんだんじゃないかなという気がしている。自分が入る前のことは伝聞や各種資料でしか知らないから、具体的に比較をするのは難しい(といってサボる)けども、なんとなく、色々変わった気がするのだ。

そう思うと、わたしがここに来た甲斐がいくらかあったのかなと、ぼんやり思うのだ。

……なーんて甘い妄想に一瞬ふけった、というのは前段。

実際には、会社や同僚たちに対するその影響はマイナス面の方が大きかった、なんてことはないだろうか。さすがに、「任意の変化には何らかの価値がある」とするならマイナスしかなかった、とはならないと信じつつ。

自然と広まったのなら受け入れられていたのだからいいじゃないか、と言えれば簡単だけど、実際には色んなパワーバランスによって、じわじわと、そうなりやすかったとかもあるはずで。たとえば、なんだかんだよく喋っていたように思うので、それによって目立つことでどうのこうのとか、あるいはよく喋る人が他にいると控えめになるとか、そういう自然に起きている現象のたぐい。そもそも人と人の相性みたいなものは、そこにいるだけで影響があったりするので、今更なにをというのはそうなのだけど。そういう、無意識に発生しているであろうバイアスも、気にはなってしまう。

それでもやっぱり、新しい風を取り込むのはきっと大事なことなんだろうなーという。楽しいことが待ってるかもしれないし。たぶん、きっと。

最後に前段の話に戻しておくと、わたしが吹きこんだ風なんて微々たるもので、別になにも変わっちゃいないのかもしれない。まあもしお前のせいでまったくもう、と思っている関係者がいたら、もうしわけねえ。フィードバックしてくれてもいいのよ。