2016/01/15

WineにおけるMIDIデバイスの扱い

ここではWineにおけるMIDIデバイスの扱いについてを扱う。

MIDIマッパー関係の内容は2008年9月と2009年10月に書かれたが、MIDIデバイスの扱い全般に内容を広げた上で、MIDIマッパーのGUI設定ツールの自作やコマンドによる同設定の指定例についても扱っている。また、FluidSynth/Qsynthを音源として使用する場合の注意点についての内容も追加している。

  1. WindowsアプリケーションにおけるMIDIデバイスの扱いの分類
    1. 出力先デバイスが選択可能なもの
    2. 出力先デバイスがOS設定で決まるもの
    3. Microsoft Synthesizerを用いるもの
  2. ALSA対応のハードウェアMIDI音源やソフトウェアシンセサイザを用いる
    1. FluidSynth/Qsynth使用時の注意点
  3. MIDIマッパー設定
    1. コマンドによる設定
    2. 自作GUIツールによる設定
  4. Microsoft Synthesizerの動作についての問題と対処
  5. Microsoft GS Wavetable Synth

WindowsアプリケーションにおけるMIDIデバイスの扱いの分類

出力先デバイスが選択可能なもの

MIDIシーケンサやMIDIプレーヤの一部などではアプリケーション内の設定項目で出力先のMIDIデバイスが指定でき、見出し:ALSA対応のハードウェアMIDI音源やソフトウェアシンセサイザを用いることができる。

出力先デバイスがOS設定で決まるもの

アプリケーション内に出力先MIDIデバイスの選択機能を持たない一部のMIDIプレーヤやゲームなどでは、Windows OSの “MIDIマッパー” と呼ばれる機能によってMIDIマッパー設定(Windows OSの設定としての出力先MIDIデバイス設定)に基づいて出力先のデバイスが決まる。

見出し:MIDIマッパー設定を行うと見出し:ALSA対応のハードウェアMIDI音源やソフトウェアシンセサイザを用いることができる。

Microsoft Synthesizerを用いるもの

DirectMusicに対応するMIDIプレーヤやゲームには “Microsoft Synthesizer” と呼ばれるソフトウェアシンセサイザを(アプリケーションによっては強制的に)用いてMIDIデータを再生するものがある。

見出し:Microsoft Synthesizerの動作についての問題と対処を参照。

一部のMIDIプレーヤではMicrosoft Synthesizerを含めてMIDIデバイスが選択できる。

ALSA対応のハードウェアMIDI音源やソフトウェアシンセサイザを用いる

Wineのサウンドドライバ(出力バックエンド)がALSAもしくはPulseAudio[1]の場合、Windows用のMIDIアプリケーションをWineで動かしたときにALSAのサウンドアーキテクチャに基づいたMIDIデバイスが使用できる。ALSAドライバが対応しているハードウェアMIDI音源はもちろん、FluidSynth(GUIの場合はQsynth)やTiMidity++といったソフトウェアシンセサイザについても(動かしている間は)ALSAのMIDIデバイスとして使用でき、これらは全てWine上のMIDIアプリケーションから使用できる。

FluidSynth/Qsynth使用時の注意点

サウンドフォントを用いるソフトウェアシンセサイザFluidSynth(GUIのQsynth含む)ではプロセスIDをALSAのMIDIポート名に含める形で動作するモードがあり、この形で動かすとポート名が固定されないため、Wine上のMIDIアプリケーションで出力先デバイスが起動の度に変化してしまい、次に起動したときに出力先を設定し直す必要が出てくる(MIDIマッパーを用いるアプリケーションから出力する場合も同様)。

これでは使い勝手が悪いため、プロセスIDをポート名に含まないようにして動かすようにする。

FluidSynthの場合は--portname=オプションで名前を付ける。

(FluidSynthの起動例)
$ fluidsynth --server --no-shell --audio-driver=pulseaudio --midi-driver=alsa_seq --portname=FluidSynth1 /path/to/soundfont.sf2

Qsynthでは、シンセサイザの各エンジンのMIDI設定において、MIDIクライアント名のIDにプロセスIDを用いる指定(pid)*以外*が選択されていればよい。

これらのソフトウェアは、MIDIアプリケーションを起動する前に開始し、かつMIDIアプリケーションを終了するまでは動かし続ける必要がある。

MIDIマッパー設定

MIDIマッパーの機能自体はWineでも実装されているが、バージョン1.8時点では設定値の編集には直接レジストリを編集する必要があり、GUI設定ツールは存在しない。

レジストリ項目の階層(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap)自体はWindowsと同じものが使用されるが、Wineのバージョン1.8時点ではWindows 9x系(NT系ではない古い系統)の形(デバイス名の設定項目名が “CurrentInstrument”)を用いており、NT系[2]のWindows(設定項目名が “szPname”)向けのGUIのMIDIマッパー設定ツールを使ってもWineでは設定が反映されない。

