SVX日記
2022-08-26(Fri) 色々なノイズをジェネレートする
シツコく飽きずにオリジナルのシューティングの製作をポチポチと進めているのだが、いくつか敵キャラクタを用意したところで、そろそろ効果音が欲しくなってきた。
以前に「sox」というコマンドラインツールを使って効果音を作ったことがあり、少しイジり始めたのだが、音に対する自分の知識が少ないのに対して、ツールが多機能すぎるので、どうにも勝手がわからない。このパターンは……あー、いかんて、いかんて、次はサウンドエディタ作り始めちま……で、またもや「ヤクの毛刈り」の始まりである。
で、正弦波、矩形波、三角波、鋸歯状波あたりを生成、合成や変調、周波数、音量を変化させるアルゴリズムを組んだ辺りで、ノイズの実装が必要になってきた。ん? ノイズって、種類があったよな。ピンクノイズとか。フムン。ブラウンノイズなんてのもあるのか。このパターンは……あー、いかんて、いかんて、次はノイズ生成エンジン作り始めちま……と、さらなる「ヤクの毛刈り」の始まりである。
問題はピンクノイズ。1/fゆらぎの実装が必要らしい。1/fゆらぎといえば、このブログを始める前にUSB扇風機にPICマイコンを使ってそれっぽい制御を組み込んだことがあったな……などと、思い出す。弱い変化は頻繁、強い変化は稀、というヤツだ。が、結局よくわからず。ググッてどこかで見つけたサイコロ理論をアレンジして組み込んだ。
だいぶバラついているように見えるが、左の軸が自動調整により拡大されすぎているからだ。-21dB±1dBの範囲に収まっており、ホワイトノイズの特徴である「平坦なパワースペクトル」が実現できている。次はブラウンノイズ。
ブラウンノイズの特徴は「1オクターヴあたりパワーが6dB降下」である。言い換えると「周波数が倍になる都度パワーが6dB降下」。理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。最後はピンクノイズ。
ピンクノイズの特徴は「1オクターヴあたりパワーが3dB降下」である。言い換えると「周波数が倍になる都度パワーが3dB降下」。同じく、理想的な降下ラインを赤で示してみたが、ほぼピッタリであり、実現できたといえよう。
というわけで、調べると他にも「ブルーノイズ」「パープルノイズ」などがあるようであるが、やりだすとキリがないので、今回はここまで。コードを置いておく。ノイズ生成のキモは「when」に続く各数行のみだ。ピンクはちょっとアクロバチックかな。
 #!/usr/bin/env ruby
 # coding: utf-8
 
 # create base.wav
 #   sox -n -r 44100 -b 16 -c 2 base.wav trim 0 1
 
 begin
     $LOAD_PATH.unshift('/usr/local/lib/ruby')
     require 'libwav'
 rescue LoadError
     raise
 end
 
 include Math
 
 ARGV.size < 2 and abort(<<USAGE % $0)
 Usage:
   $ %s base.wav white|brown|pink [sec]
 USAGE
 
 wav = NewWavFile.new(ARGV[0])
 wav.get_info[0].each {|l|
     puts(l)
 }
 type = ARGV[1]
 sec = (it = ARGV[2]) ? it.to_i : 1
 
 case(type)
   when('white')
     gain = Proc.new {
         (rand(0) - 0.5) * 65536
     }
 
   when('brown')
     gain = Proc.new {|g|
         g + (rand(0) - 0.5) * 1024
     }
 
   when('pink')
     gain = Proc.new {|g, pbuf|
         (9 - ('%b' % (rand(255) + 1)).length).times {|n|
             g -= pbuf[n]
             g += (pbuf[n] = (rand(0) - 0.5) * 8192)
         }
         g
     }
 
   else
     raise('Unexpected noise type.')
 end
 
 srand; g = 0; pbuf = [0] * 8
 (n_sample = wav.freq * sec).times {|n|
     wav[n] = [g, g]
     g = gain.call(g, pbuf)
     g < -32768 and g = -32768
     g >  32767 and g =  32767
 }
 
 wav.save_phrase(0, n_sample, '%s_noise.wav' % type)
 
 __END__