Digitalians' Alchemy Diary Archive



あるけみすとの居る風景:2008 年 07 月

2008/07/29(火)

 GigaStudio 開発中止、と報道されてます。まあ、登場当時と比べると競争相手も増えたし、役割は終えたというところだろうか。これを受けて、"Life After Giga: A Call for Open Source Sampling Development" という記事も出ている。LinuxSampler はオープンソースのサンプラーとしてはまあまあのレベルに達していると思うのだが、この記事の筆者は「商用ソフトのコピーではなくて、『新しい』ものを!」と気勢を上げている。うーん、LinuxSampler のメーリングリストでも一時議論になってたんだけど、これだけの規模で一応動いているコードを一旦全部捨てて完全にゼロから書き直すというのは、そりゃうまくいけば美しい結果を生み出すんだろうけど、気の遠くなるほど膨大な作業なんだよね。この記事とコメント欄で「アイデア」を熱く語っている人たち、どうも「自分はこんな素晴らしいアイデアがあるから誰かコーディングしてくれぇ」と言いたいみたいだけど、オープンソースの開発者は「自分のアイデアを具体化する」ことに熱意を持ってコーディングするのであって、他人のアイデアに乗ってくれる人はあんまりいないんじゃないかしら。

2008/07/28(月)

 組み込み Ruby の件の続き。どうやら、キー割り込みの監視用スレッドと実行本体を別々の rb_eval_string コールで実行したのでは安定して動作しないみたい。考えてみたら当然という気もするのだが、逆に動くこともあるのが不思議と言えば不思議。そこで、実行したい内容を「レシーバ・メソッド・引数(の配列)」にまとめて、「チェック用スレッドと目的のメソッド呼び出しを同時に実行し、終わったらチェック用スレッドを止める」という内容の Proc に渡すようにしてみた。

static int s_interrupt_var_initialized = 0; static VALUE s_interrupt_thread = Qnil; static VALUE s_interrupt_proc = Qnil; static VALUE s_interrupt_lock = Qnil; static VALUE s_interrupt_flag = Qfalse; /* rb_protect 用の補助関数 */ static VALUE s_Ruby_CallMethod(VALUE val) { void **ptr = (void **)val; VALUE receiver = (VALUE)ptr[0]; ID method_id = (ID)ptr[1]; VALUE args = (VALUE)ptr[2]; return rb_funcall(s_interrupt_proc, rb_intern("call"), 3, receiver, ID2SYM(method_id), args); } /* 割り込みチェック付きでメソッドを呼び出す */ VALUE Ruby_CallMethodWithInterrupt(VALUE receiver, ID method_id, VALUE args, int *status) { void *ptr[3]; VALUE retval; if (s_interrupt_var_initialized == 0) { rb_define_variable("$interrupt_thread", &s_interrupt_thread); rb_define_variable("$interrupt_proc", &s_interrupt_proc); rb_define_variable("$interrupt_lock", &s_interrupt_lock); rb_define_variable("$interrupt_flag", &s_interrupt_flag); rb_eval_string_protect("require 'thread'; $interrupt_lock = Mutex.new", status); if (*status != 0) return Qnil; s_interrupt_proc = rb_eval_string_protect( "Proc.new { |rec, method, args| ¥n" /* 監視用スレッドがすでに走っていれば止める */ " runflag = set_interrupt_flag(false) ¥n" /* 現在の監視用スレッドを退避 */ " thread_save = $interrupt_thread ¥n" /* 新しい監視用スレッドを作る */ " $interrupt_thread = Thread.new { ¥n" " while 1 ¥n" " 10.times { Thread.pass; Thread.stop if !get_interrupt_flag } ¥n" " if check_interrupt > 0 ¥n" /* "raise Interrupt" は Ruby1.8.6 ではエラーになる */ " Thread.main.raise Interrupt.new(nil) ¥n" " end ¥n" " end ¥n" " } ¥n" /* 割り込みチェックを開始する */ " set_interrupt_flag(true) ¥n" " begin ¥n" /* メソッド呼び出し本体 */ " rec.send(method, *args) ¥n" " ensure ¥n" /* 後始末 */ /* 監視用スレッドを止める */ " $interrupt_thread.kill ¥n" /* 以前の監視用スレッドを復帰 */ " $interrupt_thread = thread_save ¥n" /* 監視用スレッドが走っていたなら再開 */ " set_interrupt_flag(runflag) ¥n" " end ¥n" "}", status); if (*status != 0) return Qnil; s_interrupt_var_initialized = 1; } ptr[0] = (void *)receiver; ptr[1] = (void *)method_id; ptr[2] = (void *)args; retval = rb_protect(s_Ruby_CallMethod, (VALUE)ptr, status); return retval; }

 set_interrupt_flag, get_interrupt_flag は次のように実装している。