(2021/7/31)バージョン6.14以上のWineではこの問題は修正され、GUIのMIDIマッパー設定ツール “MIDIせれくたー” などが書き込むレジストリ設定をWineが使用するようになった。ソースによると、古いレジストリ(CurrentInstrument)も引き続きサポートされるが、新しいレジストリ(szPname)が同時に存在する場合はこちらが優先されるようだ。

コマンドによる設定

下はコマンドでの設定例となる。

(Qsynthの1番エンジンの0番ポートを指定)
$ (WINEPREFIX=[Wine環境の場所]) wine reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap" /v "CurrentInstrument" /t REG_SZ /d "Synth input port (Qsynth1:0)"
ADD - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap CurrentInstrument 0 REG_SZ Synth input port (Qsynth1:0) 0
操作は正常に完了しました

(設定を解除)
$ (WINEPREFIX=[Wine環境の場所]) wine reg delete "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap" /v "CurrentInstrument"
DELETE - HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap CurrentInstrument 0 0 0
操作は正常に完了しました

自作GUIツールによる設定

以前、既存のWindows用のMIDIマッパー設定変更ツールを改造することによるGUIツールでの設定変更についてを扱ったが、ソースの入力から修正、ビルドまでが色々と手間になる上にコードがシンプルではないので、一からプログラムを自作してみた。

[任意]ファイル名:winemidisel.c ライセンス:パブリックドメイン
/*
 * Wine MIDI Mapper GUI setting tool (Public Domain)
 *
 * Winelib:
 *   winegcc -O2 -Wall winemidisel.c -o winemidisel.exe.so -lwinmm -mwindows
 * Windows:
 *   x86_64-w64-mingw32-gcc -O2 -Wall winemidisel.c -o winemidisel.exe -lwinmm -mwindows
 *   i686-w64-mingw32-gcc -O2 -Wall winemidisel.c -o winemidisel.exe -lwinmm -mwindows
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>

LRESULT CALLBACK
WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  UINT i, devnum;
  static HFONT hFont = NULL;
  static HWND hCombo;

  switch (msg)
  {
  case WM_DESTROY:
    if (hFont != NULL)
      DeleteObject (hFont);
    PostQuitMessage (0);
    break;
  case WM_CREATE:
    hFont = CreateFont (14, 0, 0, 0, FW_REGULAR,
                        FALSE, FALSE, FALSE,
                        DEFAULT_CHARSET,
                        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                        DEFAULT_QUALITY,
                        DEFAULT_PITCH | FF_DONTCARE,
                        "MS UI Gothic");
    hCombo = CreateWindow ("COMBOBOX", NULL,
                           WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST,
                           0, 0, 300, 100,
                           hWnd, NULL, ((LPCREATESTRUCT) lParam)->hInstance, NULL);
    SendMessage (hCombo, WM_SETFONT, (WPARAM) hFont, FALSE);

    devnum = midiOutGetNumDevs ();
    if (devnum > 0)
    {
      LONG lResult;
      HKEY hMIDIMap;
      DWORD dwSize = MAXPNAMELEN - 1;
      TCHAR szCurrentDev[MAXPNAMELEN] = {0};

      /* Query current setting */
      lResult = RegOpenKeyEx (HKEY_CURRENT_USER,
                              "Software\\Microsoft\\Windows\\CurrentVersion\\Multimedia\\MIDIMap",
                              0,
                              KEY_ALL_ACCESS, &hMIDIMap);
      if (lResult == ERROR_SUCCESS)
        lResult = RegQueryValueEx (hMIDIMap, "CurrentInstrument", 0, NULL,
                                   (LPBYTE) szCurrentDev, &dwSize);
      RegCloseKey (hMIDIMap);

      /* Add items to combo box */
      SendMessage (hCombo, CB_ADDSTRING, 0, (LPARAM) "--- Select ---");
      for (i = 0; i < devnum; i++)
      {
        MIDIOUTCAPS c;

        midiOutGetDevCaps (i, &c, sizeof (c));
        SendMessage (hCombo, CB_ADDSTRING, 0, (LPARAM) c.szPname);
      }

      /* Set active item */
      lResult = SendMessage (hCombo, CB_FINDSTRINGEXACT, -1, (LPARAM) szCurrentDev);
      SendMessage (hCombo, CB_SETCURSEL, (lResult != CB_ERR) ? (WPARAM) lResult : 0, 0);
    }
    break;
  case WM_COMMAND:
    if (HIWORD (wParam) == CBN_SELCHANGE)
    {
      /* The active item is changed */
      LONG_PTR idx;
      TCHAR szSelected[MAXPNAMELEN];

      idx = SendMessage (hCombo, CB_GETCURSEL, 0, 0);
      if (idx != CB_ERR)
      {
        LONG_PTR len;
        LONG lResult;
        HKEY hCurrentVersion, hMultimedia, hMIDIMap;

        /* Get device name */
        if (idx > 0)
          len = SendMessage (hCombo, CB_GETLBTEXT, idx, (LPARAM) szSelected);

        /* Create registry keys */
        lResult = RegOpenKeyEx (HKEY_CURRENT_USER,
                                "Software\\Microsoft\\Windows\\CurrentVersion",
                                0,
                                KEY_ALL_ACCESS, &hCurrentVersion);
        if (lResult != ERROR_SUCCESS)
          break;
        lResult = RegCreateKeyEx (hCurrentVersion, "Multimedia", 0, NULL,
                                  REG_OPTION_NON_VOLATILE,
                                  KEY_ALL_ACCESS, NULL, &hMultimedia, NULL);
        if (lResult != ERROR_SUCCESS)
          goto close_currentversion;
        lResult = RegCreateKeyEx (hMultimedia, "MIDIMap", 0, NULL,
                                  REG_OPTION_NON_VOLATILE,
                                  KEY_ALL_ACCESS, NULL, &hMIDIMap, NULL);
        if (lResult != ERROR_SUCCESS)
          goto close_multimedia;

        /*
         * "--- Select ---" -> clear
         * MIDI device name -> save
         */
        if (idx > 0)
          RegSetValueEx (hMIDIMap, "CurrentInstrument", 0, REG_SZ,
                         (LPBYTE) szSelected, (len + 1) * sizeof (TCHAR));
        else
          RegDeleteValue (hMIDIMap, "CurrentInstrument");

        RegCloseKey (hMIDIMap);
      close_multimedia:
        RegCloseKey (hMultimedia);
      close_currentversion:
        RegCloseKey (hCurrentVersion);
      }
    }
    break;
  default:
    return DefWindowProc (hWnd, msg, wParam, lParam);
  }

  return 0;
}

