2017/04/30

TiMidity++で使える音源(GUS patch)“Eawplus”と自作インストーラ

MIDIシンセサイザTiMidity++で扱えるGUS patch形式の音源である “Eawplus” についてを扱う。

元の記事は2007年6月と2008年3月に書かれたが、自動でファイルをダウンロード・展開するスクリプトを追加するなど一部内容を書き直している。

  1. Eawplusについて
  2. ファイルのダウンロード
  3. ダウンロードと展開を自動化するPythonスクリプト

Eawplusについて

GUS patch形式の音源の中では高品質な “Eawpatches” をベースとして “出雲パッチ” と呼ばれる音源と幾つかの追加ファイル群を加えて調整し直した音色セットで、同形式の音源としては最も優れたものとなっている。

どちらの公式サイトも閉鎖されており、現在はアクセスできない。

TiMidity++で使用する設定ファイル(-cオプションで場所を指定)はtimidity/timidity.cfgとして配布ファイルの中に含まれているが、環境(配置場所)に応じて “dir” の行を編集する必要がある。

ファイルのダウンロード

ファイルのダウンロードは上記のWayback Machine上のページからも行えるが

  • EawpatchesはGentoo Linuxのミラーサーバ(tar.gz形式で配布)
  • 出雲パッチとEawplus用追加パッチはFreeBSDのミラーサーバ

にもそれぞれ存在するため、直接取得することもできる。

ファイル名説明
eawpats12_full.tar.gzEawpatches(Eric氏による)
guspat-20000706-required.tar.gz出雲パッチ(出雲氏による)の基本ファイル群
guspat-20000706-optional.tar.gz同パッチの追加ファイル群
eawplus-12.1.tar.gzEawplus用の追加パッチ(田向氏とSYUUHOU氏による)

Eawpatches本家配布のファイルはRAR形式なので、GNU/Linuxでは展開にunrarが必要。

ダウンロードと展開を自動化するPythonスクリプト

下のスクリプトを保存し、実行属性を付けてPython 3で実行すると、上記のファイル群をミラーサーバから全て自動的にダウンロードして、引数に指定したディレクトリ(要書き込み権限)以下にこれらを展開する。サーバからのダウンロードに失敗した場合はWayback Machine上のURLからのダウンロードを試みる。

--update-cfg(-u)オプションを付けて実行すると、展開後にtimidity/timidity.cfgdir行を書き換えて、そのままの場所で(TiMidity++から-cオプションで.cfgファイルの場所を指定するだけで)音源データが使用可能になり、更にEawpatches単体で使いたい場合向けのeawpats/timidity.cfgも同様に書き出す。

処理は全てPythonの標準機能で行っており、外部コマンドは使っていない。

プロキシサーバを使用している環境では小文字の環境変数http_proxyにサーバURLを指定して実行する。

[任意]ファイル名:get_eawplus.py ライセンス:MIT
#! /usr/bin/python3

# get_eawplus.py - Download/extract "Eawplus" GUS patch set
#                  (Eawpatches + Izumo [required + optional] + Eawplus)
# (C) 2017 kakurasan
# Licensed under MIT

from urllib.request import urlopen
import optparse
import tempfile
import tarfile
import locale
import shutil
import errno
import sys
import os


version = '20170430'

g_src = ('http://gentoo.gg3.net/distfiles/eawpats12_full.tar.gz',
         'http://distcache.freebsd.org/local-distfiles/nork/guspat-20000706-required.tar.gz',
         'http://distcache.freebsd.org/local-distfiles/nork/guspat-20000706-optional.tar.gz',
         'http://distcache.freebsd.org/local-distfiles/nork/eawplus-12.1.tar.gz')


class OptParser (optparse.OptionParser):
  _opts = \
  (
    optparse.make_option ('-u', '--update-cfg', dest = 'update_cfg', action = 'store_true', help = 'update .cfg files for TiMidity++', default = False),
  )
  _config = \
  {
    'usage'       : '%prog (-u/--update-cfg) (DIRECTORY)',
    'version'     : '%prog ' + version,
    'option_list' : _opts
  }
  def __init__ (self):
    optparse.OptionParser.__init__ (self, **self._config)

class Unsuccessful (Exception):
  def __init__ (self, message):
    self._message = message
  def __str__ (self):
    return self._message