static VALUE s_SetInterruptFlag(VALUE self, VALUE val) { VALUE oldval; /* 値を true/false に揃える */ if (val != Qundef) val = ((val == Qfalse || val == Qnil) ? Qfalse : Qtrue); /* 同期制御 */ if (s_interrupt_lock != Qnil) rb_funcall(s_interrupt_lock, rb_intern("lock"), 0); oldval = s_interrupt_flag; if (val != Qundef) s_interrupt_flag = val; if (s_interrupt_lock != Qnil) rb_funcall(s_interrupt_lock, rb_intern("unlock"), 0); if (s_interrupt_thread != Qnil) { if (val == Qtrue) /* 監視用スレッドを再開する */ rb_funcall(s_interrupt_thread, rb_intern("wakeup"), 0); else if (val == Qfalse) /* 監視用スレッドが止まるまで待つ */ rb_eval_string("while $interrupt_thread.status == ¥"run¥"; end"); } return oldval; } static VALUE s_GetInterruptFlag(VALUE self) { return s_SetInterruptFlag(self, Qundef); }

 肝心の check_interrupt はだいたいこんな感じ。

static VALUE s_CheckInterrupt(VALUE self) { if (s_GetInterruptFlag(self) == Qfalse) return INT2NUM(-1); else { /* AppKit (Objective-C) code */ NSEvent *event = [NSApp nextEventMatchingMask: NSKeyDownMask untilDate: nil inMode: NSEventTrackingRunLoopMode dequeue: YES]; if (event != nil) { NSString *s = [event charactersIgnoringModifiers]; unsigned int flags = [event modifierFlags]; if ([s isEqualToString: @"."] && (flags & NSCommandKeyMask) != 0) return INT2NUM(1); } return INT2NUM(0); } }

 今のところ 10.4 (Ruby 1.8.2), 10.5 (Ruby 1.8.6) どちらでも動いているようです。まだ細かい問題はいろいろありそうだが、なんとか峠は越えたかな。

2008/07/25(金)

 7/1 に書いた組み込み Ruby とキースキャンの件のつづき。手元の PowerBook G4 (10.4) では一応動いているのに、MacBook (10.5) に持って行くと落ちまくって使い物にならない。いろいろ調べて気がついたこと(全部が今回のバグに関係あるとは限らない):

 解決案として、Thread.new で監視用スレッドを作るのと、実際にスクリプトを走らせるのを、同じ Objective-C 関数の中からやるようにしてみた。これなら、Ruby 側でコンテキストが切り替わっても、Objective-C に関係するスタックの中身は変わらないだろう、という読み。これでダメだったら rb_add_event_hook() を使うしかないかなあ。

2008/07/21(月)

 またワミーで遊んでみた。

星形ワミー・ボール1
これも正十二面体だが、頂点をとんがらせてみた。できあがってしまえばきれいに対称形になるのだが、途中段階ではパーツがこの形に曲がったままではいてくれないので、制作はかなり難しい。

正四面体
今度は正四面体。少し趣向を変えて、頂点に集まるパーツの色を揃えてある。この方がきれいかな。

正四面体×2 正四面体×2
一つ前の正四面体を2つ互いに貫通させて組み合わせると、立方体になる。左は3回軸の方向から見た図、右は面の法線方向から見た図。

