DirectSoundとRubyのプログラミング その15

続き。
目立つバグをつぶしてpauseとresumeを実装したので次はフェードイン/アウトを実装してみよう。

どのように作るか

基本的にはフェード処理を流用する。play/stop/pause/resumeそれぞれに引数を追加して、時間と、フェードインの場合は目標値を設定できるようにする。ついでに引数を全部キーワード引数にして、あと、パンの移動はmoveメソッドに変更してみた。
それからfading?、moving?と、stop_fade、stop_move、フェード中のステータス(目標値や時間)を取得するfade_statusとmove_statusも追加した。

悩ましい問題

例えば、playでフェードインする場合、フェードイン開始時点でのボリュームが100だとして、これを自動的に0に落として100まで変化する、という動きにする。目標値を開始時点のボリュームとすると、フェードアウトして0になった音をフェードインさせようとしても0→0の変化になってしまう。なので、フェードインする場合は目標値を指定しないといけない。
しかし、例えば普通にstopで停止した音はボリュームが0になってるわけではなく、ボリューム100で停止状態である。フェードアウトする音も、フェードアウトしているだけでボリュームの設定はあくまでも100であり、フェードアウトしているリアルなボリュームは下がっていくが、設定値としては100なのである。従って、設定値とリアルなボリュームの値は別に持つ必要がある。
ということで実装としては、設定値としてのMySound#volume/volume=と、フェードイン/アウト中のリアルなボリュームを取得するMySound#real_volumeを分けて、内部で別管理するようにした。そのためにSoundTest#volumeはSoundTest#_volumeと改名してある。
また、並列動作はしないにしてもマルチスレッド動作するため、Mutexでロックするようにした。

Ruby側実装

設定値とリアルなボリュームを分けるため、フェードイン/アウトと通常のフェード処理は分けておいた。通常のフェード処理が使われるのかどうかはよくわからない。フェード中は@fadingをtrueにして、ハッシュを見に行かないようにした。MySound#fading?はこの値を参照する。
あと、ツクールを参考にプリデコードする条件を3秒として、それ以下のデータについては1/5秒分のバッファを生成してストリーミング再生するようにしておいた。このへんは微妙な調整である。
その他、細かい条件により処理をわけて、なんとなく色んな状況でそこそこうまく動くように地道に調整した。コードはずいぶん長くなってしまったのでgistに置いておいた。

C側実装

メソッド名を変えただけである。gistに置いておいた。

サンプル

こんな感じのコードで一通り実験することができる。

require 'dxruby'
require_relative 'mysound'

rs1 = MySound.load("test.ogg")
rs1.play(loop:true, time:1, volume:100)

Window.loop do
  rs1.move(-100, 1) if Input.key_push?(K_LEFT)
  rs1.move(100, 1) if Input.key_push?(K_RIGHT)
  rs1.move(0, 1) if Input.key_push?(K_SPACE)
  rs1.fade(0, 1) if Input.key_push?(K_DOWN)
  rs1.fade(100, 1) if Input.key_push?(K_UP)

  Window.draw_font(0, 0, "playing? : #{rs1.playing?.to_s}", Font.default)
  Window.draw_font(0, 24, "pausing? : #{rs1.pausing?.to_s}", Font.default)
  Window.draw_font(0, 48, "fading? : #{rs1.fading?.to_s}", Font.default)
  Window.draw_font(0, 72, "moving? : #{rs1.moving?.to_s}", Font.default)
  Window.draw_font(0, 96, "pan : #{rs1.pan.to_i.to_s}", Font.default)
  Window.draw_font(0, 120, "volume : #{rs1.volume.to_i.to_s}, real:#{rs1.real_volume.to_i.to_s}", Font.default)

  Window.draw_line(0, 200, 639, 200, C_WHITE)
  Window.draw_font(320.0 * rs1.pan / 100 + 320 - 12, 200, '', Font.default, color:C_GREEN)

  rs1.play(loop:true, time:1) if Input.key_push?(K_A)
  rs1.stop(time:1) if Input.key_push?(K_S)
  rs1.resume(time:1) if Input.key_push?(K_D)
  rs1.pause(time:1) if Input.key_push?(K_F)
  rs1.play(loop:true) if Input.key_push?(K_Z)
  rs1.stop if Input.key_push?(K_X)
  rs1.resume if Input.key_push?(K_C)
  rs1.pause if Input.key_push?(K_V)
end

でも良く考えてみたらこれではフェードやパン移動中の決め打ち設定ができないのでそのへんがおかしくなる可能性がなくはない。

おしまい

えらい苦労したが、こんなコード書いてバグが無いはずがない、という感じである。
誰かもっといい感じのコード書いてくれないかなーと思いつつ、今回はこれにておしまい。