Wineで複数のMIDIポートを持つALSA MIDIデバイスのポートが1つしか使えない問題に対処
(2008/8/23)この記事の不具合に関しては、Wine上で認識されるMIDIデバイス名がWineのバージョン1.1.3から「[クライアント名] - [ポート名]」の組み合わせの形で表現されるようになったため、このバージョン以上であれば解消されている。
以下は、バージョン1.1.2以下に関する内容となる。
- ALSAでのMIDIデバイスとポート
- Wineでの認識
- aplaymidiでは「Client name」と「Port name」をどのように取得しているか
- Wineのソース修正
- ebuildの修正例
- 結果
ALSAでのMIDIデバイスとポート
ALSAでは、複数のMIDIポート(16パート単位)を持つ音源に対して、ポートごとに番号が割り当てられ、それぞれにポート名も存在する。「alsa-utils」パッケージに含まれるaplaymidiコマンドに-lオプションを付けて実行すると、利用可能なMIDIデバイスとポートの一覧が表示されるのだが、手元のSC-8820という音源*1では、
$ aplaymidi -l Port Client name Port name 20:0 SC-8820 SC-8820 Part A 20:1 SC-8820 SC-8820 Part B 20:2 SC-8820 SC-8820 MIDI
このような表示になる。
複数ポートでの演奏も可能で、aplaymidiを使用する場合、
$ aplaymidi -p 20:0,20:1 [2ポートを使用しているMIDIデータ]
のようになる。
Wineでの認識
Wineでは、ALSA MIDI APIにおけるMIDIデバイスをWindows APIにおけるMIDIデバイスとして使用し、認識されて表示されるポートの数はALSA上(で認識される数)と同じなのだが、その名前は全て同一で、使えることは使えるのだが、TMIDI Player上でポートを設定しても、2ポート演奏がされない。*2
レジストリを見てみると、MIDIデバイス名の設定は
[Software\\Fummy\\TMIDI Player] ... "PORT_A"="SC-8820" "PORT_B"="SC-8820" "PORT_C"="(None)" "PORT_D"="(None)" "PORT_E"="(None)" "PORT_F"="(None)"
となっていて、別々に設定したはずなのに同じものとして扱われているように見えた。
2つあるポートが区別できていないのではないかと思ったのだが、よく見ると、上のaplaymidiで「Client name」として表示されていた部分(手元の音源では「SC-8820」という文字列)が使用されていることが分かり、これを「Port name」のほうの文字列(手元の音源では「SC-8820 Part A」などの文字列)を扱うような修正ができれば、この問題は解決できるのではないか、ということになった。
この音源とTimidity++とを組み合わせると2ポート演奏ができ、このことからも、その疑いが強まった。
aplaymidiでは「Client name」と「Port name」をどのように取得しているか
[引用]ファイル名: alsa-utils-1.0.15/seq/aplaymidi/aplaymidi.c よりstatic void list_ports(void) { snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; (中略) printf("%3d:%-3d %-32.32s %s\n", snd_seq_port_info_get_client(pinfo), snd_seq_port_info_get_port(pinfo), snd_seq_client_info_get_name(cinfo), snd_seq_port_info_get_name(pinfo)); } } }
ALSAのAPIのことはさっぱり分からないが、名前を取得している関数が
- Client name: const char *snd_seq_client_info_get_name(snd_seq_client_info_t *info)
- Port name: const char *snd_seq_port_info_get_name(const snd_seq_port_info_t *info)
であることは分かった。
Wineのソース修正
WineのMIDI関係のソースは、dlls/winealsa.drv/midi.c。このファイルの中で、snd_seq_client_info_get_name()関数を探し出して、snd_seq_port_info_get_name()関数に置き換えればよいはず。デバッグ出力の部分は置き換えずに除外すると、
$ sed -i 's/\(MultiByteToWideChar(CP_ACP, 0, snd_seq_\)client\(_info_get_name\)(cinfo)/\1port\2(pinfo)/' dlls/winealsa.drv/midi.c
だけを実行すればよいことが分かった。diffの形で書くと下のようになる。
--- wine-0.9.49/dlls/winealsa.drv/midi.c.orig +++ wine-0.9.49/dlls/winealsa.drv/midi.c @@ -1146,7 +1146,7 @@ * not MIDICAPS_CACHE. */ MidiOutDev[MODM_NumDevs].caps.dwSupport = MIDICAPS_VOLUME|MIDICAPS_LRVOLUME; - MultiByteToWideChar(CP_ACP, 0, snd_seq_client_info_get_name(cinfo), -1, + MultiByteToWideChar(CP_ACP, 0, snd_seq_port_info_get_name(pinfo), -1, MidiOutDev[MODM_NumDevs].caps.szPname, sizeof(MidiOutDev[MODM_NumDevs].caps.szPname) / sizeof(WCHAR)); @@ -1201,7 +1201,7 @@ * not MIDICAPS_CACHE. */ MidiInDev[MIDM_NumDevs].caps.dwSupport = MIDICAPS_VOLUME|MIDICAPS_LRVOLUME; - MultiByteToWideChar(CP_ACP, 0, snd_seq_client_info_get_name(cinfo), -1, + MultiByteToWideChar(CP_ACP, 0, snd_seq_port_info_get_name(pinfo), -1, MidiInDev[MIDM_NumDevs].caps.szPname, sizeof(MidiInDev[MIDM_NumDevs].caps.szPname) / sizeof(WCHAR)); MidiInDev[MIDM_NumDevs].state = 0;
ebuildの修正例
Gentoo Linuxのebuild修正例。--- /usr/portage/app-emulation/wine/wine-0.9.49.ebuild.orig +++ /usr/portage/app-emulation/wine/wine-0.9.49.ebuild @@ -68,6 +68,7 @@ sed -i '/^UPDATE_DESKTOP_DATABASE/s:=.*:=true:' tools/Makefile.in epatch "${FILESDIR}"/wine-gentoo-no-ssp.patch #66002 sed -i '/^MimeType/d' tools/wine.desktop || die #117785 + sed -i 's/\(MultiByteToWideChar(CP_ACP, 0, snd_seq_\)client\(_info_get_name\)(cinfo)/\1port\2(pinfo)/' dlls/winealsa.drv/midi.c || die } config_cache() {
ダイジェストを取ってインストール。
$ sudo ebuild /usr/portage/app-emulation/wine/wine-[バージョン].ebuild digest $ sudo emerge -av wine
結果
無事にポート名が適用され、TMIDI Playerでの複数ポート再生ができることを確認。TiMidity++をALSA MIDIデバイスとして使用している場合も、同様に複数ポートでの演奏ができることを確認。ポートの名前も、aplaymidiで取得したものと同じものが表示されている。
使用したバージョン:
- Wine 0.9.48, 0.9.49