1

Rubyでwavファイルをいじる WavFile.rbを作った

gemにしました → 橋本商会 wavファイルをRubyで編集する

*****

Rubyでwavファイルを操作するためにWavFile.rbを作った。スピーカから音を鳴らすのではなくて、wavファイルそのものをいじって合成したりつなげたり、逆再生や左右反転させたりした後ファイルに書き出す為に作った。

packやunpackを使ってRubyでバイナリを読み書きする部分でかなり苦戦したけど、WAVファイル – MoonRock@MoonRock/A mere diary (2002-2)(7年も前に同じような事やってる!)がすごく参考になった。attr_accessorとかも知らなかったから勉強になった。


http://shokai.org/projects/ruby-wavfile/にサンプルを色々置いておく。


例えば、逆再生のwavファイルを作るコードはこう書ける
reverseWav.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# wavファイルを逆再生にして保存する
# ステレオの場合、左右チャンネルが入れ替わってしまうがまあいい
require File.dirname(__FILE__) + '/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の配列として取り出して処理する。ファイルに戻す部分はWavFile.rbがやってくれるようにした。

実行
ruby reverseWav.rb test.wav reverse.wav
結果
フォーマットID: 1
チャンネル数: 1
サンプリングレート: 8000 (Hz)
byte per sec: 16000
bit per sample: 16
ブロックサイズ: 2
data 4077172



主にできる事と、処理の手順はこんな感じ
  1. wavファイルのファイルヘッダ、フォーマットチャンク、データチャンクをメモリに読み込む。WavFile.rbではファイルヘッダとフォーマットチャンクをまとめて管理するためにWavFile::Formatクラスを作ってある。
  2. データチャンクを波形として扱う処理は時前でやってください。ステレオ・モノラルやbpsなどのフォーマットが全て読み込まれているのでそんなに大変ではないはず。format.bitPerSampleやunpackを使う。ただしCPUのエンディアンが違うとおかしくなるかも。
  3. 最後にいじったデータチャンクをpackでバイト列に戻して、ファイルヘッダと合わせてwavファイルに戻す。


上の例ではWavFile::readAllを使ってフォーマットと全チャンクを読み込んでいるけど、wavファイルにはデータチャンク(波形)以外のチャンクもある。でもデータチャンクとフォーマットさえあれば後は必要ない場合が多いので、フォーマットとデータチャンクのみを取り出す関数も用意してある。
require 'WavFile'
File.open("test.wav"){|file|
  format = WavFile::readFormat(file)
  dataChunk = WavFile::readDataChunk(file)
}

タプルを使ってこう受け取る事もできる
format, dataChunk = WavFile::read(file)

wavファイルへ保存
open("out.wav"){|out|
  WavFile.write(out, format, [dataChunk])
}


他にも色々やった。下にいくほど新しい。
上の方はWavFile.rbのバージョンが古い頃の物なので読み書きまわりが少し違うかもしれないが、解説が書いてあるので列挙しておく。最新のWavFile.rbで動くコードはリポジトリに置いておく


wavファイルの扱いについては、C言語で書かれたこの本を参考にした。この本ではファイルを先頭からseekして逐次処理して結果をwavファイルとして書き込んでいる。
でもwavファイルなんて数百MB程度だから、WavFile.rbではメモリ上で処理した方が後々便利そうだから今回は富豪的に全部メモリに読み込むようにした。
WAVプログラミング—C言語で学ぶ音響処理
北山 洋幸
カットシステム
売り上げランキング: 160207
おすすめ度の平均: 3.0
2 C言語初心者向けでわかりやすいが本格的な音響処理はいまいち
3 分かりやすいが…
4 あまり見かけないジャンルの本ですね



作ってる時に大変だったのは音を出さないとデバッグできないので電車の中で作業するためにイヤホンは必須。
バイナリとRubyのオブジェクトとのやりとりの部分は音で聞いてもなんだかわからない時もあるので、putsで波形を数字としてdumpしてエクセルで描画すると原因がすぐわかる。

0

FT232RLのEEPROMをすごい勢いで書き込む

俺はこの手順で効率よく大量に書き込んでいます


まずデバイスマネージャとMProgを起動して並べる。MProgにはEEPROMをloadしておく。
83f5648ab664ddc5a963416810c1f4ba.png


ターゲットのボードをUSBケーブルに刺す。

するとWindowsが「新しいハードウェアが見つかりました」と報告してくる。
14fe5d204e7bb00ffcc3707cfa10c1b5.png

