ルギア君の戯言

雑多な記事。

ポケダンのスクラッチくじ

ものの本性を見たくなるのは常にあることである(謎)。


真相は「たいようのいし」が欲しい (景品の中にあるのかも「僕は」知らないが…たしか「つきのいし」は見かけた)。
LV100 になるまえに絶対チュリネを進化させるぞ! と思っていたのだが、LV100 になるほうが残念ながら先になってしまった。『ドレディアを直接仲間にしろ』という話は受け付けないのでよろしく。


実力が伴う「ホッケー」や「潜水」は問題外として、くじはランダムだから、目的の物をゲットするのが少しむずかしいと思うのね。本当はダンジョン内で見つかるのが一番早いけど…。


なぜスクラッチかというと、

  • スピードくじはくじびきけんをとってくるのが面倒
  • けんしょうくじはダンジョンに行かないといけないのが面倒

というわけ。まあ、お金も 9,999 しか持てないからこれをいっぱいやっていると結構頻繁にあずかりボックスまで戻ることになるが…。

確認事項

  • 店員さんは乱数に無関係
  • 複数建設する理由もまずない
  • 1枚のスクラッチカードに対して、数学的なあたる確率は \frac{3}{5}。大当たりする確率は \frac{1}{5}。計算は省略。
  • 削る場所を選り好みしない。
  • ある特定の景品をゲットするのに何回くじをやれば良いかを考える。

ゲームから未確認の前提条件

  • 景品の種類があたり用、おおあたり用それぞれ別の 40 種類と仮定
    • 実際にカウントする際には、「てきしばりだま」「てきしばりだま×2」は別物と考える。
    • ここでは番号できめることにする (1〜80)。
  • 乱数にメルセンヌツイスタを使う
    • アルゴリズム以外にもどこからどのように使うかも問題はある。
    • メルセンヌツイスタなのは下で出てくる Ruby がそうだから。それだけ。

そこで、したのようなプログラムを作る (このプログラムは Ruby 1.9 以降でしか動かない上、いくつか問題を抱えています)。

#!/usr/bin/ruby

# カード (1 がマークのあるところ)
card = [0, 0, 1, 1, 1, 1]

# 景品 (1〜40 は あたり用、41〜80 はおおあたり用)
award = {
  :win     => (1..40).to_a,
  :jackpot => (41..80).to_a
}

# ゲットした景品
get  = []
# ほしい景品
want = 51

# 統計情報用 (それぞれ得た回数)
data = {
  :lose    => 0,
  :win     => 0,
  :jackpot => 0,

  :got_time => [],
  :got_time_max => nil,
  :got_time_min => nil,
}

# 1000回トライしてみて、取れるまでの平均を求める。
1000.times do
  # 得た景品をリセット
  get = []

  loop do |i|
    # 景品を決める
    aw = {
      :win => award[:win].sample,
      :jackpot => award[:jackpot].sample
    }

    # カードを作る
    card = card.shuffle

    # 削るところを選ぶ
    kezuri = card.sample(3)

    # 判定
    case kezuri.inject(0) { |s, m| s + m }
    when 1
      data[:lose] += 1
    when 2
      data[:win] += 1
      get << aw[:win]
    
      # 欲しい景品ならループを抜ける
      break if aw[:win] == want
    when 3
      data[:jackpot] += 1
      get << aw[:jackpot]
      break if aw[:jackpot] == want
    end
  end

  # 集計
  s = get.size
  data[:got_time] << s
  data[:got_time_max] ||= s
  data[:got_time_min] ||= s
  if data[:got_time_max] < s then
    data[:got_time_max] = s
  end
  if data[:got_time_min] > s then
    data[:got_time_min] = s
  end
end

# 結果を表示
puts "LOSE   : %7d" % data[:lose]
puts "WIN    : %7d" % data[:win]
puts "JACKPOT: %7d" % data[:jackpot]
puts ""
ave = data[:got_time].inject(0) { |s, m| s + m } / data[:got_time].size.to_f
puts "Average Challenge to get #{want}: #{ave}"

実行すると、

