wavファイルのフォーマットを書き換えて倍速再生にする

前:wavファイルのフォーマットを読み込む - 橋本詳解


wavのDSPをやる準備が整った。
wavヘッダをメモリに読み込んでrubyの変数として扱って、バイナリに書き戻せるようになった。dataチャンクの中の波形をいじれば音を変えれる。


まずは波形ではなくwavヘッダの周波数とbpsの値をいじって、倍速再生のwavファイルを作る例。
かなりバイナリをすんなり扱えるラッパーができた。


(後述のWavFile.rbを使う)
test.rb

#!/usr/bin/env ruby
require 'WavFile'

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

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

puts format.to_s # フォーマット表示
chunks.each{|c|
  puts "#{c.name} #{c.size}" # 各チャンクの中身を見てみる
}
puts '=====ここまで元ファイルの情報====='

format.hz *= 2 # 倍速
format.bytePerSec *= 2 # 倍速設定
puts format.to_s # 倍速になってるか確かめる

out = open(ARGV.shift, "w")
WavFile::write(out, format, chunks) # 倍速フォーマットで保存
out.close

実行してみる
ruby test.rb input.wav out.wav

フォーマットID: 1
チャンネル数: 2
サンプリングレート: 44100 (Hz)
byte per sec: 176400
bit per sample: 16 
ブロックサイズ: 4
data 43720704
=====ここまで元ファイルの情報=====
フォーマットID: 1
チャンネル数: 2
サンプリングレート: 88200 (Hz)
byte per sec: 352800
bit per sample: 16 
ブロックサイズ: 4

サンプリングレートとbyte per secが倍になっている。再生すると倍速になってる。



bpsがbytes per secondsじゃなくてbits per secondsなのに注意。
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('S')+
        [@channel].pack('S') +
        [@hz].pack('V') +
        [@bytePerSec].pack('V') +
        [@blockSize].pack('S') +
        [@bitPerSample].pack('S')
    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