このまま待っているとデバイスドライバのインストーラが起動してしまうので、
MProgからEEPROMを書き込む。だいたいwindowsのデバイス認識音がしてから3秒後にwriteする。


インストーラより先に書き込み成功すると、インストーラが起動しない。
あらかじめターゲットデバイスのドライバをインストールしてあれば、「使用準備ができました」になる
501a7c01d5793951465148ff1252debd.png


デバイスマネージャで「USB Serial Port」が増えている事を確認。
b361d82d4b05be6b382cbe633eecea07.png

デバイスをUSBケーブルから抜いて、次のデバイスにEEPROMを書き込む。

0

iPhoneからMac経由でdocomoケータイにアドレス帳をコピー

docomoの普通の携帯電話を買ったが、iPhoneからdocomoへのアドレス帳移行の手段が無い。

windowsのiTunesでiPhoneから読み込み→csv形式でドコモdatalinkで携帯電話に転送
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1124847736
という手段があるが、iTunesで読み込む時にoutlookがなければ駄目。windowsアドレス帳形式はドコモdatalinkで転送できない。


今回はiPhone→MacのiTunesでAddress Bookに読み込み→「グループのvcardを書き出し(vcf形式で)」→メールで携帯電話に転送
という手段で移行した。

ただし

  • 検索しやすいようにiPhoneでは読み仮名をローマ字で入力してたが、docomoはカタカナのみ対応
  • vcf形式は1ファイルに複数のvcardを保持できるはずなのに、SH-02Aは先頭のvcardしか読み込んでくれない
  • メールに同時に添付できるファイル数は10個まで

なので読み仮名は全部無くなったし、10個ずつ添付して登録したから面倒だった。


まず、一つのvcfファイルに複数のアドレス帳が入っていても全部読み込める機種なら、MacのAddress Bookからvcfで書き出して
nkf -s phone-addr.vcf > phone-addr-sjis.vcf
でshift jisに文字コードを変えれば読み込めるはず。(そういう機種があるならば)


ファイルを分割するためにスクリプトを書いた
convert-vcard-mac-docomo.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'kconv'

if ARGV.size < 2
puts 'error! 入力ファイルと出力先ディレクトリ名を指定してください'
puts 'ruby convert-vcard-mac-docomo.rb /path/to/input/file.vcf /path/to/output/dir/'
exit 1
end
input = ARGV.shift
output = ARGV.shift
puts output += "/" if !(output =~ /\/$/)

