MacRuby+HotCocoa でオーディオファイルの再生

Ruby 1.9 && Cocoa -> MacRuby

ruby-dev で少し前にこんな文章が流れてました。via [ruby-dev:38931]

RubyCocoaruby 1.9に対応する予定は今のところありません。
1.9系はMacRubyの守備範囲という認識です。

ふむふむ。ところでよく訓練された信者Rubyist はもう 1.9 系に移行しているはずですよね!! ということは三段論法により「よく訓練された RubyistCocoa が使いたければ MacRuby を使う」という命題が導出されますね。

MacRuby インストール

というわけで MacRuby をインストールしてみました。安定版は 0.4 です。わたしは MacRubyの公式サイトの「Source」のページをみて GitHub から git clone しました。

http://www.macruby.org/source.html

……ところが、実は今の master ブランチは開発版がマージされてしまっていて、今普通に git clone すると 0.5 が取れてきてしまいます。この記事は 8/23 に書いていますが、実際にわたしがソースを取ってきたのは 7/29 のことで、8/5 頃にマージが行なわれたようです。しかも安定版のbranchもなければ tag もついていません。どういうことなの……。まあともかくログで「copying branches/experimental as trunk」とあるのが 0.5 をマージした変更だと思われますので、その前に移動すれば 0.4 が得られると思います。さすが Ruby の眷属だけあって豪快なバージョン管理ですね。そんなところ真似しなくていいのに。

ともかくソースを取ってきたらあとはディレクトリを移動して

$ rake
$ sudo rake install

これで完了です。すっごい簡単。CRuby よりもスムースかも。何がどこに置かれるのか気になる神経質な人もいるかと思います。わたしがそうでした。rake install する前にそのタスクで何をするのかいちいちチェックしていましたが、その最後でそんな必要ないことがわかりました。 ".installed.list" というファイルにどのファイルがインストールされたのかが記録されますので、それをみればばっちりわかります。掃除したくなったらこれをみて消せばいいので安心です。すばらしい。

これで /usr/local/bin に macruby とか macirb とか macgem とか macrake とかがインストールされます。

macirb でちょっと遊ぶ

Rubyist であれば誰でも、そこに irb があれば脊髄反射的に起動して遊びますよね。噂にはきいていた MacRuby のクラスツリーの違いを確認してみました。

>> Object.ancestors
=> [NSObject, Kernel]
>> String.ancestors
=> [NSMutableString, NSString, Comparable, NSObject, Kernel]
>> Array.ancestors
=> [NSMutableArray, NSArray, Enumerable, NSObject, Kernel]
>> Hash.ancestors
=> [NSMutableDictionary, NSDictionary, Enumerable, NSObject, Kernel]

まあそういうことです。見ての通り NSObject とか NSString とか Objective-C のクラスを継承しているように見えています。これで String や Array 等をそのまま Cocoaフレームワークに渡せるところが RubyCocoa(Proxyオブジェクトが必要)に対する MacRuby の advantage ってことでしたね。

NSSound でオーディオファイルの再生

わたしの主な目的は音を鳴らすことなので NSSound というクラスを使ってみることにしました。
ところがこの調査が思いの外難航しました。MacRuby ではフレームワークからロードした Objective-C のクラス(の wapper?)に対して instance_methods や singleton_methods がうまくメソッド名を返してくれないようなんです。Ruby を弄る時ってドキュメントを読むのは最後で、まずこれらのメソッドを利用してクラスの概要をみてみるのが癖になっていたので、これにはまいりました。

まず Cocoaフレームワークをロードするには framework という MacRuby で追加されたメソッドを利用します。これは require と似たようなもので、Mac OS X の Framework という拡張ライブラリ+メタデータみたいなもの*1をロードするためのものです。するとクラスの定義がたくさん追加されます。

次に HotCocoa をrequire して、読み込んだ Cocoa フレームワークを簡単に使えるようにする wrapper を定義してもらいます。そうすると NSSound のために HotCocoa.sound という module function が定義されるので、これを使うと、以下のようにしてオーディオファイルを再生することができるようになります。

#!/usr/local/bin/macruby

framework "cocoa"
require "hotcocoa"

filepath = ARGV.first

sound = HotCocoa.sound file:filepath

# 再生開始
sound.play
# NSSound#play はすぐに戻ってくるので
# NSSound#duration で取得できる再生時間(sec)のぶんだけ待つ
sleep sound.duration

ファイル形式は .wav, .aiff, .mp3, .m4a, .mid などどれでもいけました。MIDI が再生できるのはちょっと驚き。

Core Audio へ

NSSound を使うととても簡単に音を鳴らすことができますが、オーディオファイルを指定するか、その内容を流し込むことで再生する方法にしか対応してないようです。リアルタイムに音を鳴らしたい場合は Core Audio を直接使う必要があります。

試しに framework "coreaudio" とするとなにがしかロードされました。しかし Core Audio は C で書かれたフレームワークで、うまいこと名前空間を分離した Module ができるかと思いきや、トップレベル(Object? Kernel?)にメソッドが追加されるようです。C の関数がかなりそのまま見えてしまっているので、このまま使うのは骨が折れそうです。少なくとも名前空間の隔離については MacRuby の framework でのロードの仕様を洗練して欲しいところです。

MacRuby 0.5 へ

0.5 では VMLLVM を採用していて、またバイナリにコンパイルする機能があるなど興味深いトピックがあるのでソースコードは探索してみたいものです。

*1:多分このへんはすごい適当なこと書いてます