逆再生するwavを作る

dataチャンクから配列として波形を取り出せるか試してみた。Rubyはunpackで取り出してpackでバイナリに戻せる(しかもけっこう高速に)のでこれを使っていこうかな。


nameがdataのchunkのデータ部分が波形データなので、wav formatを見て16bitか8bitか確かめて、それぞれunpackすると1hz分ごとの数値の入った配列が得られる。ステレオだとLRLRLRLRと入っているので左右チャンネルが入れ替わってしまうがまあ、とりあえず気にしなくていいか。

reverseWav.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# wavファイルを逆再生にして保存する
# ステレオの場合、左右チャンネルが入れ替わってしまう
require 'WavFile'

if ARGV.size < 2
  puts 'ruby reverseWav.rb input.rb output.wav'
  exit 1
end

f = open(ARGV.shift)
format, chunks = WavFile::readAll(f)
f.close

puts format.to_s

dataChunk = nil
chunks.each{|c|
  puts "#{c.name} #{c.size}"
  dataChunk = c if c.name == 'data' # 波形の入っているchunkを探す
}
if dataChunk == nil
  puts 'no data chunk'
  exit 1
end

bit = 's*' if format.bitPerSample == 16 # int16_t
bit = 'c*' if format.bitPerSample == 8 # signed char
wavs = dataChunk.data.unpack(bit) # 16bit or 8bitずつbinaryから読み出し
dataChunk.data = wavs.reverse.pack(bit) # 逆再生、binaryに戻す

open(ARGV.shift, "w"){|out|
  WavFile::write(out, format, [dataChunk])
}


実行

ruby reverseWav.rb ~/test.wav reverse.wav

結果

フォーマットID: 1
チャンネル数: 2
サンプリングレート: 44100 (Hz)
byte per sec: 176400
bit per sample: 16 
ブロックサイズ: 4
data 44951040


WavFile.rb

module WavFile

  class WavFormatError < StandardError
  end

  class Chunk
    attr_accessor(:name, :size, :data)

    def initialize(file)
      @name = file.read(4)
      @size = file.read(4).unpack("V")[0].to_i
      @data = file.read(@size)
    end

    def to_bin
      @name + [@data.size].pack('V') + @data
    end
  end

  class Format
    attr_accessor(:id, :channel, :hz, :bytePerSec, :blockSize, :bitPerSample)
    
    def initialize(chunk)
      return if chunk.class != Chunk
      return if chunk.name != 'fmt '
      @id = chunk.data.slice(0,2)[0].to_i
      @channel = chunk.data.slice(2,2)[0].to_i
      @hz = chunk.data.slice(4,4).unpack('V').join.to_i
      @bytePerSec = chunk.data.slice(8,4).unpack('V').join.to_i
      @blockSize = chunk.data.slice(12,2)[0].to_i
      @bitPerSample = chunk.data.slice(14,2)[0].to_i
    end

    def to_s
      <<EOS
フォーマットID: #{@id}
チャンネル数: #{@channel}
サンプリングレート: #{@hz} (Hz)
byte per sec: #{@bytePerSec}
bit per sample: #{@bitPerSample} 
ブロックサイズ: #{blockSize}
EOS
    end

    def to_bin
      [@id].pack('v')+
        [@channel].pack('v') +
        [@hz].pack('V') +
        [@bytePerSec].pack('V') +
        [@blockSize].pack('v') +
        [@bitPerSample].pack('v')
    end
  end

  def WavFile.readFormat(f)
    f.binmode
    f.seek(0)
    header = f.read(12)
    riff = header.slice(0,4)
    data_size = header.slice(4,4).unpack('V')[0].to_i
    wave = header.slice(8,4)
    raise(WavFormatError) if riff != 'RIFF' or wave != 'WAVE'
    
    formatChunk = Chunk.new(f)
    Format.new(formatChunk)
  end

  def WavFile.readAll(f)
    format = readFormat(f)
    chunks = Array.new
    while !f.eof?
      chunk = Chunk.new(f)
      chunks << chunk
    end
    return format, chunks
  end

  def WavFile.write(f, format, dataChunks)
    header_file_size = 4
    dataChunks.each{|c|
      header_file_size += c.data.size + 8
    }
    f.write('RIFF' + [header_file_size].pack('V') + 'WAVE')
    f.write("fmt ")
    f.write([format.to_bin.size].pack('V'))
    f.write(format.to_bin)
    dataChunks.each{|c|
      f.write(c.to_bin)
    }
  end

end