delimiter = "BEGIN:VCARD"
cards = open(input).read.split(/#{delimiter}/m)
cards.shift # 1件目いらない

for i in 0...cards.size do
puts card = delimiter+cards[i]
puts "-"*10
open(output+"#{i}.vcf","w"){|f|
f.puts card.tosjis
}
end

mkdir ~/vcards
ruby convert-vcard-mac-docomo.rb ~/phone-addr.vcf ~/vcards/
で ~/vcards/にたくさんファイルができるので、10件ずつメールに添付して送って登録した。
指が憑かれた。


後で気づいたが、読み仮名はsjisで
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:ʼヨミガナ;;;;
と書いておくと読み込めるみたい。
俺の場合ローマ字→カタカナに変換しないとならなかったので面倒だが、suikyoを使えば楽にできるかもしれない。

0

opencv-haartrainingの進行状況をtwitterに流すbot

OpenCVをソースからビルドするとhaarlike分類器(顔認識などに使われているやつ)の学習ツールが手に入るんだけど、たくさんのマシンでたくさん学習させているとそれぞれの進行状況をチェックするのが面倒になってくる。

でも、入力した画像ファイルが壊れていると学習が強制終了してしまったり、データがばらつきすぎてて収束しなくてあきらめて終了されたりするので、プロセスが死んでいたらパラメータを直してすぐやり直しをさせたい。学習中は予断を許さない状況が続く。

なので、進行状況を監視してtwitterアカウントshokai_logにpostするbotを作った。
5分間隔でopencv-haartrainingの作業ディレクトリとプロセスが生きているかをチェックする。
学習stageが進む毎に適当に通知し、プロセスが強制終了していた場合は激しくreplyしてくれる。これで安心して寝れる。OpenCV1.0/2.0両方対応。

プロセスが落ちていると教えてくれたり、段階が進む毎にtwitterに投稿したりする。
一見何言ってるのかわかりにくいpostもあるが、「ドドドド」だったらstage4が終わったという意味。
tweet-haartraining.rb
辞書はコード内にある。


第2引数にopencv-haartrainingの-dataオプションで渡した「結果の書き出し先ディレクトリ名」を指定する。第3引数は無しでもいいが、twitter投稿の末尾にメモを付けられる。複数のマシンで実行していてどれの進行状況かわからなくなる時は、マシンの名前を入れておけばいい。
ruby tweet-haartraining.rb /Users/sho/path/to/training/dir/ "Macbook黒"

tweet-haartraining.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'twitter'

# setting
USER = 'your-account'
PASS = 'your-password'
INTERVAL = 300 # sleep sec
YOU = 'shokai' # 時々replyしてくる nilでreplyなし
NOPOST = false # debug用

def post(message)
  return if !message
  message = "@#{YOU} #{message}" if rand(3)<1 if !(message =~ /@#{YOU}/) && rand(2)<1
  puts message + "\t" + Time.now.to_s
  return if NOPOST
  httpAuth = Twitter::HTTPAuth.new(USER, PASS)
  tw = Twitter::Base.new(httpAuth)
  tw.update(message)
end

if ARGV.size < 1
  puts '結果が出力されるディレクトリへのパスが必要です。メモも付けられます(オプション)'
  puts 'e.g. ruby tweet-haartraining.rb /path/to/haar/training/dir/ "研究室の学習用パソコン"'
  exit 1
end

puts path = ARGV.shift
memo = ARGV.shift || ""
dir_path = path
if path =~ /\/$/
  dir_path = path
  xml_path = path[0...path.size-1]+'.xml'
else
  dir_path = path+'/'
  xml_path = path+'.xml'
end


if stage_p = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  post "ステージ#{stage_p}から開始" + " " + memo
else
  messages = ["開始。",
              "はじめ",
              "起床",
              "おきた",
              "start",
              "スタートしました",
              "hello world",
              "hello work",
              "はじめますわっ",
              "スタンバイレディ セタップ"]
  post messages[rand(messages.size)] + " " + memo
end


while true do
  sleep INTERVAL
  stage = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  if File.exists? xml_path
    messages = ["全行程完了(ステージ#{stage})。お疲れ様でした。",
                "全部オワタ(#{stage})",
                "修了しました",
                "寝る。#{stage}時に起きる。",
                "終わったので、#{stage}時に帰ります",
                "全段階完了しました。データを回収し、電源を落としてください(#{xml_path.split(/\//).last})",
                "全ステージ完了しました(#{xml_path.split(/\//).last})",
                "ⓢⓤⓨⓐⓡⓘ"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    exit 0
  end
  if nil == `ps aux | grep opencv-haartraining`.split(/[\r\n]/).delete_if{|m|m=~/grep opencv-haartraining/}.first
    messages = ["#{stage}段階目まで来たけど異常終了したかも",
                "落ちてる",
                "ERROR! haartraining is not working. please restart \(^o^)/",
                "異常終了",
                "異常です",
                "動いてないっぽい・・・",
                "死んだかも",
                "だめっぽい・・",
                "おい、異常終了してるぞ",
                "冒 険 の 書 (#{stage}) は 消 え ま し た",
                "おお、死んでしまうとは情けない",
                "\(^o^)/"*stage,
                "ピッコロの気が消えた",
                "なん・・だと・・",
                "#{stage}面でピチュった"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    sleep INTERVAL*2
    next
  end
  next if stage == stage_p or stage == nil
  stage_p = stage
  messages = ["#{stage}段階目まで進みました",
              "バリバリです(stage#{stage})",
              "ばっちりですわっ",
              "------ここまで読んだ(#{stage})------",
              "がんばってます(#{stage})",
              "stage #{stage}",
              "ステージ#{stage}なう",
              "now finished stage#{stage}.",
              "よし!ステージ#{stage}まで終わった!!!",
              "うわ"+"あ"*stage,
              "ド"*stage,
              "ゴ"*stage,
              "ゴ"+"ー"*stage,
              "もりもり",
              "ふむふむなるほど"+"・"*stage,
              "頭が"*stage+"おかしくなりそうだ",
              "もういや",
              "無理"*stage,
              "ズザ"+"ー"*stage,
              "帰りたい",
              "まだ#{stage}段階目だ",
              "もうstage#{stage}まで終わった。超はやい",
              "もうstage#{stage}まで終わった",
              "stage#{stage}まで終わった",
              "stage#{stage}まで終わったし",
              "ククク・・遂に#{stage}界までまで昇ってきたか・・・",
              "ⓢⓤⓨⓐ"*stage]
  
  post messages[rand(messages.size)] + " " + memo
end

0

これAPIとして使えそうだな

37e3808047553cedb34daa9b1d7ab2a3.jpg