Boy's surface 1
前回はクラインのびんを作ったので、今回は頑張って射影平面に挑戦。射影平面を3次元に埋め込むやり方はいくつもあるのだが、Boy's surface(Wikipedia 英語版Mathematisches Forschungsinstitut Oberwolfach)が一番気に入ったので、これを目標にしてみた。残念ながら全然そうは見えないところが悲しい。一応トポロジーは正しい(と思う)し、3回対称も実現できたのだが。

Boy's surface 2
横から見た図。射影平面は円盤とメビウスの帯を貼り合わせた閉曲面だが、図の下半分が円盤、上半分がメビウスの帯になっている。

Boy's surface 3
上半分は、3枚の帯を半回転ひねったものを貼り合わせて作ってある(それで3回対称になる)。そのうちの一枚について、ひねった部分を拡大した写真。

Boy's surface 4
射影平面の3次元埋め込みには欠かせない(?)、面の自己交差の部分。

 まあ、ワミーで Boy's surface を作るというのは世界初の試みでしょうな(ちなみに、レゴで制作した例はある)。今回作ったのは上半分がどうもチンチクリンで見栄えがよろしくないので、後日改良版を制作する予定。

2008/07/20(日)

 ヒメ、坊の幼稚園の学芸会。年長のヒメは、3つのクラスが合同で演し物をするのだが、今までヒメに聞いてもどんな内容なのかさっぱり要領を得なかった。要領を得ないのも道理で、総計40分にもわたって内容に乏しい踊りがひたすら脈絡無く並ぶというとんでもない演目だった。「地球をみんなで守ろう」的なタイトルがつけられていてメッセージ性を織り込もうとした形跡があるのだが、これだけ内容が支離滅裂では何も伝わりようがない。去年、一昨年の演し物はテレビアニメの歌に振り付けをした踊りで、いかにも幼稚園児らしくかわいく楽しげだったのに、いったいあなた方はどこに行こうとしているのでしょうか。もっとも、ヒメは(他の園児たちも)そんな中でもそれなりに楽しげに自分の役割を果たしてはいた。こんな幼少時からかくも意味不明の企画に強制的に参加させるとは、なんとも嘆かわしい「教育的配慮」である。一方、年少の坊は「ドラえもん」メドレーの踊りで、こちらはまあまあ普通だった。ステージの上で泣き出したり「ばぶ」して寝転がったりということもなかったので、ひとまず安心。

2008/07/18(金)

 野茂投手引退ですか! うーむ、ついにこの日が来てしまったか。実は、野茂が一番活躍していた時期は僕がテレビも見ず外出もほとんどしていなかった時期と重なっているので、実際に投げているところは映像ですらほとんど見たことがないのだが、それでも僕にとっては最大のヒーローの一人だった。ちょうどアメリカに滞在した年が野茂のドジャースでの最初のシーズンで、周りのアメリカ人たちが驚きの目を見張って野茂を賞賛していたのが、それ以前からのファンとして無性に嬉しかったものだった。9月にロッキーズ戦でノーヒットノーランを達成した時、両親に「あの試合見に行ってなかったのか?」なんて言われたのだが、確かにコロラド州に住んでたわけだから野茂の登板予定に合わせてドジャース戦を見に行くぐらいのことはやるべきだったと今でも思ってるけど、なんかめんどくさくってねえ。まあファンとは言ってもこの程度のへたれファンだったわけだが。

 近鉄バファローズの新人として、最初に取ったアウトが清原からだったというのは、今回の関連報道を見て初めて知った(無死満塁で三振を奪った)。野茂対清原というのは「平成の名勝負」と言われているそうで、ふーんそうだったんだ。僕はライオンズ時代の清原は(も)あんまり記憶に残ってなくて、デストラーデの方がよほど印象に残っているんだけどな。ちなみに、メジャーリーグでの最初のアウトも三振なんですね。さすがはドクターKだな。

 大石大二郎(オリックスバファローズ監督代行)のコメントがすこぶる正直。「三振か四球という投手だったので、野手としては守りづらかった。」いやまあそりゃそうだろうけど、みんな今日だけはそれは言わないでおこうと思ってた(だろう)のに。さすがオールスターで江川の9連続三振をぼてぼての二塁ゴロで阻止しただけのことはある。野茂が独立独歩なら大石も独立独歩。なんかバファローズ応援したくなってきたよ。いや一応ファンなんですけど、何しろへたれファンなもんで。