$ ruby scratch.rb
LOSE   :   40570
WIN    :  121298
JACKPOT:   40242

Average Challenge to get 51: 161.54
Maximum Challenge to get 51: 1622
Minimum Challenge to get 51: 1

となります。最悪で 1600 回もやらないと得られないとか、だるすぎる。


次、その景品が「あたり」の場合。

$ ruby scratch.rb
LOSE   :   13599
WIN    :   41162
JACKPOT:   13697

Average Challenge to get 11: 54.859
Maximum Challenge to get 11: 539
Minimum Challenge to get 11: 1

これでも最悪で 539 とかまで行ってますね。当然、あたる確率が3倍なので、必要な挑戦回数は3分の1で済みますね。また景品の種類が倍なら、必要な挑戦回数も倍になります。


ただ具体的な目安が欲しかったという話。


PS: そうとう金をつぎ込んだけど、やっぱり景品にない…?

Ruby バグ?

CentOS 6.3。CentOS 5.4、CentOS 5.6 でも発症する。再現性取れなさそうなぐらい奇怪な現象が発生してます。

とりあえずメモ程度。

症状

初期化の時点 (標準入力を読み始める前) で LoadError が発生する。内容は起動するたびに変わる。

$ /usr/bin/ruby19
/usr/bin/ruby19: No such file or directory -- �B (LoadError)
$ /usr/bin/ruby19                     
/usr/bin/ruby19: No such file or directory --  (LoadError)  
$ /usr/bin/ruby19                     
/usr/bin/ruby19: No such file or directory -- ��M (LoadError)
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory --@� (LoadError)  
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory --0� (LoadError)  
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory -- (LoadError)    
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory -- �o) (LoadError)
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory --  (LoadError)   
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory -- � (LoadError)  
$ /usr/bin/ruby19                      
/usr/bin/ruby19: No such file or directory -- ▒ (LoadError)  

バイナリ的な部分が出てるので inspect させるとこんな感じ。