int WINAPI
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASS wc;
  MSG msg;

  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor (NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = "WINEMIDISEL";

  if (!RegisterClass (&wc))
    return -1;

  if (CreateWindow ("WINEMIDISEL", "Wine MIDI Mapper config",
                    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                    CW_USEDEFAULT, CW_USEDEFAULT, 350, 100,
                    NULL, NULL, hInstance, NULL) == NULL)
    return -1;

  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }

  return msg.wParam;
}

コンパイルにはWineの開発パッケージ(もしくはWindowsを動作の対象としたクロスコンパイラ)が必要。

(コンパイル例)
$ winegcc -O2 -Wall winemidisel.c -o winemidisel.exe.so -lwinmm -mwindows

(実行例)
$ (WINEPREFIX=[Wine環境の場所]) wine /path/to/winemidisel.exe.so

コンパイルして実行するとコンボボックスのみを含んだウィンドウが表示され、このコンボボックスにはWineで認識されているMIDIデバイスの一覧が含まれている。この項目を変更することでMIDIマッパーの出力先が直ちに変更され、一番上の “--- Select ---” という項目を選択すると設定が解除される(未指定状態になる)。

WineのMIDIマッパー設定を行う自作GUIツール

Microsoft Synthesizerの動作についての問題と対処

Wineで “Microsoft Synthesizer” を使用する場合、このシンセサイザから使用される音源ファイルgm.dlsが提供されないことに加えてWine版のDirectMusic関係のDLL群の実装が不十分であることにより、そのままではMIDIデータが正しく再生されない(音が出ない)。

これらの問題はWinetricksで対処可能となっており、前者は “gmdls” を選択することでDirectXの古い再頒布可能パッケージから取り出して適切な場所[3]に配置するところまでが自動的に行われ、後者は “directmusic” を選択することで同様にDirectXの古い再頒布可能パッケージからネイティブ版[4]のDLL群を取り出して配置し、Wine版の代わりに用いるための設定までが自動的に行われる。

アプリケーションによっては更にリバーブ効果をかけており、dsdmo.dllというDLLを追加で使用する。これはWinetricksで “dsdmo” を選択することでDirectXの古い再頒布可能パッケージから取り出して配置するまでが自動的に行われる。[5]

(Microsoft SynthesizerがWineで正しく動作するようにする)
$ (WINEPREFIX=[Wine環境の場所]) winetricks gmdls directmusic dsdmo

Microsoft GS Wavetable Synth

WindowsではMIDIアプリケーションからDirectMusicを用いずにgm.dlsの音を鳴らすための “Microsoft GS Wavetable Synth” [6]というソフトウェアシンセサイザがMIDIデバイスとして使用可能となっているが、これと同等のものはWineには(バージョン1.8時点では)存在せず、DirectMusicに対応していないプレーヤやゲームなどから同音源ファイルの音を鳴らすことはできない。

一方で、DirectMusic対応のMIDIプレーヤを動かしてMIDIファイルを再生することでgm.dlsの音を鳴らすことはできる。

使用したバージョン:
  • FluidSynth 1.1.6
  • Qsynth 0.3.9
  • Wine 1.6.2, 1.8
[1]: GNU/Linuxでは基本的にPulseAudioが選択される
[2]: NT/2000/XP以降
[3]: [仮想Cドライブ]/windows/[system32 もしくは syswow64]/drivers/以下で、Wine環境が32bitのみの対応か64bit対応かで異なる
[4]: 32bit版のみ含まれており、Winetricksで64bit版をインストールすることはできない
[5]: これも32bit版のみ
[6]: いわゆる “ゲイツシンセ” で、Windows Vistaより前は “Microsoft GS Wavetable SW Synth” という名前だった