試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Wineで複数のMIDIポートを持つALSA MIDIデバイスのポートが1つしか使えない問題に対処

(2008/8/23)この記事の不具合に関しては、Wine上で認識されるMIDIバイス名がWineバージョン1.1.3から「[クライアント名] - [ポート名]」の組み合わせの形で表現されるようになったため、このバージョン以上であれば解消されている。

以下は、バージョン1.1.2以下に関する内容となる。

  1. ALSAでのMIDIデバイスとポート
  2. Wineでの認識
  3. aplaymidiでは「Client name」と「Port name」をどのように取得しているか
  4. Wineのソース修正
  5. ebuildの修正例
  6. 結果

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));
		}
	}
}

ALSAAPIのことはさっぱり分からないが、名前を取得している関数が

  • 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のソース修正

WineMIDI関係のソースは、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 Linuxebuild修正例。

--- /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

*1:再生ポートが「PART A」と「PART B」の2つある

*2:1パート演奏でも、常にPART Aが使用され、PART Bで鳴らすことはできない