2008/07/07(月)

 アイ・オー・データの HDP-U250S(K) を購入。仕事場の MacBook の TimeMachine バックアップ用。60 GB のハードディスクを使っていたのだが、1日分のバックアップしか保持できず、これでは TimeMachine の意味がない、ということで、奮発してみた。説明書が丁寧で(Win 用と Mac 用がそれぞれ別々に入っていて、Mac 用の方には Mac OS 9 での使い方まで書いてある)、使いもしない Windows 専用の余分なユーティリティもほとんどついてこないし、なかなかよろしい。アマゾンで購入したのだが、なぜかシルバーの方が値段が高く、しかも在庫切れだったため、白の MacBook に黒の DP-U250 ということになってしまった。まあ、特に気にするほどのことではない。

2008/07/06(日)

 ワミーでいろいろ遊んでおります。ヒメは小さなアクセサリみたいなのを熱心に作っている。僕はこういうやつ専門。

ワミー・ボール1
素直な正二十面体。色が5色あるので、全部の頂点を同じ配色にしようと思ったのだが、どうやら不可能みたい。白−赤−緑−青−黄と、白−黄−緑−赤−青の二種類を交互に置いてある。

ワミー・ボール2
ちょっとひねった正二十面体。頂点のところを編み合わせてある。

ワミー・ボール3
同じく正十二面体。

ワミー・クラインびん
クラインのびん(リンクは Wikipedia)。写真ではわかりにくいが、青・黄・緑の部分が本体で、赤・白でできた管が貫通している。

2008/07/01(火)

 引き続き(っていつの「続き」だ?)、Ruby の組み込みに奮闘中。いろいろわかってきた。

 これまでのところで一番厄介だったのは、スクリプトを command-ピリオドで止める処理。Cocoa アプリのキー入力はすべて run loop で処理されるが、スクリプト実行中は run loop が走っていないため、うっかり while 1; end みたいなスクリプトを実行してしまうと止める方法がなくなる。

 自前でキースキャンをやるには NSApplicationnextEventMatchingMask:untilDate:inMode:dequeue: メソッドを使えばよいのだが、これをどこから呼び出すかが問題。最初、Ruby の set_trace_func 機能を使ってみたのだが、スクリプトの速度低下があまりにも激しかった。1.8.6 なら C レベルで rb_set_event_hook() というのが使えるのだが、Mac OS 10.4 の Ruby は 1.8.2 なのでだめ。

 いろいろ試行錯誤した末、Ruby の Thread クラスを使うことにした。Cocoa アプリからスクリプトを実行する前に、Thread.new { while 1; sleep 1; Thread.main.raise Interrupt if check_interrupt > 0; end } という風にキー監視用のスレッドを走らせておく(check_interrupt はキー入力をチェックして、command-ピリオドなら 1 を返す関数)。Thread.main.raise がポイント。

 ここで注意すべきは、Cocoa はデフォルトではマルチスレッド対応になっていないこと。このため、Ruby の Thread を使う前に、NSThread を使って1回ダミーのスレッドを立てておく必要がある。NSThreaddetachNewThreadSelector:toTarget:withObject: を1回でも呼ぶと、以後はマルチスレッド対応になる。また、Ruby スクリプトを走らせていない時や、Ruby スクリプトからダイアログを出している時など、Cocoa 側で run loop が走っている時は、上の監視スレッドを止めておかないといけない。これにはグローバル変数と Mutex を使う。

 実はここに至るまでに、TCL と Python の組み込みを試してみたのだが、Ruby が一番楽な気がする。TCL は組み込み自体は簡単なのだが、言語の機能が高くないので、ちょっと込み入ったスクリプトを書こうとするとぐちゃぐちゃになってしまう。Python の組み込みは、ドキュメントが充実していてやるべきことは明快なのだが、コードの量が妙に多くなる。あと僕は「インデントの量がブロックレベルを表す」という Python の文法がどうにも辛い(組み込みには関係ないけど)。もちろんブロックレベルとインデントは対応させるべきだが、それを文法で強制されるのはしんどいんだな。