def download_file (url, filename, fallback = False):
  print ('Downloading {0}{1} ...'.format (url, ' (fallback)' if fallback else ''))
  try:
    f_url = urlopen (url)
    try:
      with open (filename, 'wb') as f_out:
        while True:
          buf = f_url.read (8192)
          if not buf:
            break
          f_out.write (buf)
    finally:
      f_url.close ()
  except IOError as e:
    if not fallback:
      return download_file ('http://web.archive.org/web/' + url, filename, fallback = True)
    else:
      return 'Could not download "{0}": {1}'.format (url, e)

def main ():
  locale.setlocale (locale.LC_ALL, '')

  opt_parser = OptParser ()
  options, args = opt_parser.parse_args ()

  if len (args) > 0:
    outdir = args[0]
    try:
      os.makedirs (outdir)
    except OSError as e:
      if e.errno != errno.EEXIST:
        return 'Could not create directory "{0}": {1}'.format (outdir, e)
    try:
      os.chdir (outdir)
    except OSError as e:
      return 'Could not move to directory "{0}": {1}'.format (outdir, e)

  tempdir = tempfile.mkdtemp ()
  try:
    for url in g_src:
      filename = os.path.basename (url)
      filepath = os.path.join (tempdir, filename)

      # Download
      error = download_file (url, filepath)
      if error:
        print ('Download failed.')
        raise Unsuccessful (error)

      # Extract
      print ('Extracting {0} ...'.format (filename))
      try:
        with tarfile.open (os.path.join (tempdir, filename), 'r|gz') as f_tar:
          f_tar.extractall ()  # Some files are not extracted if Python 2 is used
      except Exception as e:
        raise Unsuccessful ('Could not extract file "{0}": {1}'.format (url, e))
      print ('Deleting {0} ...'.format (filename))
      os.unlink (filepath)

    # Update .cfg files if the option '-u' ('--update-cfg') is specified
    if options.update_cfg:
      # Write timidity.cfg for Eawpatches
      cwd = os.getcwd ()
      cfg_path = os.path.join ('eawpats', 'timidity.cfg')
      print ('Writing {0} ...'.format (cfg_path))
      try:
        with open (cfg_path, 'w', encoding = 'ascii') as f_out_cfg:
          f_out_cfg.write ('dir {0}\n'.format (os.path.join (cwd, 'eawpats')))
          for name in ('gravis', 'gsdrums', 'gssfx', 'xgmap2'):
            f_out_cfg.write ('source {0}.cfg\n'.format (name))
      except IOError as e:
        raise Unsuccessful ('Could not write to file "{0}": {1}'.format (cfg_path, e))

      # Update timidity.cfg for Eawplus
      cfg_temp_path = os.path.join (tempdir, 'timidity.cfg')
      cfg_path = os.path.join ('timidity', 'timidity.cfg')
      print ('Updating {0} ...'.format (cfg_path))
      try:
        with open (cfg_path, 'r', encoding = 'ascii') as f_in_cfg:
          with open (cfg_temp_path, 'w', encoding = 'ascii') as f_out_cfg:
            for l in f_in_cfg:
              if l.startswith ('dir '):
                l = l.replace ('/usr/share/timidity/eawpats', os.path.join (cwd, 'eawpats'))
                l = l.replace ('/usr/share/timidity', cwd)
              f_out_cfg.write (l)
        os.unlink (cfg_path)
        shutil.move (cfg_temp_path, 'timidity')
      except IOError as e:
        raise Unsuccessful ('Could not update file "{0}": {1}'.format (cfg_path, e))
  except Unsuccessful as e:
    return e
  finally:
    print ('Deleting temporary directory ...')
    shutil.rmtree (tempdir)
  print ('Done.')


if __name__ == '__main__':
  sys.exit (main ())
実行例
(現在の階層にeawplusディレクトリを作ってその中に配置する・設定ファイルを更新)
$ /path/to/get_eawplus.py -u eawplus

(現在の階層に直接配置する・設定ファイルは更新しない)
$ /path/to/get_eawplus.py

全ての処理に成功すると、配置ディレクトリには以下のディレクトリが生成される。

ディレクトリ説明
docドキュメント類(動作には必須ではない)
eawpatsEawpatches
timidity出雲パッチとEawplus
使用したバージョン:
  • Python 3.5.3
  • TiMidity++ 2.13.2