目次

Ruby における Wave ファイルの処理

Wave ライブラリを実装する

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 = 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