DirectSoundとRubyのプログラミング その12
続き。
前回の最後のほうで構成について考えたが、現在の構成では波形のリアルタイム生成がやりにくい。できることはできるが、MySoundクラスを作り直す感じになってしまうことになるので無駄に手間がかかる。
IO→デコーダ→MySound→SoundTestという4階層構造のどこかを差し替えてリアルタイム生成をする感じにできるとよいのだが、MySoundはベースに使うクラスなのでこれを作り直すんじゃなくて、これをそのまま使う感じで、データを生成する部分であるデコーダを差し替える感じにしてみたい。
カスタムデコーダ
データを読み込む部分はIOで、読み込んだデータをリニアPCMの文字列に変換するのがデコーダである。この部分を差し替えるとIOも必要なくなるわけだが、別に何かしらを入力してダメということは無いし、他のデコーダとの互換性の問題もあるのでとりあえず何かしら受け取るようにはしておこう。使わないぶんには問題ない。
デコーダはこのような形に作る
class MyDecoder < CustomDecoder def initialize(io) @pos = 0 end def byte_per_sample;2;end def rate;44100;end def channels;1;end def size;nil;end def eof?;false;end def seek(v);@pos=v;end def read(size) self.create_buffer(Array.new(size / byte_per_sample / channels){ tmp = Math.sin(2 * Math::PI * @pos / (self.rate / 440.0)) @pos += 1 tmp }) end end
MySoundがデコーダに要求するメソッド一式を用意する。ここでCustomDecoderというクラスと、create_bufferというメソッドが出てくるが、これはC側で実装するカスタムデコーダ用サポート機能である。以下のようなソース、customdecoder.cを作る。
#include "ruby.h" // RubyのCostomDecoderクラス static VALUE cCostomDecoder; // Rubyの例外オブジェクト static VALUE eCustomDecoderError; // CustomDecoder#create_buffer static VALUE CustomDecoder_im_create_buffer(VALUE self, VALUE vary) { int i, byte_per_sample; VALUE vstr; Check_Type(vary, T_ARRAY); byte_per_sample = NUM2INT(rb_funcall(self, rb_intern_const("byte_per_sample"), 0)); if (byte_per_sample != 1 && byte_per_sample != 2) rb_raise(eCustomDecoderError, "byte_per_sample error"); if (byte_per_sample == 1) { // 8bit unsigned char *buf = ALLOCA_N(unsigned char, RARRAY_LEN(vary)); for(i = 0; i < RARRAY_LEN(vary); i++) { double tmp = NUM2DBL(rb_ary_entry(vary, i)); *(buf + i) = (unsigned char)((tmp + 1) / 2 * 255); } vstr = rb_str_new((char *)buf, RARRAY_LEN(vary)); } else { // 16bit short *buf = ALLOCA_N(short, RARRAY_LEN(vary)); for(i = 0; i < RARRAY_LEN(vary); i++) { double tmp = NUM2DBL(rb_ary_entry(vary, i)); *(buf + i) = (short)(tmp * 32767); } vstr = rb_str_new((char *)buf, RARRAY_LEN(vary) * 2); } return vstr; } void Init_customdecoder(void) { // 例外定義 eCustomDecoderError = rb_define_class( "CustomDecoderError", rb_eRuntimeError ); // CustomDecoderクラス生成 cCostomDecoder = rb_define_class("CustomDecoder", rb_cObject); rb_define_method(cCostomDecoder, "create_buffer", CustomDecoder_im_create_buffer, 1); }
短い。create_bufferしか持っていないクラスである。create_bufferは-1.0〜1.0の値を詰め込んだ配列をバイナリの文字列に変換してくれる。内部でselfのbyte_per_sampleを呼び出して量子化ビット数を自動判別する。
カスタムデコーダのreadはこれを使って文字列化したデータを返すので、配列を受け取るSoundTest#writebufが必要なくなった。SoundTestStrというクラスも必要なくなるので、文字列を受け取るもののみに一本化してSoundTestという名前にしてしまおう。修正箇所は面倒なので省略。
MySound修正
こうなる。
class MySound < SoundTest def self.load(filename) MySound.new(open(filename, 'rb')) end def self.load_from_memory(str) MySound.new(StringIO.new(str)) end def initialize(io, klass = nil) if klass @decoder = klass.new(io) else begin @decoder = SoundWav.new(io) rescue begin io.seek(0) @decoder = SoundOgg.new(io) rescue raise SoundTestError, 'format error' end end end @loop = false # play時のループ指定 @end_count = 2 if @decoder.size && @decoder.size < @decoder.rate super(@decoder.size, ->size{@decoder.read(size)}, false, @decoder.rate, @decoder.byte_per_sample, @decoder.channels) else # 大きなデータなら1秒ぶんのバッファを作ってストリーミング再生する prc = ->size{ if @end_count == 1 @end_count = 0 return (@decoder.byte_per_sample == 1 ? 128.chr : 0.chr) * size elsif @end_count == 0 return nil end buf = @decoder.read(size) if @decoder.eof? if @loop @decoder.seek(0) tmp = @decoder.read(size - buf.size) buf += tmp else @end_count = 1 return buf + (@decoder.byte_per_sample == 1 ? 128.chr : 0.chr) * (size - buf.size) end end buf } super(@decoder.rate, prc, true, @decoder.rate, @decoder.byte_per_sample, @decoder.channels) end end def play(lp=false) @loop = lp super end end
initializeの最初のところが変わった。klassを受け取り、設定されていればklass.newでデコーダを生成する。ここでioを渡すが、今回のカスタムデコーダは受け取ったioを使わない。このへんのインターフェイスはもうちょい考えたほうがいいかもしれないが、重要な感じでもないのでどうでもいいかもしれない。
これを使う場合の生成はこんな感じになる。
rs1 = MySound.new(nil, MyDecoder)
MyDecoderは単純な正弦波を生成する。
そういえばMyDecoderのsizeがnilを返すようになっているが、MySoundのinitializeをちょこっと修正して、nilだった場合はストリーミング再生をするようにしてある。eof?をfalse固定で返しているのでどんだけreadしてもデータが終了することは無い。つまりこのパターンの指定は無限ストリームという意味で、正弦波の再生はいつまで経っても終わらない。