要点
- Rubyの配列の要素取り出しは
ary[p..q]
とary[p...q]
という表記があって、Python のary[p:q]
に相当するのは後者。 - オーディオサンプリング周波数 44100 を 4 倍して 1 足すと素数になる (←知ってました?)。
- FFT の一番広く使われている Cooley-Tukey のアルゴリズムはデータを因数分解した数に分けて細切れにしていく。
以上で何がいいたいかわかった方はそれで十分です。気をつけましょう…
本当にあった怖くて恥ずかしい話
Python の numpy で 4 s のオーディオ信号を FFT するプログラムがありました。
ぼくは Python が嫌いなので、それを mrkn 氏の素晴らしい Pycall (https://github.com/mrkn/pycall.rb) を使って Ruby に移植しました。その際、Python で 4 s 切り出す data[ptr:ptr + 44100 * 4]
という感じの部分を、うっかりdata[ptr..(ptr + 44100 * 4)]
と書いてしまった のです。もちろん正解は ..
ではなく ...
です。
その結果 謎のフリーズ。いや実際は単に計算に時間がかかっていただけなんですが、10 s 以上も固まれば、そりゃ ^C
押しますって。そして何は原因なんだろうpycallかな、などとあらぬ疑いを頓珍漢な方向に向けたりして リアルに3-4時間くらい時間を無駄にしました。(恥ずかしい。)
衝撃の再現コード
巷では Python の方が人気と聞くので Python のみで動くように書きました。実行すれば驚きを体験できます。
#!/usr/bin/python3
import numpy as np
fs = 44100
duration = 4
data = np.random.randn(fs * 10) # dummy data
print("test 1")
y = np.fft.fft(data[0:fs * duration])
print("test 2")
y = np.fft.fft(data[0:fs * duration + 1])
本当はいろいろ Ruby で書きたいんだけど numpy とか keras とかがないから必要悪と割り切って Python を使っている人は pycall.rb (やそれを使った numpy.rb) を使いましょう!上記のコードならこんな↓感じにほぼベタ移植できます。すごい > pycall
#!/usr/bin/ruby
require "numpy" # gem install numpy
fs = 44100
duration = 4
data = Numpy.random.randn(fs * 10) # dummy data
puts "test 1"
y = Numpy.fft.fft(data[0...fs * duration])
puts "test 2"
y = Numpy.fft.fft(data[0..fs * duration])
おわりに
- Ruby は文法がきれいで書きやすくて好きなんですが、まさかこういうハマり方をするとは思ってもいませんでした。自分が悪いんですけど。
- $44100\times4+1$ という大きめの覚えやすい素数が手に入ったのは収穫。ちなみに $44100\times4-1 = 419\times 421$ も
ふたつの素数の積なので注意。