$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \u001A (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory --  (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory --  (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \xE8\u000F \u0001 (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \xE8\u007F6\u0002 (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \u001A (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory --  (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \b \xDC (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory --  (LoadError)\n"
$ /usr/bin/ruby19 2>&1 | ruby -e "ARGF.each { |d| puts d.inspect }"
"/usr/bin/ruby19: No such file or directory -- \b \xF0 (LoadError)\n"

-e オプションで起動すると LoadError ではなく TypeError になる。(別のバグかもしれない)

$ ruby19 -e ''
ruby19: wrong argument type Object (expected Data) (TypeError)

--help は正常に終了する。

$ ruby19 --help
Usage: ruby19 [switches] [--] [programfile] [arguments]
  -0[octal]       specify record separator (\0, if no argument)
  -a              autosplit mode with -n or -p (splits $_ into $F)
  -c              check syntax only
  -Cdirectory     cd to directory, before executing your script
(snip)
  -W[level=2]     set warning level; 0=silence, 1=medium, 2=verbose
  -x[directory]   strip off text before #!ruby line and perhaps cd to directory
  --copyright     print the copyright
  --version       print the version

バージョン情報

$ ruby19 -v
ruby 1.9.3p327 (2012-11-10 revision 37606) [x86_64-linux]
$ ruby -v
ruby 1.9.3p327 (2012-11-10 revision 37606) [x86_64-linux]
$ gcc -v
Using built-in specs.
Target: x86_64-redhat-linux
コンフィグオプション: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
スレッドモデル: posix
gcc version 4.4.6 20120305 (Red Hat 4.4.6-4) (GCC)

発現するタイミング

不明

  • インストールしたての頃は問題なく動作するが、半日から1日経って起動すると発現するようになる。
  • インストールしなおせば解消される (リビルドも不要)。が、また半日か経つと発現する。

回避方法

その他

  • Shared library を使った場合はどうなるかは不明 (上はどちらも static linked な ruby と ruby19)
  • ECC メモリの効果はない模様 (上のログは非 ECC だが、ECC の 5.6 でも発症した)。
  • -g まではやる暇がなかった。
  • 単純にビルドに失敗している…のかも。
  • 2.0.0dev で発現するかも不明
  • Momonga Linux での -O3 でビルドしたものについて発症するかどうかを誰か見たとは思うが、僕はその結果を知らないし、自分で試してもない。

Momonga Linux に Skype を入れる

他の人誰かやっているとは思うけど、Wiki にはなさそうなのでメモ。


Skype は 32 bit 版しか rpm が提供されていないため、32 bit 版の人は楽ですが、64 bit 版の人は針山を登る覚悟で作業してください。なお、PowerPCPowerPC 64 の方々は問題外ですので悪しからず。


以下 64 bit 環境 / development の 2012/12/29 現在の最新バイナリで説明する。
development へのアップデートの方法は こちら


Momonga Linux で、Nonfree 提供している lame を自分でビルドしてインストールしていない場合は (usolame のままの場合) は lame をビルドする作業を飛ばせますので、飛ばしてください。なお、usolame のままの場合も、デフォルトでは /etc/yum.conf で除外する設定になってますので外す必要があります。

レポジトリの設定

development の 32bit バイナリのレポジトリを足します。momonga-trunk の内容は basearch に依存するため、mirrorlist から 32bit 版のバイナリを取ってくることができません。直接 URL 指定してください。可能であれば、これもミラーをご利用ください。(ミラー一覧)


なお、この設定はプロジェクトとしてはサポートを得られなくなるのでご注意を。まあ、そもそも開発版だし。

# 64 ビット
[momonga-devel-x86_64]
name=Momonga Linux devel - x86_64
#baseurl=http://dist.momonga-linux.org/pub/momonga/development/x86_64/os/
mirrorlist=http://www.momonga-linux.org/download/mirrors/momonga-trunk
enabled=1
metadata_expired=1d
gpgcheck=0

# 32 ビット
[momonga-devel-i686]
name=Momonga Linux devel - $basearch
baseurl=http://dist.momonga-linux.org/pub/momonga/development/i686/os/
#mirrorlist=http://www.momonga-linux.org/download/mirrors/momonga-trunk
enabled=1
metadata_expired=1d
gpgcheck=0

なお、yum でのインストールに際し、! がついているもの

! gcc.i686

は、x86_64 とファイルが被っており、yum が Conflict と認識するものです。
これらは依存するパッケージを yum でインストールしたあと、rpm ファイルを直接レポジトリからダウンロードしてきて rpm -ivh --force でインストールします。


作業例。

# yum install gcc.i686       # 依存するパッケージを確認する (N でキャンセルして良い)
# yum install gmp.i686 libgomp.i686 libmpc.i686 mpfr.i686 ppl.i686 ppl-pwl.i686  # 依存するパッケージをインストール
# wget ftp://dist.momonga-linux.org/pub/momonga/development/i686/os/Packages/gcc-4.6.4-0.20121207.1m.mo8.i686.rpm
# rpm -ivh --force gcc-4.6.4-0.20121207.1m.mo8.i686.rpm
# yum reinstall gcc.x86_64   # 被っているファイルを x86_64 のものと取り替えたい場合

なお、バイナリファイル (/bin/*, /usr/bin/*, /usr/sbin/* など) の重複と、ファイルが同一の場合はエラー報告しないようです。


多い場合の減らす方法としては、yumトランザクションログをみて、ひとつのパッケージに対してこのようなメッセージが出ます。

---> パッケージ ffmpeg.i686 0:0.10.4-1m.mo8 は インストール です
--> 依存性の処理をしています: libx264.so.125 のパッケージ: ffmpeg-0.10.4-1m.mo8.i686
--> 依存性の処理をしています: libvpx.so.1 のパッケージ: ffmpeg-0.10.4-1m.mo8.i686
    (省略)
--> 依存性の処理をしています: libSDL-1.2.so.0 のパッケージ: ffmpeg-0.10.4-1m.mo8.i686
--> トランザクションの確認を実行しています。

この直後に続く

--> トランザクションの確認を実行しています。
---> パッケージ SDL.i686 0:1.2.15-1m.mo8 は インストール です
---> パッケージ dirac-libs.i686 0:1.0.2-7m.mo8 は インストール です
---> パッケージ faac.i686 0:1.28-6m.mo8 は インストール です
---> パッケージ gsm.i686 0:1.0.13-4m.mo8 は インストール です
---> パッケージ jack-libs.i686 0:1.9.8-2m.mo8 は インストール です
---> パッケージ libpostproc.i686 0:0.10.4-1m.mo8 は インストール です
---> パッケージ libtheora.i686 0:1.1.1-5m.mo8 は インストール です
---> パッケージ libva.i686 0:1.1.0-3m.mo8 は インストール です
---> パッケージ libvpx.i686 0:1.1.0-1m.mo8 は インストール です
---> パッケージ openjpeg-libs.i686 0:1.5.0-2m.mo8 は インストール です
---> パッケージ schroedinger.i686 0:1.0.11-3m.mo8 は インストール です
---> パッケージ x264.i686 0:0.0.2208-0.20120720.1m.mo8 は インストール です

に出てくるパッケージをインストールすれば揃います。*1

lame をビルドする環境を整える

ははは、こんなの、build-momonga.i686 をインストールすればそれで終わりやろ、とは思った。

# yum install build-momonga.i686
読み込んだプラグイン:aliases, auto-update-debuginfo, changelog, dellsysid, download-order,
                   : downloadonly, etckeeper, fastestmirror, filter-data, fs-
                   : snapshot, installonlyn, keys, langpacks, list-data, local,
                   : merge-conf, post-transaction-actions, presto, priorities,
                   : protectbase, ps, puppetverify, refresh-packagekit, refresh-
                   : updatesd, remove-with-leaves, rpm-warm-cache, security,
                   : show-leaves, tmprepo, tsflags, upgrade-helper, verify,
                   : versionlock
Adding ja_JP to language list
Loading mirror speeds from cached hostfile
 * momonga-devel-x86_64: ftp.iij.ad.jp
Skipping filters plugin, no data
0 packages excluded due to repository protections
パッケージ build-momonga.i686 は利用できません。
エラー: 何もしません
# rpm -q build-momonga
build-momonga-1-20m.mo8.noarch

noarch やん。…というわけで、build-momonga のちからを借りることができず、地道にインストールしていくことになります。

インストールするパッケージは以下の通り。

! gcc.i686
  cpp.i686
  binutils.i686
  glibc-devel.i686
(他依存パッケージ少々)

必要最小限なので、ちらほらライブラリが見つからないとかエラーが出ますが、あまり気にしないことにします。

lame のビルド

いつもどおり、OmoiKondara でビルドするだけです (rpmbuild でも可能だと思いますがおすすめしません。momonga-nonfree-package-builder (MNPB) ではシステムの Arch に固定されているためビルドできません。)。

ただし、

  • すでに x86_64 のバイナリをビルドしている場合には SKIP になってしまいますので、-f を
  • lame は Nonfree ですから -n を
  • i686 のパッケージを作るので、 -A i686

忘れずにつけてください。

多分、ビルドできると思います。できたら、ローカルレポジトリに置きましょう。
(ローカルレポジトリを用意していない場合は、rpm -i でインストールしてください)

$ cd ..../Momonga/pkgs
$ ../tools/OmoiKondara -A i686 -nvf lame
$ ../tools/update-yum

いくつか無理があるので、chrootbuild の方が確実です。
やり方は こちら を参照。lame だけのために用意するのも…という感じもしますが。

Skype に必要なパッケージのインストール

以下の通り入れるのです! 依存の "葉っぱ" に当たる部分は下の 4 つだけです。

! libstdc++.i686
! libidn.i686
! flac.i686
! gd.i686
! libsamplerate.i686
! libdc1394.i686
! zvbi.i686
! ffmpeg.i686 (+ libpostproc.i686, x264.i686 : Loop)
! graphite2.i686
! libcroco.i686
  qt-x11.i686
  qtwebkit.i686
  libXScrnSaver.i686
  libXv.i686
(他依存パッケージ多数)

途中でわからなくなったら、

# yum check

を使えば依存関係の問題を確認できます。

Skype のインストール

skype.com からダウンロードしてきた fedorarpm

# rpm -ivh skype-4.1.0.20-fedora.i586.rpm

でインストールします。依存関係がちゃんとしてればすんなりいくはずです。

起動確認

$ skype

Core 2 Duo の環境では重いです。

f:id:lugia:20121229214940p:plain
快調です。

終わり。

*1:この場合、x264 と libpostproc が ffmpeg に依存している (ループ) のため、x264 と libpostproc は別に ffmpeg と同時に入れることになります。

“べいず”による自動ふぁぼ

を導入してみました。


もし、あなたの迷惑 (ふぁぼ通知音 etc.) になっているようでしたら、(寝ていない限り) すぐ止めますので、リプライなり DM なりください。

説明

実質的には https://github.com/toshia/fav/blob/master/fav_bayes.rb (+バグフィックス) です。ここにある通り、ベイジアンフィルタでふぁぼりますので、僕の意志に反したふぁぼ投下が頻繁に発生します。


mikutter のプラグインですので、ルギア君が家で mikutter を使っている時に受信したメッセージだけが対象です。


次のような文章はふぁぼの対象になりやすいです。

  • 英文のみ (空リプ etc.)
  • 記号のみ (「?」 etc.)
  • 日本語になっていない (「ぁあああん」 etc.)
  • 短い (「ふぁぼれよ」 etc.)
  • 以前のツイートと似たツイート (非公式 RT など要注意)


無駄な投下は逐次消しおりますが、通知される前に消すことはほぼ不可能です。


#○○な他人はRT てきなのは RT ではなく「ふぁぼ」ですのでお間違いのないようお願いいたします。

f:id:lugia:20120720200624p:plain

CMake で sed

を実装してみた。誰得。

# read from stdin
execute_process(COMMAND cat 
  OUTPUT_VARIABLE RET)

string(REPLACE ";" "\\;" RET "${RET}")
string(REPLACE "\n" ";" RET "${RET}")
string(REGEX REPLACE ";$" "" RET "${RET}")

foreach(I RANGE 1 "${CMAKE_ARGC}")
  set(ARGV "${ARGV}" "${CMAKE_ARGV${I}}")
endforeach()

string(ASCII 127 DEL)
foreach(MC IN LISTS ARGV)
  if(COMMAND_MODE)
    set(COMMAND ${COMMAND} "${MC}")
    set(COMMAND_MODE OFF)
  elseif(FILE_MODE)
    set(FILE ${FILE} "${MC}")
    set(FILE_MODE OFF)
  else()
    if(MC MATCHES "^-e")
      string(REGEX REPLACE "^-e(.*)$" "\\1" MC_ "${MC}")
      string(LENGTH "${MC_}" MC_L)
      if(MC_L GREATER 0)
	set(COMMAND ${COMMAND} "${MC_}")
      else()
	set(COMMAND_MODE ON)
      endif()
    elseif(MC MATCHES "^-f")
      string(REGEX REPLACE "^-f(.*)$" "\\1" MC_ "${MC}")
      string(LENGTH "${MC_}" MC_L)
      if(MC_L GREATER 0)
	set(FILE ${FILE} "${MC_}")
      else()
	set(FILE_MODE ON)
      endif()
    endif()
  endif()
endforeach()

foreach(F IN LISTS FILE)
  file(READ "${F}" C)
  string(REPLACE "\n" ";" C "${C}")
  set(COMMAND "${COMMAND}" "${C}")
endforeach()

set(CNT 1)
foreach(RR IN LISTS RET)
  set(RC "${RR}")
  foreach(C IN LISTS COMMAND)
    
    set(ADR_RE "\n")
    set(ADR_RA1 OFF)
    set(ADR_RA2 OFF)
    set(C1 OFF)
    set(C2 OFF)
    set(C3 OFF)
    
    string(REGEX MATCH "[sghd].*$" CC "${C}")
    string(SUBSTRING "${CC}" 0 1 CH)
    string(REGEX REPLACE "^/(.*)/ *[sghd].*$" "\\1" ADR_RE "${C}")
    if(ADR_RE STREQUAL C)
      set(C1 ON)
      set(ADR_RE "\n")
    endif()
    string(REGEX REPLACE "^([0-9$]*) *[sghd].*$" "\\1" ADR_RA1 "${C}")
    if(ADR_RA1 STREQUAL "" OR ADR_RA1 STREQUAL C)
      set(C2 ON)
    endif()
    string(REGEX REPLACE 
      "^([0-9$]*),([0-9$]*) *[sghd].*$" "\\1;\\2" ADR_RA2 "${C}")
    if(ADR_RA2 STREQUAL C)
      set(C3 ON)
    endif()
    if(NOT C2)
      set(ADR_RA2 "${ADR_RA1}")
    endif()
    if(NOT C3)
      list(GET ADR_RA2 0 ADR_RA1)
      list(GET ADR_RA2 1 ADR_RA3)
      set(ADR_RA2 ${ADR_RA3})
    endif()
    if(C1 AND C2 AND C3)
      set(ADR_RA1 "0")
      set(ADR_RA2 "2147483647")
    endif()
    if(ADR_RA1 STREQUAL "$")
      set(ADR_RA1 "0")
    endif()
    if(ADR_RA2 STREQUAL "$")
      set(ADR_RA2 "2147483647")
    endif()
    if(RR MATCHES "${ADR_RE}")
      set(C1 ON)
    elseif(CNT EQUAL ADR_RA1 OR CNT EQUAL ADR_RA2)
      set(C1 ON)
    elseif(CNT GREATER ADR_RA1 AND CNT LESS ADR_RA2)
      set(C1 ON)
    else()
      set(C1 OFF)
    endif()
    if(C1)
      if("${CH}" STREQUAL "s")
	string(SUBSTRING "${CC}" 1 1 CCH)
	set(REGMOD "")
	string(REGEX REPLACE 
	  "^s${CCH}(.*)${CCH}(.*)${CCH}(.*)$" "\\1" REGEXP "${CC}")
	string(REGEX REPLACE 
	  "^s${CCH}(.*)${CCH}(.*)${CCH}(.*)$" "\\2" REGREP "${CC}")
	string(REGEX REPLACE 
	  "^s${CCH}(.*)${CCH}(.*)${CCH}(.*)$" "\\3" REGMOD "${CC}")
	if(REGMOD STREQUAL "g")
	  string(REGEX REPLACE "${REGEXP}" "${REGREP}" RC "${RC}")
	elseif(REGEXP MATCHES "^\\^$")
	  set(RC "${REGREP}${RC}")
	elseif(REGEXP MATCHES "^\\\$$")
	  set(RC "${RC}${REGREP}")
	elseif(REGEXP MATCHES "^\\^")
	  string(REGEX REPLACE "${REGEXP}" "${REGREP}" RC "${RC}")
	elseif(REGEXP MATCHES "\\\$$")
	  string(REGEX REPLACE "${REGEXP}" "${REGREP}" RC "${RC}")
	else()
          string(REGEX REPLACE "([^\\])\\(" "\\1" BREGEXP "${REGEXP}")
          string(REGEX REPLACE "^\\(" "" BREGEXP "${BREGEXP}")
          string(REGEX REPLACE "([^\\])\\)" "\\1" BREGEXP "${BREGEXP}")
          string(REGEX REPLACE "\\\\" "\\\\\\\\" RCP "${RC}")
          string(REPLACE ";" "\\;" RCP "${RCP}")
          string(REGEX REPLACE "(\\\\?)${BREGEXP}(.*)$" "\\1\\1\\1;\\2"
            BREGEXP "${RCP}")
          string(REGEX REPLACE "([^\\])([][*+?|{()}^$])" "\\1\\\\\\2"
            BBREGEXP "${BREGEXP}")
          string(REGEX REPLACE "^([][*+?|{()}^$])" "\\\\\\1"
            BBREGEXP "${BBREGEXP}")
          list(GET  BREGEXP 0  B1)
          list(GET  BREGEXP 1  B2)
          list(GET BBREGEXP 0 BB1)
          list(GET BBREGEXP 1 BB2)
          string(REGEX REPLACE "\\\\\\\\$" "" BB1 "${BB1}")
          string(REGEX REPLACE "\\\\\\\\$" "" B1  "${B1}")
          set(BREG "${BB1}${REGEXP}${BB2}")
          set(BREP "${B1}${REGREP}${B2}")
          string(REGEX REPLACE "^${BREG}$" "${BREP}" RC "${RC}")
	endif()
      elseif("${CH}" STREQUAL "h")
	set(HOLD_SPACE "${RC}")
      elseif("${CH}" STREQUAL "g")
	set(RC "${HOLD_SPACE}")
      elseif("${CH}" STREQUAL "d")
	set(RC "${DEL}")
      endif()
    endif()
  endforeach()
  if(NOT RC STREQUAL "${DEL}")
    execute_process(COMMAND
      ${CMAKE_COMMAND} -E echo "${RC}")
  endif()
  math(EXPR CNT "${CNT} + 1")
endforeach()

フル仕様には全然至らず、対応するコマンドはとりあえず s, h, g, d だけだけどとりあえず自分の目的は果たせそうだからここで終わりかな。


って思ったけど正規表現の扱いが sed と全然違うので共通する動作を得るには正規表現に工夫が必要…。


ちなみに、CMake には標準入力から読み込む機能がないので、cat コマンドを叩いて読み込むという暴挙に出ています。


ものにもよりますが、本物の sed の 10 倍ぐらい遅いです。


【追記】 message(...) が stdout ではなく stderr に書き出していることが発覚したため cmake -E echo を実行するように変更。


【追記】 s/foo/bar/ (g なし!) にバグあったので修正した。バックスラッシュトルネード!! (意味不明

最強の Linux サウンドシステム (多分)


OSS, ALSA, Jack, PulseAudio のアプリを全部同時に使える、まさに究極のシステム…だと思う。

図の黒線は再生系統、赤線は録音系統。

制限事項

  • OSS については占有までシミュレーションする (というか API がファイルロックする?) ため、同方向の OSS のアプリを同時に2つ使うことはできない。
  • OSS アプリと ALSA アプリを同時に使った場合はブロックされないが、後から起動した方は音が鳴らない (ALSA アプリ同士は平気:snd-pcm-oss の仕様?)。
  • MIDI 関係は未確認 (ハードウェア音源を持っているわけでもないし)。

メモ

  • サウンドカードが 2 枚以上装着されている場合、snd-aloop のデバイスは hw:2、hw:3、…と末尾の番号を使うので読み替えてくれ。
  • Jack が握るサウンドカードは通常は 1 枚だけだが、この方法を使うことで Jack ですべてのサウンドカードを統括的に扱うことができるようになる。
  • できる限り Jack を使ったほうが繋ぎ変えなどで有利になるが遅延しやすい場合もある (後述)
  • できる限り他の人に影響を与えないようにする設定である。

必要なカーネルモジュール

  • snd-aloop
  • snd-pcm-oss

Momonga Linux では snd-aloop のモジュールを提供していない(現在の trunk では提供するよう変更済みですのでリビルドは不要です)ため、config ファイルを弄って kernel をリビルドすることで作成する。

ちなみに、再起動してもロードするようにするためには

/etc/module-load.d

に適当な名前のファイルを作って

snd-aloop
snd-pcm-oss

て書いておけば OK。ただし、これは systemd を使っている場合で、init や upstart の場合は設定はまた別なのでご注意を。

リビルド

有りそうでなかった。が、Jack の Firewire サポートの影響でクラッシュしてしまうので、これだけ切ってリビルドする(現在 trunk ではデフォルトでオフになるよう変更済みです)

  • qjackctl (お好みで)
  • jack
  • pulseaudio-module-jack
  • python (2.x 系 3.x 系どちらでも可。)

はインストールする。

OSS の設定

各アプリは ALSA の hw:1 に対応する

/dev/dsp1

を使わせる必要がある (が、これは非対応なアプリも多い)。

ALSA の設定

pcm.dsp0 {      type plug      slave.pcm "default" }

# ------------------------------------------------------
# Custom asoundrc file for use with snd-aloop and JACK
#

# ------------------------------------------------------
# playback device
pcm.aloopPlayback {
  type dmix
  ipc_key 1
  ipc_key_add_uid true
  slave {
    pcm "hw:Loopback,0,0"
    format S32_LE
    rate {
      @func igetenv
      vars [ JACK_SAMPLE_RATE ]
      default 44100
    }
    period_size {
      @func igetenv
      vars [ JACK_PERIOD_SIZE ]
      default 1024
    }
    buffer_size 4096
  }
}

# capture device
pcm.aloopCapture {
  type dsnoop
  ipc_key 2
  ipc_key_add_uid true
  slave {
    pcm "hw:Loopback,0,1"
    format S32_LE
    rate {
      @func igetenv
      vars [ JACK_SAMPLE_RATE ]
      default 44100
    }
    period_size {
      @func igetenv
      vars [ JACK_PERIOD_SIZE ]
      default 1024
    }
    buffer_size 4096
  }
}

# duplex device
pcm.aloopDuplex {
  type asym
  playback.pcm "aloopPlayback"
  capture.pcm "aloopCapture"
}

# ------------------------------------------------------
# default device

pcm.!default {
  type plug
  slave.pcm "aloopDuplex"
}

# ------------------------------------------------------
# alsa_in -j alsa_in -dcloop -q 1
pcm.cloop {
  type dsnoop
  ipc_key 3
  ipc_key_add_uid true
  slave {
    pcm "hw:Loopback,1,0"
    format S32_LE
    rate {
      @func igetenv
      vars [ JACK_SAMPLE_RATE ]
      default 44100
    }
    period_size {
      @func igetenv
      vars [ JACK_PERIOD_SIZE ]
      default 1024
    }
    buffer_size 4096
  }
}

# ------------------------------------------------------
# alsa_out -j alsa_out -dploop -q 1
pcm.ploop {
  type plug
  slave {
    pcm "hw:Loopback,1,1"
  }
}

Jack の設定

Driver を ALSA にし、メインのデバイスを hw:0 にしておくことだけ注意する。(default にしてはいけない)

端末で起動する場合は、

$ jackd -d alsa -d hw:0

QJackCtl を使って ~/.jackdrc を作ってもらうと楽。

PulseAudio の設定

$ cat ~/.pulse/default.pa 
#!/usr/bin/pulseaudio -nF

.nofail

load-module module-jack-sink channels=2 channel_map=front-left,front-right
load-module module-jack-source channels=2 channel_map=front-left,front-right

load-module module-native-protocol-unix
## The following is not mandatory

.ifexists /usr/lib/pulse-0.9/modules/module-x11-publish.so
load-module module-x11-publish
.endif
set-default-sink jack_out
set-default-source jack_in

ALSA と Jack の橋渡し

これをログイン時に動くようにしておく。同時に Jack も起動する。

Phonon

Gstreamer の jack sink を使うように変更する。そのほうが遅延が少ない。変更は qtconfig (Qt4) で。

OSS を使わないといけないアプリ

基本的にはないのだが、一部の Legacy アプリで必要な場合がある。1つ前の記事も参照。

ALSA を使わないといけないアプリ

  • Audacity
  • Timidity (Timidity は OSS と ESD にも対応している。)

など。

Jack を使わないといけないアプリ

  • Qtractor

など。

PulseAudio を使わないといけないアプリ

  • FlashPlayer-plugin + libflashsupport

など。

VLC

Jack よりも ALSA の方が遅延が少ない。うまくプログラムされていないのだろうか。

MPlayer

VLC ほどではないが、ALSA の方が遅延は少ないようである。

ffmpeg

Jack を使う。

$ ffmpeg -f jack -ac 2 -ar 44100 -acodec pcm_f32le -i FFMpeg_output ...

SDL

ALSA

$ SDL_AUDIODRIVER=alsa [SDL Application]

または OSS

$ SDL_AUDIODRIVER=dsp SDL_PATH_DSP=/dev/dsp1 [SDL Application]

http://www.libsdl.org/docs/html/sdlenvvars.html を参照。