python_wave のような処理の ruby 版を検討する。
以下は、全然スマートではないが、sox で nohead ファイルと相互変換して pack/unpack する例。 カレントディレクトリに中間ファイルを作成する。 サンプリングレートは 16KHz でモノラル 16bit とする。
def wav_read(file) system("sox -t wav #{file} -c 1 -r 16000 -t sw _tmp1.sw") data = nil File.open("_tmp1.sw", "rb") do |f1| data = f1.read.unpack("s*") end data end def wav_write(file, data) File.open("_tmp2.sw", "wb") do |f2| f2.write data.pack("s*") end system("sox -c 1 -r 16000 -t sw _tmp2.sw -t wav #{file}") end
最近の sox はファイル名の拡張子だけでフォーマットを判断しないようになっているらしい。 16bit signed short word であることを -t sw で明示している。
以下は、読み込んだファイルに前後1秒ずつの無音を付与して保存する例。
data = Array.new(16000, 0) data.concat wav_read("source.wav") data.concat Array.new(16000, 0) wav_write("dest.wav", data)
以下、wav_play のいくつかの実装例。 いずれの場合も ruby 1.8 では再生中に CTRL-C が効かないので、 最初の実装でよいのではなかろうか。
def wav_play(filename) system("play -q #{filename}") end def wav_play(filename) system("cat #{filename} | play -q -r 16000 -t wav -c 1 -") end def wav_play(filename) IO.popen("play -q -r 16000 -t wav -c 1 -", "wb") do |io| File.open(filename, "rb") do |file| io.write file.read end end end def wav_play(filename) IO.popen("play -q -r 16000 -t wav -c 1 -", "wb") do |io| File.open(filename, "rb") do |file| while d = file.read(10000) io.write d sleep 0.001 end end end end
play -q は –no-show-progress (Run in quiet mode) の意味。
クラスにしてメソッドが新しいインスタンスを返すようにすると、
wave = Wave.wav_read("src.wav").normalize.fadeout(1600) wave.wav_write("_dest.wav")
のようなことができる。以下、実装例:
# wave.rb class Wave attr_accessor :data def initialize @data = [] end def self.wav_read(file) wave = Wave.new system("sox -t wav #{file} -c 1 -r 16000 -t sw _tmp1.sw") File.open("_tmp1.sw", "rb") do |f1| wave.data = f1.read.unpack("s*") end wave end def self.play_file(filename) system("play -q #{filename}") sleep 0.1 end def get(index) @data[index] end def wav_write(file) File.open("_tmp2.sw", "wb") do |f2| f2.write @data.pack("s*") end system("sox -c 1 -r 16000 -t sw _tmp2.sw -t wav #{file}") end def wav_play wav_write("_tmp.wav", @data) Wave.play_file("_tmp.wav") end def size @data.size end def abs_max abs_max = 0 @data.each do |v| if v.abs > abs_max abs_max = v.abs end end abs_max end def normalize nw = Wave.new gain = 32700 / self.abs_max 0.upto(@data.size - 1) do |i| nw.data.push(@data[i] * gain) end nw end def fadeout(width) nw = Wave.new 0.upto(@data.size - 1) do |i| nw.data.push @data[i] end 1.upto(width) do |i| pos = nw.data.size - i gain = (i-1).to_f / width nw.data[pos] *= gain end nw end end