#!/usr/bin/env ruby # coding: utf-8 # create base.wav # sox -n -r 44100 -b 16 -c 2 base.wav trim 0 1 # sssim (Sound Space Simulator) # sssim base.wav begin $LOAD_PATH.unshift('/usr/local/lib/ruby') require 'libwav' rescue LoadError raise end include Math # +45° 0° +y 座標系 # \ | # \ | # \ | # +90° -------O------- +x(m) # | class Man # 自分の位置、向き、耳の間の距離(m) def initialize(x = 0.0, y = 0.0, d = 0.0, w0 = 0.17) @x = x; @y = y; @d = d; @w = w0 / 2 @sources = [] end # 移動する def move(x, y) @x += x; @y += y end # 向きを変える(左正<->負右) def turn(d) @d += d end # 耳の位置を算出して返す def ear_position r = @d * PI / 180 lx = @x - cos(r) * @w; ly = @y - sin(r) * @w # 左耳 rx = @x + cos(r) * @w; ry = @y + sin(r) * @w # 右耳 [[lx, ly], [rx, ry]] end # 音源オブジェクトを追加 def <<(source) @sources << source end # 指定の時刻間の音をレンダリング def rend(base_wav, t0 = 0, t1 = 1, file = 'output.wav') wav = NewWavFile.new(base_wav) wav.get_info[0].each {|l| puts(l) } vol = 30000 * (5 ** 2) # 音量係数(5mの位置で±30000) t0 *= 44100; t1 *= 44100; (t0...t1).each {|t| yield(t) ear_pos = ear_position l_gain = r_gain = 0; @sources.each {|source| src_pos = source.position(t) l_dist = sqrt((src_pos[0] - ear_pos[0][0]) ** 2 + (src_pos[1] - ear_pos[0][1]) ** 2) r_dist = sqrt((src_pos[0] - ear_pos[1][0]) ** 2 + (src_pos[1] - ear_pos[1][1]) ** 2) l_time = l_dist * 44100 / 340.290 # 音速(340.29m/s)による遅れ(1/44100s単位) r_time = r_dist * 44100 / 340.290 l_gain += source.get_gain(t - l_time) * (vol / l_dist ** 2) # 過去の発生音量(ゲイン)、距離減衰を加味 r_gain += source.get_gain(t - r_time) * (vol / r_dist ** 2) } wav << [l_gain, r_gain] } wav.save_phrase(0, t1 - t0, file) end end class SoundSource def initialize(file = nil) file and @wav = NewWavFile.new(file) end # 時刻 t の時点の位置を返す、(0, 0) から 10m の位置を 4 秒で左回りに周回する def position(t) dist = 10 d = t.to_f * 90 / 44100 r = d * PI / 180 [-dist * sin(r), dist * cos(r)] end # 音(波)を発生 def get_gain(t) g0 = get_gain0(t0 = t.floor) g1 = get_gain0(t0 + 1) g0 + (g1 - g0) * (t - t0) # 線形補間 end def get_gain0(t) (@wav.get_gain(t)[1] || 0).to_f / 32768 end end class Ambulance < SoundSource # 時刻 t の時点の位置を返す、時速 60km (100m 前方から、100m 後方まで 12 秒)で、右 5m の位置を通り過ぎる def position(t) t1 = t % (12 * 44100) [5, 100 - t1.to_f * 200 / 44100 / 12] end # 音(波)を発生 def get_gain(t) f = t % 52920 > 26460 ? 770.0 : 960.0 omega = 2 * PI * f / 44100 sin(omega * t) end end class Gaisensha < SoundSource def initialize(file = nil, int = 0) super(file) @int = int end # 時刻 t の時点の位置を返す、時速 60km (100m 前方から、100m 後方まで 12 秒)で、右 5m の位置を通り過ぎる def position(t) t1 = (t - @int * 44100) % (12 * 44100) [5, 100 - t1.to_f * 200 / 44100 / 12] end end class PanoramaCar < SoundSource def initialize(file = nil, int = 0, side = 1) super(file) @int = int @side = side end # 時刻 t の時点の位置を返す、時速 60km (100m 前方から、100m 後方まで 12 秒)で、右 5m の位置を通り過ぎる def position(t) t1 = (t - @int * 44100) % (12 * 44100) [5 * @side, 100 - t1.to_f * 200 / 44100 / 12] end end man = Man.new(0, 0) ss = SoundSource.new('pacmon.wav') man << ss man.rend(ARGV[0], 0, 10) {|t| } #amb = Ambulance.new #man << amb #man.rend(ARGV[0], 0, 12) {|t| #} #gs1 = Gaisensha.new('kimigayo.wav') #man << gs1 #gs2 = Gaisensha.new('kimigayo.wav', 3) #man << gs2 #man.rend(ARGV[0], 0, 24) {|t| #} #pc1 = PanoramaCar.new('panoramacar.wav') #man << pc1 #pc2 = PanoramaCar.new('panoramacar.wav', 3, -1) #man << pc2 #man.rend(ARGV[0], 0, 24) {|t| #} __END__